mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-07-06 18:47:20 +00:00
feat: native timestamps
This commit is contained in:
parent
eb5fda8d19
commit
6006504459
7 changed files with 305 additions and 243 deletions
|
@ -2,10 +2,23 @@ import humanizeDuration from "humanize-duration";
|
|||
import { getMemberLevel } from "knub/helpers";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { CaseTypes } from "../../../data/CaseTypes.js";
|
||||
import { clearExpiringTempban, registerExpiringTempban } from "../../../data/loops/expiringTempbansLoop.js";
|
||||
import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import {
|
||||
clearExpiringTempban,
|
||||
registerExpiringTempban,
|
||||
} from "../../../data/loops/expiringTempbansLoop.js";
|
||||
import {
|
||||
canActOn,
|
||||
hasPermission,
|
||||
sendErrorMessage,
|
||||
sendSuccessMessage,
|
||||
} from "../../../pluginUtils.js";
|
||||
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin.js";
|
||||
import { renderUsername, resolveMember, resolveUser } from "../../../utils.js";
|
||||
import {
|
||||
renderUsername,
|
||||
resolveMember,
|
||||
resolveUser,
|
||||
toRelativeNativeTimestamp,
|
||||
} from "../../../utils.js";
|
||||
import { banLock } from "../../../utils/lockNameHelpers.js";
|
||||
import { waitForButtonConfirm } from "../../../utils/waitForInteraction.js";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin.js";
|
||||
|
@ -16,204 +29,247 @@ import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromA
|
|||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
notify: ct.string({ option: true }),
|
||||
"notify-channel": ct.textChannel({ option: true }),
|
||||
"delete-days": ct.number({ option: true, shortcut: "d" }),
|
||||
mod: ct.member({ option: true }),
|
||||
notify: ct.string({ option: true }),
|
||||
"notify-channel": ct.textChannel({ option: true }),
|
||||
"delete-days": ct.number({ option: true, shortcut: "d" }),
|
||||
};
|
||||
|
||||
export const BanCmd = modActionsCmd({
|
||||
trigger: "ban",
|
||||
permission: "can_ban",
|
||||
description: "Ban or Tempban the specified member",
|
||||
trigger: "ban",
|
||||
permission: "can_ban",
|
||||
description: "Ban or Tempban the specified member",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
time: ct.delay(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
time: ct.delay(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
...opts,
|
||||
},
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user.id) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
return;
|
||||
}
|
||||
const time = args["time"] ? args["time"] : null;
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user.id) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
return;
|
||||
}
|
||||
const time = args["time"] ? args["time"] : null;
|
||||
|
||||
const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]);
|
||||
const memberToBan = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.member;
|
||||
if (args.mod) {
|
||||
if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) {
|
||||
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
|
||||
return;
|
||||
}
|
||||
const reason = formatReasonWithAttachments(args.reason, [
|
||||
...msg.attachments.values(),
|
||||
]);
|
||||
const memberToBan = await resolveMember(
|
||||
pluginData.client,
|
||||
pluginData.guild,
|
||||
user.id,
|
||||
);
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.member;
|
||||
if (args.mod) {
|
||||
if (
|
||||
!(await hasPermission(pluginData, "can_act_as_other", {
|
||||
message: msg,
|
||||
channelId: msg.channel.id,
|
||||
}))
|
||||
) {
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
"You don't have permission to use -mod",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
mod = args.mod;
|
||||
}
|
||||
mod = args.mod;
|
||||
}
|
||||
|
||||
// acquire a lock because of the needed user-inputs below (if banned/not on server)
|
||||
const lock = await pluginData.locks.acquire(banLock(user));
|
||||
let forceban = false;
|
||||
const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id);
|
||||
if (!memberToBan) {
|
||||
const banned = await isBanned(pluginData, user.id);
|
||||
if (banned) {
|
||||
// Abort if trying to ban user indefinitely if they are already banned indefinitely
|
||||
if (!existingTempban && !time) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User is already banned indefinitely.`);
|
||||
return;
|
||||
}
|
||||
// acquire a lock because of the needed user-inputs below (if banned/not on server)
|
||||
const lock = await pluginData.locks.acquire(banLock(user));
|
||||
let forceban = false;
|
||||
const existingTempban =
|
||||
await pluginData.state.tempbans.findExistingTempbanForUserId(user.id);
|
||||
if (!memberToBan) {
|
||||
const banned = await isBanned(pluginData, user.id);
|
||||
if (banned) {
|
||||
// Abort if trying to ban user indefinitely if they are already banned indefinitely
|
||||
if (!existingTempban && !time) {
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`User is already banned indefinitely.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask the mod if we should update the existing ban
|
||||
const reply = await waitForButtonConfirm(
|
||||
msg.channel,
|
||||
{ content: "Failed to message the user. Log the warning anyway?" },
|
||||
{ confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id },
|
||||
);
|
||||
if (!reply) {
|
||||
sendErrorMessage(pluginData, msg.channel, "User already banned, update cancelled by moderator");
|
||||
lock.unlock();
|
||||
return;
|
||||
} else {
|
||||
// Update or add new tempban / remove old tempban
|
||||
if (time && time > 0) {
|
||||
if (existingTempban) {
|
||||
await pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id);
|
||||
} else {
|
||||
await pluginData.state.tempbans.addTempban(user.id, time, mod.id);
|
||||
}
|
||||
const tempban = (await pluginData.state.tempbans.findExistingTempbanForUserId(user.id))!;
|
||||
registerExpiringTempban(tempban);
|
||||
} else if (existingTempban) {
|
||||
clearExpiringTempban(existingTempban);
|
||||
pluginData.state.tempbans.clear(user.id);
|
||||
}
|
||||
// Ask the mod if we should update the existing ban
|
||||
const reply = await waitForButtonConfirm(
|
||||
msg.channel,
|
||||
{ content: "Failed to message the user. Log the warning anyway?" },
|
||||
{ confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id },
|
||||
);
|
||||
if (!reply) {
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
"User already banned, update cancelled by moderator",
|
||||
);
|
||||
lock.unlock();
|
||||
return;
|
||||
} else {
|
||||
// Update or add new tempban / remove old tempban
|
||||
if (time && time > 0) {
|
||||
if (existingTempban) {
|
||||
await pluginData.state.tempbans.updateExpiryTime(
|
||||
user.id,
|
||||
time,
|
||||
mod.id,
|
||||
);
|
||||
} else {
|
||||
await pluginData.state.tempbans.addTempban(user.id, time, mod.id);
|
||||
}
|
||||
const tempban =
|
||||
(await pluginData.state.tempbans.findExistingTempbanForUserId(
|
||||
user.id,
|
||||
))!;
|
||||
registerExpiringTempban(tempban);
|
||||
} else if (existingTempban) {
|
||||
clearExpiringTempban(existingTempban);
|
||||
pluginData.state.tempbans.clear(user.id);
|
||||
}
|
||||
|
||||
// Create a new case for the updated ban since we never stored the old case id and log the action
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Ban,
|
||||
userId: user.id,
|
||||
reason,
|
||||
noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`],
|
||||
});
|
||||
if (time) {
|
||||
pluginData.getPlugin(LogsPlugin).logMemberTimedBan({
|
||||
mod: mod.user,
|
||||
user,
|
||||
caseNumber: createdCase.case_number,
|
||||
reason,
|
||||
banTime: humanizeDuration(time),
|
||||
});
|
||||
} else {
|
||||
pluginData.getPlugin(LogsPlugin).logMemberBan({
|
||||
mod: mod.user,
|
||||
user,
|
||||
caseNumber: createdCase.case_number,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
// Create a new case for the updated ban since we never stored the old case id and log the action
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Ban,
|
||||
userId: user.id,
|
||||
reason,
|
||||
noteDetails: [
|
||||
`Ban updated to ${time ? `expire ${toRelativeNativeTimestamp(time)}` : "indefinite"}`,
|
||||
],
|
||||
});
|
||||
if (time) {
|
||||
pluginData.getPlugin(LogsPlugin).logMemberTimedBan({
|
||||
mod: mod.user,
|
||||
user,
|
||||
caseNumber: createdCase.case_number,
|
||||
reason,
|
||||
banTime: humanizeDuration(time),
|
||||
});
|
||||
} else {
|
||||
pluginData.getPlugin(LogsPlugin).logMemberBan({
|
||||
mod: mod.user,
|
||||
user,
|
||||
caseNumber: createdCase.case_number,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`,
|
||||
);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Ask the mod if we should upgrade to a forceban as the user is not on the server
|
||||
const reply = await waitForButtonConfirm(
|
||||
msg.channel,
|
||||
{ content: "User not on server, forceban instead?" },
|
||||
{ confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id },
|
||||
);
|
||||
if (!reply) {
|
||||
sendErrorMessage(pluginData, msg.channel, "User not on server, ban cancelled by moderator");
|
||||
lock.unlock();
|
||||
return;
|
||||
} else {
|
||||
forceban = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Ban updated to ${time ? `expire ${toRelativeNativeTimestamp(time)}` : "indefinite"}`,
|
||||
);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Ask the mod if we should upgrade to a forceban as the user is not on the server
|
||||
const reply = await waitForButtonConfirm(
|
||||
msg.channel,
|
||||
{ content: "User not on server, forceban instead?" },
|
||||
{ confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id },
|
||||
);
|
||||
if (!reply) {
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
"User not on server, ban cancelled by moderator",
|
||||
);
|
||||
lock.unlock();
|
||||
return;
|
||||
} else {
|
||||
forceban = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we're allowed to ban this member if they are on the server
|
||||
if (!forceban && !canActOn(pluginData, msg.member, memberToBan!)) {
|
||||
const ourLevel = getMemberLevel(pluginData, msg.member);
|
||||
const targetLevel = getMemberLevel(pluginData, memberToBan!);
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Cannot ban: target permission level is equal or higher to yours, ${targetLevel} >= ${ourLevel}`,
|
||||
);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
// Make sure we're allowed to ban this member if they are on the server
|
||||
if (!forceban && !canActOn(pluginData, msg.member, memberToBan!)) {
|
||||
const ourLevel = getMemberLevel(pluginData, msg.member);
|
||||
const targetLevel = getMemberLevel(pluginData, memberToBan!);
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Cannot ban: target permission level is equal or higher to yours, ${targetLevel} >= ${ourLevel}`,
|
||||
);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
let contactMethods;
|
||||
try {
|
||||
contactMethods = readContactMethodsFromArgs(args);
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, e.message);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
let contactMethods;
|
||||
try {
|
||||
contactMethods = readContactMethodsFromArgs(args);
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, e.message);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
const deleteMessageDays =
|
||||
args["delete-days"] ?? (await pluginData.config.getForMessage(msg)).ban_delete_message_days;
|
||||
const banResult = await banUserId(
|
||||
pluginData,
|
||||
user.id,
|
||||
reason,
|
||||
{
|
||||
contactMethods,
|
||||
caseArgs: {
|
||||
modId: mod.id,
|
||||
ppId: mod.id !== msg.author.id ? msg.author.id : undefined,
|
||||
},
|
||||
deleteMessageDays,
|
||||
modId: mod.id,
|
||||
},
|
||||
time,
|
||||
);
|
||||
const deleteMessageDays =
|
||||
args["delete-days"] ??
|
||||
(await pluginData.config.getForMessage(msg)).ban_delete_message_days;
|
||||
const banResult = await banUserId(
|
||||
pluginData,
|
||||
user.id,
|
||||
reason,
|
||||
{
|
||||
contactMethods,
|
||||
caseArgs: {
|
||||
modId: mod.id,
|
||||
ppId: mod.id !== msg.author.id ? msg.author.id : undefined,
|
||||
},
|
||||
deleteMessageDays,
|
||||
modId: mod.id,
|
||||
},
|
||||
time,
|
||||
);
|
||||
|
||||
if (banResult.status === "failed") {
|
||||
sendErrorMessage(pluginData, msg.channel, `Failed to ban member: ${banResult.error}`);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
if (banResult.status === "failed") {
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Failed to ban member: ${banResult.error}`,
|
||||
);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
let forTime = "";
|
||||
if (time && time > 0) {
|
||||
forTime = `for ${humanizeDuration(time)} `;
|
||||
}
|
||||
let forTime = "";
|
||||
if (time && time > 0) {
|
||||
forTime = `for ${humanizeDuration(time)} `;
|
||||
}
|
||||
|
||||
// Confirm the action to the moderator
|
||||
let response = "";
|
||||
if (!forceban) {
|
||||
response = `Banned **${renderUsername(user)}** ${forTime}(Case #${banResult.case.case_number})`;
|
||||
if (banResult.notifyResult.text) response += ` (${banResult.notifyResult.text})`;
|
||||
} else {
|
||||
response = `Member forcebanned ${forTime}(Case #${banResult.case.case_number})`;
|
||||
}
|
||||
// Confirm the action to the moderator
|
||||
let response = "";
|
||||
if (!forceban) {
|
||||
response = `Banned **${renderUsername(user)}** ${forTime}(Case #${banResult.case.case_number})`;
|
||||
if (banResult.notifyResult.text)
|
||||
response += ` (${banResult.notifyResult.text})`;
|
||||
} else {
|
||||
response = `Member forcebanned ${forTime}(Case #${banResult.case.case_number})`;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
sendSuccessMessage(pluginData, msg.channel, response);
|
||||
},
|
||||
lock.unlock();
|
||||
sendSuccessMessage(pluginData, msg.channel, response);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
notifyUser,
|
||||
resolveMember,
|
||||
resolveUser,
|
||||
toRelativeNativeTimestamp,
|
||||
ucfirst,
|
||||
} from "../../../utils.js";
|
||||
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects.js";
|
||||
|
@ -172,7 +173,7 @@ export async function banUserId(
|
|||
user,
|
||||
caseNumber: createdCase.case_number,
|
||||
reason: reason ?? "",
|
||||
banTime: humanizeDuration(banTime),
|
||||
banTime: toRelativeNativeTimestamp(banTime),
|
||||
});
|
||||
} else {
|
||||
pluginData.getPlugin(LogsPlugin).logMemberBan({
|
||||
|
|
|
@ -10,7 +10,7 @@ import moment from "moment-timezone";
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { humanizeDurationShort } from "../../../humanizeDurationShort.js";
|
||||
import { getBaseUrl } from "../../../pluginUtils.js";
|
||||
import { DBDateFormat, MINUTES, renderUsername, resolveMember } from "../../../utils.js";
|
||||
import { DBDateFormat, MINUTES, renderUsername, resolveMember, toRelativeNativeTimestamp } from "../../../utils.js";
|
||||
import { IMuteWithDetails, mutesCmd } from "../types.js";
|
||||
|
||||
export const MutesCmd = mutesCmd({
|
||||
|
@ -131,15 +131,15 @@ export const MutesCmd = mutesCmd({
|
|||
|
||||
if (mute.expires_at) {
|
||||
const timeUntilExpiry = moment.utc().diff(moment.utc(mute.expires_at, DBDateFormat));
|
||||
const humanizedTime = humanizeDurationShort(timeUntilExpiry, { largest: 2, round: true });
|
||||
line += ` ⏰ Expires in ${humanizedTime}`;
|
||||
const humanizedTime = toRelativeNativeTimestamp(timeUntilExpiry, 0);
|
||||
line += ` ⏰ Expires ${humanizedTime}`;
|
||||
} else {
|
||||
line += ` ⏰ Indefinite`;
|
||||
}
|
||||
|
||||
const timeFromMute = moment.utc(mute.created_at, DBDateFormat).diff(moment.utc());
|
||||
const humanizedTimeFromMute = humanizeDurationShort(timeFromMute, { largest: 2, round: true });
|
||||
line += ` 🕒 Muted ${humanizedTimeFromMute} ago`;
|
||||
const timeFromMute = moment.utc(mute.created_at, DBDateFormat);
|
||||
const humanizedTimeFromMute = toRelativeNativeTimestamp(timeFromMute);
|
||||
line += ` 🕒 Muted ${humanizedTimeFromMute}`;
|
||||
|
||||
if (mute.banned) {
|
||||
line += ` 🔨 Banned`;
|
||||
|
@ -207,12 +207,17 @@ export const MutesCmd = mutesCmd({
|
|||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(buttons);
|
||||
await listMessage.edit({ components: [row] });
|
||||
|
||||
const collector = listMessage.createMessageComponentCollector({ time: stopCollectionDebounce });
|
||||
const collector = listMessage.createMessageComponentCollector({
|
||||
time: stopCollectionDebounce,
|
||||
});
|
||||
|
||||
collector.on("collect", async (interaction: MessageComponentInteraction) => {
|
||||
if (msg.author.id !== interaction.user.id) {
|
||||
interaction
|
||||
.reply({ content: `You are not permitted to use these buttons.`, ephemeral: true })
|
||||
.reply({
|
||||
content: `You are not permitted to use these buttons.`,
|
||||
ephemeral: true,
|
||||
})
|
||||
// tslint:disable-next-line no-console
|
||||
.catch((err) => console.trace(err.message));
|
||||
} else {
|
||||
|
@ -228,7 +233,10 @@ export const MutesCmd = mutesCmd({
|
|||
|
||||
stopCollectionFn = async () => {
|
||||
collector.stop();
|
||||
await listMessage.edit({ content: listMessage.content, components: [] });
|
||||
await listMessage.edit({
|
||||
content: listMessage.content,
|
||||
components: [],
|
||||
});
|
||||
};
|
||||
bumpCollectionTimeout();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import humanizeDuration from "humanize-duration";
|
||||
import moment from "moment-timezone";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { registerUpcomingReminder } from "../../../data/loops/upcomingRemindersLoop.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { convertDelayStringToMS, messageLink } from "../../../utils.js";
|
||||
import { convertDelayStringToMS, toNativeTimestamp, toRelativeNativeTimestamp, messageLink } from "../../../utils.js";
|
||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin.js";
|
||||
import { remindersCmd } from "../types.js";
|
||||
|
||||
|
@ -61,16 +60,9 @@ export const RemindCmd = remindersCmd({
|
|||
|
||||
registerUpcomingReminder(reminder);
|
||||
|
||||
const msUntilReminder = reminderTime.diff(now);
|
||||
const timeUntilReminder = humanizeDuration(msUntilReminder, { largest: 2, round: true });
|
||||
const prettyReminderTime = (await timeAndDate.inMemberTz(msg.author.id, reminderTime)).format(
|
||||
pluginData.getPlugin(TimeAndDatePlugin).getDateFormat("pretty_datetime"),
|
||||
);
|
||||
const timeUntilReminder = toRelativeNativeTimestamp(reminderTime, 0);
|
||||
const prettyReminderTime = toNativeTimestamp(reminderTime);
|
||||
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`I will remind you in **${timeUntilReminder}** at **${prettyReminderTime}**`,
|
||||
);
|
||||
sendSuccessMessage(pluginData, msg.channel, `I will remind you ${timeUntilReminder} at ${prettyReminderTime}`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
import humanizeDuration from "humanize-duration";
|
||||
import moment from "moment-timezone";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { createChunkedMessage, DBDateFormat, sorter } from "../../../utils.js";
|
||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin.js";
|
||||
import {
|
||||
createChunkedMessage,
|
||||
sorter,
|
||||
toNativeTimestamp,
|
||||
toRelativeNativeTimestamp,
|
||||
} from "../../../utils.js";
|
||||
import { remindersCmd } from "../types.js";
|
||||
|
||||
export const RemindersCmd = remindersCmd({
|
||||
trigger: "reminders",
|
||||
permission: "can_use",
|
||||
trigger: "reminders",
|
||||
permission: "can_use",
|
||||
|
||||
async run({ message: msg, pluginData }) {
|
||||
const reminders = await pluginData.state.reminders.getRemindersByUserId(msg.author.id);
|
||||
if (reminders.length === 0) {
|
||||
sendErrorMessage(pluginData, msg.channel, "No reminders");
|
||||
return;
|
||||
}
|
||||
async run({ message: msg, pluginData }) {
|
||||
const reminders = await pluginData.state.reminders.getRemindersByUserId(
|
||||
msg.author.id,
|
||||
);
|
||||
if (reminders.length === 0) {
|
||||
sendErrorMessage(pluginData, msg.channel, "No reminders");
|
||||
return;
|
||||
}
|
||||
|
||||
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
|
||||
reminders.sort(sorter("remind_at"));
|
||||
const longestNum = (reminders.length + 1).toString().length;
|
||||
const lines = Array.from(reminders.entries()).map(([i, reminder]) => {
|
||||
const num = i + 1;
|
||||
const paddedNum = num.toString().padStart(longestNum, " ");
|
||||
const target = moment.utc(reminder.remind_at);
|
||||
const relative = toRelativeNativeTimestamp(target, 0);
|
||||
const prettyRemindAt = toNativeTimestamp(target);
|
||||
return `\`${paddedNum}.\` ${prettyRemindAt} (${relative}) ${reminder.body}`;
|
||||
});
|
||||
|
||||
reminders.sort(sorter("remind_at"));
|
||||
const longestNum = (reminders.length + 1).toString().length;
|
||||
const lines = Array.from(reminders.entries()).map(([i, reminder]) => {
|
||||
const num = i + 1;
|
||||
const paddedNum = num.toString().padStart(longestNum, " ");
|
||||
const target = moment.utc(reminder.remind_at, "YYYY-MM-DD HH:mm:ss");
|
||||
const diff = target.diff(moment.utc());
|
||||
const result = humanizeDuration(diff, { largest: 2, round: true });
|
||||
const prettyRemindAt = timeAndDate
|
||||
.inGuildTz(moment.utc(reminder.remind_at, DBDateFormat))
|
||||
.format(timeAndDate.getDateFormat("pretty_datetime"));
|
||||
return `\`${paddedNum}.\` \`${prettyRemindAt} (${result})\` ${reminder.body}`;
|
||||
});
|
||||
|
||||
createChunkedMessage(msg.channel, lines.join("\n"));
|
||||
},
|
||||
createChunkedMessage(msg.channel, lines.join("\n"));
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import shuffle from "lodash/shuffle.js";
|
|||
import moment from "moment-timezone";
|
||||
import { rootDir } from "../../../paths.js";
|
||||
import { getCurrentUptime } from "../../../uptime.js";
|
||||
import { resolveMember, sorter } from "../../../utils.js";
|
||||
import { resolveMember, sorter, toRelativeNativeTimestamp } from "../../../utils.js";
|
||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin.js";
|
||||
import { utilityCmd } from "../types.js";
|
||||
|
||||
|
@ -31,23 +31,18 @@ export const AboutCmd = utilityCmd({
|
|||
let version;
|
||||
|
||||
if (lastCommit) {
|
||||
lastUpdate = timeAndDate
|
||||
.inGuildTz(moment.utc(lastCommit.committer.date, "X"))
|
||||
.format(pluginData.getPlugin(TimeAndDatePlugin).getDateFormat("pretty_datetime"));
|
||||
lastUpdate = toRelativeNativeTimestamp(moment.utc(lastCommit.committer.data, "X"), 0);
|
||||
version = lastCommit.shortHash;
|
||||
} else {
|
||||
lastUpdate = "?";
|
||||
version = "?";
|
||||
}
|
||||
|
||||
const lastReload = humanizeDuration(Date.now() - pluginData.state.lastReload, {
|
||||
largest: 2,
|
||||
round: true,
|
||||
});
|
||||
const lastReload = toRelativeNativeTimestamp(pluginData.state.lastReload, 0);
|
||||
|
||||
const basicInfoRows = [
|
||||
["Uptime", prettyUptime],
|
||||
["Last config reload", `${lastReload} ago`],
|
||||
["Last config reload", lastReload],
|
||||
["Last bot update", lastUpdate],
|
||||
["Version", version],
|
||||
["API latency", `${pluginData.client.ws.ping}ms`],
|
||||
|
@ -101,7 +96,9 @@ export const AboutCmd = utilityCmd({
|
|||
|
||||
// Use the bot avatar as the embed image
|
||||
if (pluginData.client.user!.displayAvatarURL()) {
|
||||
aboutEmbed.thumbnail = { url: pluginData.client.user!.displayAvatarURL()! };
|
||||
aboutEmbed.thumbnail = {
|
||||
url: pluginData.client.user!.displayAvatarURL()!,
|
||||
};
|
||||
}
|
||||
|
||||
msg.channel.send({ embeds: [aboutEmbed] });
|
||||
|
|
|
@ -446,6 +446,14 @@ export function convertMSToDelayString(ms: number): string {
|
|||
return result;
|
||||
}
|
||||
|
||||
export function toNativeTimestamp(time, flag = "f") {
|
||||
return `<t:${Math.round(time / 1000)}:${flag}>`;
|
||||
}
|
||||
|
||||
export function toRelativeNativeTimestamp(ms, offset = Date.now()) {
|
||||
return `<t:${Math.round((offset + ms) / 1000)}:R>`;
|
||||
}
|
||||
|
||||
export function successMessage(str: string, emoji = "<:zep_check:906897402101891093>") {
|
||||
return emoji ? `${emoji} ${str}` : str;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue