mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-23 09:35:02 +00:00
Merge https://github.com/Dragory/ZeppelinBot into pr/metal0/274
This commit is contained in:
commit
05256c1985
41 changed files with 405 additions and 119 deletions
14
backend/package-lock.json
generated
14
backend/package-lock.json
generated
|
@ -24,7 +24,7 @@
|
||||||
"humanize-duration": "^3.15.0",
|
"humanize-duration": "^3.15.0",
|
||||||
"io-ts": "^2.0.0",
|
"io-ts": "^2.0.0",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"knub": "^30.0.0-beta.42",
|
"knub": "^30.0.0-beta.45",
|
||||||
"knub-command-manager": "^9.1.0",
|
"knub-command-manager": "^9.1.0",
|
||||||
"last-commit-log": "^2.1.0",
|
"last-commit-log": "^2.1.0",
|
||||||
"lodash.chunk": "^4.2.0",
|
"lodash.chunk": "^4.2.0",
|
||||||
|
@ -3043,9 +3043,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/knub": {
|
"node_modules/knub": {
|
||||||
"version": "30.0.0-beta.42",
|
"version": "30.0.0-beta.45",
|
||||||
"resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.42.tgz",
|
"resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.45.tgz",
|
||||||
"integrity": "sha512-y7nqQh1bzQniYwEftdv6S8Jp2qBvT5a7vn+3JeA0s0ADXobI+/rRVznpq8o0x2m0+E+EeKxo1Ch8F8Hy+VMX6w==",
|
"integrity": "sha512-r1jtHBYthOn8zjgyILh418/Qnw8f/cUMzz5aky7+T5HLFV0BAiBzeg5TOb0UFMkn8ewIPSy8GTG1x/CIAv3s8Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord-api-types": "^0.22.0",
|
"discord-api-types": "^0.22.0",
|
||||||
"discord.js": "^13.0.1",
|
"discord.js": "^13.0.1",
|
||||||
|
@ -8290,9 +8290,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"knub": {
|
"knub": {
|
||||||
"version": "30.0.0-beta.42",
|
"version": "30.0.0-beta.45",
|
||||||
"resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.42.tgz",
|
"resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.45.tgz",
|
||||||
"integrity": "sha512-y7nqQh1bzQniYwEftdv6S8Jp2qBvT5a7vn+3JeA0s0ADXobI+/rRVznpq8o0x2m0+E+EeKxo1Ch8F8Hy+VMX6w==",
|
"integrity": "sha512-r1jtHBYthOn8zjgyILh418/Qnw8f/cUMzz5aky7+T5HLFV0BAiBzeg5TOb0UFMkn8ewIPSy8GTG1x/CIAv3s8Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"discord-api-types": "^0.22.0",
|
"discord-api-types": "^0.22.0",
|
||||||
"discord.js": "^13.0.1",
|
"discord.js": "^13.0.1",
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
"humanize-duration": "^3.15.0",
|
"humanize-duration": "^3.15.0",
|
||||||
"io-ts": "^2.0.0",
|
"io-ts": "^2.0.0",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"knub": "^30.0.0-beta.42",
|
"knub": "^30.0.0-beta.45",
|
||||||
"knub-command-manager": "^9.1.0",
|
"knub-command-manager": "^9.1.0",
|
||||||
"last-commit-log": "^2.1.0",
|
"last-commit-log": "^2.1.0",
|
||||||
"lodash.chunk": "^4.2.0",
|
"lodash.chunk": "^4.2.0",
|
||||||
|
|
|
@ -226,9 +226,6 @@ export class GuildSavedMessages extends BaseGuildRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createFromMsg(msg: Message, overrides = {}) {
|
async createFromMsg(msg: Message, overrides = {}) {
|
||||||
const existingSavedMsg = await this.find(msg.id);
|
|
||||||
if (existingSavedMsg) return;
|
|
||||||
|
|
||||||
// FIXME: Hotfix
|
// FIXME: Hotfix
|
||||||
if (!msg.channel) {
|
if (!msg.channel) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -300,6 +300,22 @@ connect().then(async () => {
|
||||||
startUptimeCounter();
|
startUptimeCounter();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const debugGuilds = ["877581055920603238", "348468156597010432", "134286179121102848"];
|
||||||
|
bot.on("guildLoaded", guildId => {
|
||||||
|
if (!debugGuilds.includes(guildId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[!! DEBUG !!] LOADED GUILD ${guildId}`);
|
||||||
|
});
|
||||||
|
bot.on("guildUnloaded", guildId => {
|
||||||
|
if (!debugGuilds.includes(guildId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[!! DEBUG !!] UNLOADED GUILD ${guildId}`);
|
||||||
|
});
|
||||||
|
|
||||||
bot.initialize();
|
bot.initialize();
|
||||||
logger.info("Bot Initialized");
|
logger.info("Bot Initialized");
|
||||||
logger.info("Logging in...");
|
logger.info("Logging in...");
|
||||||
|
|
|
@ -232,7 +232,7 @@ export function getBaseUrl(pluginData: AnyPluginData<any>) {
|
||||||
|
|
||||||
export function isOwner(pluginData: AnyPluginData<any>, userId: string) {
|
export function isOwner(pluginData: AnyPluginData<any>, userId: string) {
|
||||||
const knub = pluginData.getKnubInstance() as TZeppelinKnub;
|
const knub = pluginData.getKnubInstance() as TZeppelinKnub;
|
||||||
const owners = knub.getGlobalConfig().owners;
|
const owners = knub.getGlobalConfig()?.owners;
|
||||||
if (!owners) {
|
if (!owners) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,21 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = options => {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (triggerObj[triggerName].match_mime_type) {
|
||||||
|
const white = triggerObj[triggerName].match_mime_type.whitelist_enabled;
|
||||||
|
const black = triggerObj[triggerName].match_mime_type.blacklist_enabled;
|
||||||
|
|
||||||
|
if (white && black) {
|
||||||
|
throw new StrictValidationError([
|
||||||
|
`Cannot have both blacklist and whitelist enabled at rule <${rule.name}/match_mime_type>`,
|
||||||
|
]);
|
||||||
|
} else if (!white && !black) {
|
||||||
|
throw new StrictValidationError([
|
||||||
|
`Must have either blacklist or whitelist enabled at rule <${rule.name}/match_mime_type>`,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
backend/src/plugins/Automod/actions/archiveThread.ts
Normal file
20
backend/src/plugins/Automod/actions/archiveThread.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { ThreadChannel } from "discord.js";
|
||||||
|
import * as t from "io-ts";
|
||||||
|
import { noop } from "../../../utils";
|
||||||
|
import { automodAction } from "../helpers";
|
||||||
|
|
||||||
|
export const ArchiveThreadAction = automodAction({
|
||||||
|
configType: t.type({}),
|
||||||
|
defaultConfig: {},
|
||||||
|
|
||||||
|
async apply({ pluginData, contexts }) {
|
||||||
|
const threads = contexts
|
||||||
|
.filter(c => c.message?.channel_id)
|
||||||
|
.map(c => pluginData.guild.channels.cache.get(c.message!.channel_id))
|
||||||
|
.filter((c): c is ThreadChannel => c?.isThread() ?? false);
|
||||||
|
|
||||||
|
for (const thread of threads) {
|
||||||
|
await thread.setArchived().catch(noop);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
|
@ -3,6 +3,7 @@ import { AutomodActionBlueprint } from "../helpers";
|
||||||
import { AddRolesAction } from "./addRoles";
|
import { AddRolesAction } from "./addRoles";
|
||||||
import { AddToCounterAction } from "./addToCounter";
|
import { AddToCounterAction } from "./addToCounter";
|
||||||
import { AlertAction } from "./alert";
|
import { AlertAction } from "./alert";
|
||||||
|
import { ArchiveThreadAction } from "./archiveThread";
|
||||||
import { BanAction } from "./ban";
|
import { BanAction } from "./ban";
|
||||||
import { ChangeNicknameAction } from "./changeNickname";
|
import { ChangeNicknameAction } from "./changeNickname";
|
||||||
import { CleanAction } from "./clean";
|
import { CleanAction } from "./clean";
|
||||||
|
@ -34,6 +35,7 @@ export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
|
||||||
set_counter: SetCounterAction,
|
set_counter: SetCounterAction,
|
||||||
set_slowmode: SetSlowmodeAction,
|
set_slowmode: SetSlowmodeAction,
|
||||||
start_thread: StartThreadAction,
|
start_thread: StartThreadAction,
|
||||||
|
archive_thread: ArchiveThreadAction,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AvailableActions = t.type({
|
export const AvailableActions = t.type({
|
||||||
|
@ -53,4 +55,5 @@ export const AvailableActions = t.type({
|
||||||
set_counter: SetCounterAction.configType,
|
set_counter: SetCounterAction.configType,
|
||||||
set_slowmode: SetSlowmodeAction.configType,
|
set_slowmode: SetSlowmodeAction.configType,
|
||||||
start_thread: StartThreadAction.configType,
|
start_thread: StartThreadAction.configType,
|
||||||
|
archive_thread: ArchiveThreadAction.configType,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { MessageOptions, Permissions, Snowflake, TextChannel, ThreadChannel, User } from "discord.js";
|
import { MessageOptions, Permissions, Snowflake, TextChannel, ThreadChannel, User } from "discord.js";
|
||||||
import * as t from "io-ts";
|
import * as t from "io-ts";
|
||||||
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
||||||
import { LogType } from "../../../data/LogType";
|
|
||||||
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter";
|
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter";
|
||||||
import {
|
import {
|
||||||
convertDelayStringToMS,
|
convertDelayStringToMS,
|
||||||
|
@ -25,6 +24,7 @@ export const ReplyAction = automodAction({
|
||||||
t.type({
|
t.type({
|
||||||
text: tMessageContent,
|
text: tMessageContent,
|
||||||
auto_delete: tNullable(t.union([tDelayString, t.number])),
|
auto_delete: tNullable(t.union([tDelayString, t.number])),
|
||||||
|
use_inline_reply: tNullable(t.boolean),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ export const ReplyAction = automodAction({
|
||||||
const users = unique(Array.from(new Set(_contexts.map(c => c.user).filter(Boolean)))) as User[];
|
const users = unique(Array.from(new Set(_contexts.map(c => c.user).filter(Boolean)))) as User[];
|
||||||
const user = users[0];
|
const user = users[0];
|
||||||
|
|
||||||
const renderReplyText = async str =>
|
const renderReplyText = async (str: string) =>
|
||||||
renderTemplate(
|
renderTemplate(
|
||||||
str,
|
str,
|
||||||
new TemplateSafeValueContainer({
|
new TemplateSafeValueContainer({
|
||||||
|
@ -94,16 +94,26 @@ export const ReplyAction = automodAction({
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageContent = validateAndParseMessageContent(formatted);
|
const messageContent = validateAndParseMessageContent(formatted);
|
||||||
const replyMsg = await channel.send({
|
|
||||||
|
const messageOpts: MessageOptions = {
|
||||||
...messageContent,
|
...messageContent,
|
||||||
allowedMentions: {
|
allowedMentions: {
|
||||||
users: [user.id],
|
users: [user.id],
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (typeof actionConfig !== "string" && actionConfig.use_inline_reply) {
|
||||||
|
messageOpts.reply = {
|
||||||
|
failIfNotExists: false,
|
||||||
|
messageReference: _contexts[0].message!.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const replyMsg = await channel.send(messageOpts);
|
||||||
|
|
||||||
if (typeof actionConfig === "object" && actionConfig.auto_delete) {
|
if (typeof actionConfig === "object" && actionConfig.auto_delete) {
|
||||||
const delay = convertDelayStringToMS(String(actionConfig.auto_delete))!;
|
const delay = convertDelayStringToMS(String(actionConfig.auto_delete))!;
|
||||||
setTimeout(() => replyMsg.delete().catch(noop), delay);
|
setTimeout(() => !replyMsg.deleted && replyMsg.delete().catch(noop), delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ export const RunAutomodOnMemberUpdate = typedGuildEventListener<AutomodPluginTyp
|
||||||
|
|
||||||
if (isEqual(oldRoles, newRoles)) return;
|
if (isEqual(oldRoles, newRoles)) return;
|
||||||
|
|
||||||
const addedRoles = diff(oldRoles, newRoles);
|
const addedRoles = diff(newRoles, oldRoles);
|
||||||
const removedRoles = diff(newRoles, oldRoles);
|
const removedRoles = diff(oldRoles, newRoles);
|
||||||
|
|
||||||
if (addedRoles.length || removedRoles.length) {
|
if (addedRoles.length || removedRoles.length) {
|
||||||
const context: AutomodContext = {
|
const context: AutomodContext = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Constants } from "discord.js";
|
import { Constants, MessageEmbed } from "discord.js";
|
||||||
import { GuildPluginData } from "knub";
|
import { GuildPluginData } from "knub";
|
||||||
import { SavedMessage } from "../../../data/entities/SavedMessage";
|
import { SavedMessage } from "../../../data/entities/SavedMessage";
|
||||||
import { resolveMember } from "../../../utils";
|
import { resolveMember } from "../../../utils";
|
||||||
|
@ -32,9 +32,9 @@ export async function* matchMultipleTextTypesOnMessage(
|
||||||
yield ["message", msg.data.content];
|
yield ["message", msg.data.content];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trigger.match_embeds && msg.data.embeds && msg.data.embeds.length) {
|
if (trigger.match_embeds && msg.data.embeds?.length) {
|
||||||
const copiedEmbed = JSON.parse(JSON.stringify(msg.data.embeds[0]));
|
const copiedEmbed: MessageEmbed = JSON.parse(JSON.stringify(msg.data.embeds[0]));
|
||||||
if (copiedEmbed.type === "video") {
|
if (copiedEmbed.video) {
|
||||||
copiedEmbed.description = ""; // The description is not rendered, hence it doesn't need to be matched
|
copiedEmbed.description = ""; // The description is not rendered, hence it doesn't need to be matched
|
||||||
}
|
}
|
||||||
yield ["embed", JSON.stringify(copiedEmbed)];
|
yield ["embed", JSON.stringify(copiedEmbed)];
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { KickTrigger } from "./kick";
|
||||||
import { LineSpamTrigger } from "./lineSpam";
|
import { LineSpamTrigger } from "./lineSpam";
|
||||||
import { LinkSpamTrigger } from "./linkSpam";
|
import { LinkSpamTrigger } from "./linkSpam";
|
||||||
import { MatchAttachmentTypeTrigger } from "./matchAttachmentType";
|
import { MatchAttachmentTypeTrigger } from "./matchAttachmentType";
|
||||||
|
import { MatchMimeTypeTrigger } from "./matchMimeType";
|
||||||
import { MatchInvitesTrigger } from "./matchInvites";
|
import { MatchInvitesTrigger } from "./matchInvites";
|
||||||
import { MatchLinksTrigger } from "./matchLinks";
|
import { MatchLinksTrigger } from "./matchLinks";
|
||||||
import { MatchRegexTrigger } from "./matchRegex";
|
import { MatchRegexTrigger } from "./matchRegex";
|
||||||
|
@ -37,6 +38,7 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
|
||||||
match_invites: MatchInvitesTrigger,
|
match_invites: MatchInvitesTrigger,
|
||||||
match_links: MatchLinksTrigger,
|
match_links: MatchLinksTrigger,
|
||||||
match_attachment_type: MatchAttachmentTypeTrigger,
|
match_attachment_type: MatchAttachmentTypeTrigger,
|
||||||
|
match_mime_type: MatchMimeTypeTrigger,
|
||||||
member_join: MemberJoinTrigger,
|
member_join: MemberJoinTrigger,
|
||||||
role_added: RoleAddedTrigger,
|
role_added: RoleAddedTrigger,
|
||||||
role_removed: RoleRemovedTrigger,
|
role_removed: RoleRemovedTrigger,
|
||||||
|
@ -72,6 +74,7 @@ export const AvailableTriggers = t.type({
|
||||||
match_invites: MatchInvitesTrigger.configType,
|
match_invites: MatchInvitesTrigger.configType,
|
||||||
match_links: MatchLinksTrigger.configType,
|
match_links: MatchLinksTrigger.configType,
|
||||||
match_attachment_type: MatchAttachmentTypeTrigger.configType,
|
match_attachment_type: MatchAttachmentTypeTrigger.configType,
|
||||||
|
match_mime_type: MatchMimeTypeTrigger.configType,
|
||||||
member_join: MemberJoinTrigger.configType,
|
member_join: MemberJoinTrigger.configType,
|
||||||
member_leave: MemberLeaveTrigger.configType,
|
member_leave: MemberLeaveTrigger.configType,
|
||||||
role_added: RoleAddedTrigger.configType,
|
role_added: RoleAddedTrigger.configType,
|
||||||
|
|
79
backend/src/plugins/Automod/triggers/matchMimeType.ts
Normal file
79
backend/src/plugins/Automod/triggers/matchMimeType.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { automodTrigger } from "../helpers";
|
||||||
|
import * as t from "io-ts";
|
||||||
|
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
|
||||||
|
import { GuildChannel, Util } from "discord.js";
|
||||||
|
|
||||||
|
interface MatchResultType {
|
||||||
|
matchedType: string;
|
||||||
|
mode: "blacklist" | "whitelist";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MatchMimeTypeTrigger = automodTrigger<MatchResultType>()({
|
||||||
|
configType: t.type({
|
||||||
|
mime_type_blacklist: t.array(t.string),
|
||||||
|
blacklist_enabled: t.boolean,
|
||||||
|
mime_type_whitelist: t.array(t.string),
|
||||||
|
whitelist_enabled: t.boolean,
|
||||||
|
}),
|
||||||
|
|
||||||
|
defaultConfig: {
|
||||||
|
mime_type_blacklist: [],
|
||||||
|
blacklist_enabled: false,
|
||||||
|
mime_type_whitelist: [],
|
||||||
|
whitelist_enabled: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
async match({ context, triggerConfig: trigger }) {
|
||||||
|
if (!context.message) return;
|
||||||
|
|
||||||
|
const { attachments } = context.message.data;
|
||||||
|
if (!attachments) return null;
|
||||||
|
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
const { contentType } = attachment;
|
||||||
|
|
||||||
|
const blacklist = trigger.blacklist_enabled
|
||||||
|
? (trigger.mime_type_blacklist ?? []).map(_t => _t.toLowerCase())
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (contentType && blacklist?.includes(contentType)) {
|
||||||
|
return {
|
||||||
|
extra: {
|
||||||
|
matchedType: contentType,
|
||||||
|
mode: "blacklist",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const whitelist = trigger.whitelist_enabled
|
||||||
|
? (trigger.mime_type_whitelist ?? []).map(_t => _t.toLowerCase())
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (whitelist && (!contentType || !whitelist.includes(contentType))) {
|
||||||
|
return {
|
||||||
|
extra: {
|
||||||
|
matchedType: contentType || "<unknown>",
|
||||||
|
mode: "whitelist",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
||||||
|
const { message } = contexts[0];
|
||||||
|
const channel = pluginData.guild.channels.resolve(message!.channel_id);
|
||||||
|
const prettyChannel = verboseChannelMention(channel as GuildChannel);
|
||||||
|
const { matchedType, mode } = matchResult.extra;
|
||||||
|
|
||||||
|
return (
|
||||||
|
asSingleLine(`
|
||||||
|
Matched MIME type \`${Util.escapeInlineCode(matchedType)}\`
|
||||||
|
(${mode === "blacklist" ? "blacklisted" : "not in whitelist"})
|
||||||
|
in message (\`${message!.id}\`) in ${prettyChannel}
|
||||||
|
`) + messageSummary(message!)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -18,11 +18,13 @@ import { ReloadServerCmd } from "./commands/ReloadServerCmd";
|
||||||
import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd";
|
import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd";
|
||||||
import { ServersCmd } from "./commands/ServersCmd";
|
import { ServersCmd } from "./commands/ServersCmd";
|
||||||
import { BotControlPluginType, ConfigSchema } from "./types";
|
import { BotControlPluginType, ConfigSchema } from "./types";
|
||||||
|
import { PerformanceCmd } from "./commands/PerformanceCmd";
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
config: {
|
config: {
|
||||||
can_use: false,
|
can_use: false,
|
||||||
can_eligible: false,
|
can_eligible: false,
|
||||||
|
can_performance: false,
|
||||||
update_cmd: null,
|
update_cmd: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -45,6 +47,7 @@ export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()({
|
||||||
ListDashboardUsersCmd,
|
ListDashboardUsersCmd,
|
||||||
ListDashboardPermsCmd,
|
ListDashboardPermsCmd,
|
||||||
EligibleCmd,
|
EligibleCmd,
|
||||||
|
PerformanceCmd,
|
||||||
],
|
],
|
||||||
|
|
||||||
async afterLoad(pluginData) {
|
async afterLoad(pluginData) {
|
||||||
|
|
23
backend/src/plugins/BotControl/commands/PerformanceCmd.ts
Normal file
23
backend/src/plugins/BotControl/commands/PerformanceCmd.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { TextChannel } from "discord.js";
|
||||||
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
|
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
||||||
|
import { createChunkedMessage, formatNumber, resolveInvite, sorter, verboseUserMention } from "../../../utils";
|
||||||
|
import { botControlCmd } from "../types";
|
||||||
|
|
||||||
|
export const PerformanceCmd = botControlCmd({
|
||||||
|
trigger: ["performance"],
|
||||||
|
permission: "can_performance",
|
||||||
|
|
||||||
|
signature: {},
|
||||||
|
|
||||||
|
async run({ pluginData, message: msg, args }) {
|
||||||
|
const stats = pluginData.getKnubInstance().getPluginPerformanceStats();
|
||||||
|
const averageLoadTimeEntries = Object.entries(stats.averageLoadTimes);
|
||||||
|
averageLoadTimeEntries.sort(sorter(v => v[1].time, "DESC"));
|
||||||
|
const lines = averageLoadTimeEntries.map(
|
||||||
|
([pluginName, { time }]) => `${pluginName}: **${formatNumber(Math.round(time))}ms**`,
|
||||||
|
);
|
||||||
|
const fullStats = `Average plugin load times:\n\n${lines.join("\n")}`;
|
||||||
|
createChunkedMessage(msg.channel as TextChannel, fullStats);
|
||||||
|
},
|
||||||
|
});
|
|
@ -9,6 +9,7 @@ import { tNullable } from "../../utils";
|
||||||
export const ConfigSchema = t.type({
|
export const ConfigSchema = t.type({
|
||||||
can_use: t.boolean,
|
can_use: t.boolean,
|
||||||
can_eligible: t.boolean,
|
can_eligible: t.boolean,
|
||||||
|
can_performance: t.boolean,
|
||||||
update_cmd: tNullable(t.string),
|
update_cmd: tNullable(t.string),
|
||||||
});
|
});
|
||||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||||
|
|
|
@ -51,7 +51,7 @@ export const CustomEventsPlugin = zeppelinGuildPlugin<CustomEventsPluginType>()(
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = createTypedTemplateSafeValueContainer({
|
const values = createTypedTemplateSafeValueContainer({
|
||||||
...args,
|
...safeArgs,
|
||||||
msg: messageToTemplateSafeMessage(message),
|
msg: messageToTemplateSafeMessage(message),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -28,9 +28,11 @@ export async function addRoleAction(
|
||||||
if (event.trigger.type === "command" && !canActOn(pluginData, eventData.msg.member, target)) {
|
if (event.trigger.type === "command" && !canActOn(pluginData, eventData.msg.member, target)) {
|
||||||
throw new ActionError("Missing permissions");
|
throw new ActionError("Missing permissions");
|
||||||
}
|
}
|
||||||
|
const rolesToAdd = (Array.isArray(action.role) ? action.role : [action.role]).filter(
|
||||||
const rolesToAdd = Array.isArray(action.role) ? action.role : [action.role];
|
id => !target.roles.cache.has(id),
|
||||||
await target.edit({
|
);
|
||||||
roles: Array.from(new Set([...target.roles.cache.values(), ...rolesToAdd])) as Snowflake[],
|
if (rolesToAdd.length === 0) {
|
||||||
});
|
throw new ActionError("Target already has the role(s) specified");
|
||||||
|
}
|
||||||
|
await target.roles.add(rolesToAdd);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Permissions, Snowflake, TextChannel } from "discord.js";
|
import { Permissions, Snowflake, TextChannel, PermissionString } from "discord.js";
|
||||||
import * as t from "io-ts";
|
import * as t from "io-ts";
|
||||||
import { GuildPluginData } from "knub";
|
import { GuildPluginData } from "knub";
|
||||||
import { ActionError } from "../ActionError";
|
import { ActionError } from "../ActionError";
|
||||||
|
@ -32,10 +32,17 @@ export async function setChannelPermissionOverridesAction(
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const override of action.overrides) {
|
for (const override of action.overrides) {
|
||||||
channel.permissionOverwrites.create(
|
const allow = new Permissions(BigInt(override.allow)).serialize();
|
||||||
override.id as Snowflake,
|
const deny = new Permissions(BigInt(override.deny)).serialize();
|
||||||
new Permissions(BigInt(override.allow)).remove(BigInt(override.deny)).serialize(),
|
const perms: Partial<Record<PermissionString, boolean | null>> = {};
|
||||||
);
|
for (const key in allow) {
|
||||||
|
if (allow[key]) {
|
||||||
|
perms[key] = true;
|
||||||
|
} else if (deny[key]) {
|
||||||
|
perms[key] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel.permissionOverwrites.create(override.id as Snowflake, perms);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
await channel.permissionOverwrites overwritePermissions(
|
await channel.permissionOverwrites overwritePermissions(
|
||||||
|
|
|
@ -17,6 +17,8 @@ export const LogsGuildMemberUpdateEvt = logsEvt({
|
||||||
const pluginData = meta.pluginData;
|
const pluginData = meta.pluginData;
|
||||||
const oldMember = meta.args.oldMember;
|
const oldMember = meta.args.oldMember;
|
||||||
const member = meta.args.newMember;
|
const member = meta.args.newMember;
|
||||||
|
const oldRoles = [...oldMember.roles.cache.keys()];
|
||||||
|
const currentRoles = [...member.roles.cache.keys()];
|
||||||
|
|
||||||
if (!oldMember || oldMember.partial) {
|
if (!oldMember || oldMember.partial) {
|
||||||
return;
|
return;
|
||||||
|
@ -30,9 +32,9 @@ export const LogsGuildMemberUpdateEvt = logsEvt({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEqual(oldMember.roles, member.roles)) {
|
if (!isEqual(oldRoles, currentRoles)) {
|
||||||
const addedRoles = diff([...member.roles.cache.keys()], [...oldMember.roles.cache.keys()]);
|
const addedRoles = diff(currentRoles, oldRoles);
|
||||||
const removedRoles = diff([...oldMember.roles.cache.keys()], [...member.roles.cache.keys()]);
|
const removedRoles = diff(oldRoles, currentRoles);
|
||||||
let skip = false;
|
let skip = false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -21,6 +21,9 @@ const defaultOptions: PluginOptions<MessageSaverPluginType> = {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let debugId = 0;
|
||||||
|
const debugGuilds = ["877581055920603238", "348468156597010432", "134286179121102848"];
|
||||||
|
|
||||||
export const MessageSaverPlugin = zeppelinGuildPlugin<MessageSaverPluginType>()({
|
export const MessageSaverPlugin = zeppelinGuildPlugin<MessageSaverPluginType>()({
|
||||||
name: "message_saver",
|
name: "message_saver",
|
||||||
showInDocs: false,
|
showInDocs: false,
|
||||||
|
@ -45,6 +48,18 @@ export const MessageSaverPlugin = zeppelinGuildPlugin<MessageSaverPluginType>()(
|
||||||
beforeLoad(pluginData) {
|
beforeLoad(pluginData) {
|
||||||
const { state, guild } = pluginData;
|
const { state, guild } = pluginData;
|
||||||
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
|
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
|
||||||
state.queue = new Queue();
|
state.debugId = ++debugId;
|
||||||
|
|
||||||
|
if (debugGuilds.includes(pluginData.guild.id)) {
|
||||||
|
console.log(`[!! DEBUG !!] MessageSaverPlugin::beforeLoad (${state.debugId}): ${pluginData.guild.id}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeUnload(pluginData) {
|
||||||
|
if (debugGuilds.includes(pluginData.guild.id)) {
|
||||||
|
console.log(
|
||||||
|
`[!! DEBUG !!] MessageSaverPlugin::beforeUnload (${pluginData.state.debugId}): ${pluginData.guild.id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
import { Constants, Message, MessageType, Snowflake } from "discord.js";
|
import { Constants, Message, MessageType, Snowflake } from "discord.js";
|
||||||
import { messageSaverEvt } from "../types";
|
import { messageSaverEvt } from "../types";
|
||||||
import { SECONDS } from "../../../utils";
|
import { SECONDS } from "../../../utils";
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
|
||||||
const recentlyCreatedMessages: Snowflake[] = [];
|
const recentlyCreatedMessages: Map<Snowflake, [debugId: number, timestamp: number, guildId: string]> = new Map();
|
||||||
const recentlyCreatedMessagesToKeep = 100;
|
const recentlyCreatedMessagesToKeep = 100;
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const toDelete = recentlyCreatedMessages.length - recentlyCreatedMessagesToKeep;
|
let toDelete = recentlyCreatedMessages.size - recentlyCreatedMessagesToKeep;
|
||||||
if (toDelete > 0) {
|
for (const key of recentlyCreatedMessages.keys()) {
|
||||||
recentlyCreatedMessages.splice(0, toDelete);
|
if (toDelete === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
recentlyCreatedMessages.delete(key);
|
||||||
|
|
||||||
|
toDelete--;
|
||||||
}
|
}
|
||||||
}, 60 * SECONDS);
|
}, 60 * SECONDS);
|
||||||
|
|
||||||
|
@ -29,17 +36,25 @@ export const MessageCreateEvt = messageSaverEvt({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.pluginData.state.queue.add(async () => {
|
// Don't save the bot's own messages
|
||||||
if (recentlyCreatedMessages.includes(meta.args.message.id)) {
|
if (meta.args.message.author.id === meta.pluginData.client.user?.id) {
|
||||||
console.warn(
|
|
||||||
`Tried to save duplicate message from messageCreate event: ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
recentlyCreatedMessages.push(meta.args.message.id);
|
|
||||||
|
// FIXME: Remove debug code
|
||||||
|
if (recentlyCreatedMessages.has(meta.args.message.id)) {
|
||||||
|
const ourDebugId = meta.pluginData.state.debugId;
|
||||||
|
const oldDebugId = recentlyCreatedMessages.get(meta.args.message.id)![0];
|
||||||
|
const oldGuildId = recentlyCreatedMessages.get(meta.args.message.id)![2];
|
||||||
|
const context = `${ourDebugId} : ${oldDebugId} / ${meta.pluginData.guild.id} : ${oldGuildId} : ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`;
|
||||||
|
const timestamp = moment(recentlyCreatedMessages.get(meta.args.message.id)![1]).format("HH:mm:ss.SSS");
|
||||||
|
// tslint:disable-next-line:no-console
|
||||||
|
console.warn(`Tried to save duplicate message from messageCreate event: ${context} / saved at: ${timestamp}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recentlyCreatedMessages.set(meta.args.message.id, [meta.pluginData.state.debugId, Date.now(), meta.pluginData.guild.id]);
|
||||||
|
|
||||||
await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message);
|
await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,9 +72,7 @@ export const MessageUpdateEvt = messageSaverEvt({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.pluginData.state.queue.add(async () => {
|
|
||||||
await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.newMessage as Message);
|
await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.newMessage as Message);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -74,9 +87,7 @@ export const MessageDeleteEvt = messageSaverEvt({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.pluginData.state.queue.add(async () => {
|
|
||||||
await meta.pluginData.state.savedMessages.markAsDeleted(msg.id);
|
await meta.pluginData.state.savedMessages.markAsDeleted(msg.id);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -87,8 +98,6 @@ export const MessageDeleteBulkEvt = messageSaverEvt({
|
||||||
|
|
||||||
async listener(meta) {
|
async listener(meta) {
|
||||||
const ids = meta.args.messages.map(m => m.id);
|
const ids = meta.args.messages.map(m => m.id);
|
||||||
meta.pluginData.state.queue.add(async () => {
|
|
||||||
await meta.pluginData.state.savedMessages.markBulkAsDeleted(ids);
|
await meta.pluginData.state.savedMessages.markBulkAsDeleted(ids);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ export interface MessageSaverPluginType extends BasePluginType {
|
||||||
config: TConfigSchema;
|
config: TConfigSchema;
|
||||||
state: {
|
state: {
|
||||||
savedMessages: GuildSavedMessages;
|
savedMessages: GuildSavedMessages;
|
||||||
queue: Queue;
|
debugId: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const StoreDataEvt = persistEvt({
|
||||||
|
|
||||||
const persistedRoles = config.persisted_roles;
|
const persistedRoles = config.persisted_roles;
|
||||||
if (persistedRoles.length && member.roles) {
|
if (persistedRoles.length && member.roles) {
|
||||||
const rolesToPersist = intersection(persistedRoles, member.roles);
|
const rolesToPersist = intersection(persistedRoles, [...member.roles.cache.keys()]);
|
||||||
if (rolesToPersist.length) {
|
if (rolesToPersist.length) {
|
||||||
persist = true;
|
persist = true;
|
||||||
persistData.roles = rolesToPersist;
|
persistData.roles = rolesToPersist;
|
||||||
|
|
|
@ -25,7 +25,7 @@ export async function applyReactionRoleReactionsToMessage(
|
||||||
|
|
||||||
let targetMessage;
|
let targetMessage;
|
||||||
try {
|
try {
|
||||||
targetMessage = channel.messages.fetch(messageId, { force: true });
|
targetMessage = await channel.messages.fetch(messageId, { force: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isDiscordAPIError(e)) {
|
if (isDiscordAPIError(e)) {
|
||||||
if (e.code === 10008) {
|
if (e.code === 10008) {
|
||||||
|
|
|
@ -63,7 +63,7 @@ export const RemoveRoleCmd = rolesCmd({
|
||||||
sendSuccessMessage(
|
sendSuccessMessage(
|
||||||
pluginData,
|
pluginData,
|
||||||
msg.channel,
|
msg.channel,
|
||||||
`Removed role **${role.name}** removed from ${verboseUserMention(args.member.user)}!`,
|
`Removed role **${role.name}** from ${verboseUserMention(args.member.user)}!`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,9 +9,6 @@ export const StarboardReactionAddEvt = starboardEvt({
|
||||||
event: "messageReactionAdd",
|
event: "messageReactionAdd",
|
||||||
|
|
||||||
async listener(meta) {
|
async listener(meta) {
|
||||||
// FIXME: Temporarily disabled
|
|
||||||
return;
|
|
||||||
|
|
||||||
const pluginData = meta.pluginData;
|
const pluginData = meta.pluginData;
|
||||||
|
|
||||||
let msg = meta.args.reaction.message as Message;
|
let msg = meta.args.reaction.message as Message;
|
||||||
|
|
|
@ -5,9 +5,6 @@ export const StarboardReactionRemoveEvt = starboardEvt({
|
||||||
event: "messageReactionRemove",
|
event: "messageReactionRemove",
|
||||||
|
|
||||||
async listener(meta) {
|
async listener(meta) {
|
||||||
// FIXME: Temporarily disabled
|
|
||||||
return;
|
|
||||||
|
|
||||||
const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock());
|
const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock());
|
||||||
await meta.pluginData.state.starboardReactions.deleteStarboardReaction(
|
await meta.pluginData.state.starboardReactions.deleteStarboardReaction(
|
||||||
meta.args.reaction.message.id,
|
meta.args.reaction.message.id,
|
||||||
|
@ -21,9 +18,6 @@ export const StarboardReactionRemoveAllEvt = starboardEvt({
|
||||||
event: "messageReactionRemoveAll",
|
event: "messageReactionRemoveAll",
|
||||||
|
|
||||||
async listener(meta) {
|
async listener(meta) {
|
||||||
// FIXME: Temporarily disabled
|
|
||||||
return;
|
|
||||||
|
|
||||||
const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock());
|
const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock());
|
||||||
await meta.pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(meta.args.message.id);
|
await meta.pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(meta.args.message.id);
|
||||||
boardLock.unlock();
|
boardLock.unlock();
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
|
import { GuildPluginData } from "knub";
|
||||||
import { StarboardMessage } from "../../../data/entities/StarboardMessage";
|
import { StarboardMessage } from "../../../data/entities/StarboardMessage";
|
||||||
import { noop } from "../../../utils";
|
import { noop } from "../../../utils";
|
||||||
|
import { StarboardPluginType } from "../types";
|
||||||
|
|
||||||
export async function removeMessageFromStarboard(pluginData, msg: StarboardMessage) {
|
export async function removeMessageFromStarboard(
|
||||||
await pluginData.client.deleteMessage(msg.starboard_channel_id, msg.starboard_message_id).catch(noop);
|
pluginData: GuildPluginData<StarboardPluginType>,
|
||||||
|
msg: StarboardMessage,
|
||||||
|
) {
|
||||||
|
// fixes stuck entries on starboard_reactions table after messages being deleted, probably should add a cleanup script for this as well, i.e. DELETE FROM starboard_reactions WHERE message_id NOT IN (SELECT id FROM starboard_messages)
|
||||||
|
await pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(msg.message_id).catch(noop);
|
||||||
|
|
||||||
|
// this code is now Almeida-certified and no longer ugly :ok_hand: :cake:
|
||||||
|
const channel = pluginData.client.channels.cache.find(c => c.id === msg.starboard_channel_id);
|
||||||
|
if (!channel?.isText()) return;
|
||||||
|
const message = await channel.messages.fetch(msg.starboard_message_id).catch(noop);
|
||||||
|
if (!message?.deletable) return;
|
||||||
|
await message.delete().catch(noop);
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ export async function getInviteInfoEmbed(
|
||||||
`),
|
`),
|
||||||
inline: true,
|
inline: true,
|
||||||
});
|
});
|
||||||
|
if (invite.channel) {
|
||||||
const channelName =
|
const channelName =
|
||||||
invite.channel.type === ChannelTypeStrings.VOICE ? `🔉 ${invite.channel.name}` : `#${invite.channel.name}`;
|
invite.channel.type === ChannelTypeStrings.VOICE ? `🔉 ${invite.channel.name}` : `#${invite.channel.name}`;
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@ export async function getInviteInfoEmbed(
|
||||||
value: channelInfo,
|
value: channelInfo,
|
||||||
inline: true,
|
inline: true,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (invite.inviter) {
|
if (invite.inviter) {
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { MessageEmbedOptions, Snowflake } from "discord.js";
|
import { MessageEmbedOptions, PremiumTier, Snowflake } from "discord.js";
|
||||||
import humanizeDuration from "humanize-duration";
|
import humanizeDuration from "humanize-duration";
|
||||||
import { GuildPluginData } from "knub";
|
import { GuildPluginData } from "knub";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
|
@ -19,6 +19,13 @@ import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
|
||||||
import { UtilityPluginType } from "../types";
|
import { UtilityPluginType } from "../types";
|
||||||
import { getGuildPreview } from "./getGuildPreview";
|
import { getGuildPreview } from "./getGuildPreview";
|
||||||
|
|
||||||
|
const PremiumTiers: Record<PremiumTier, number> = {
|
||||||
|
NONE: 0,
|
||||||
|
TIER_1: 1,
|
||||||
|
TIER_2: 2,
|
||||||
|
TIER_3: 3,
|
||||||
|
};
|
||||||
|
|
||||||
export async function getServerInfoEmbed(
|
export async function getServerInfoEmbed(
|
||||||
pluginData: GuildPluginData<UtilityPluginType>,
|
pluginData: GuildPluginData<UtilityPluginType>,
|
||||||
serverId: string,
|
serverId: string,
|
||||||
|
@ -179,20 +186,22 @@ export async function getServerInfoEmbed(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restGuild) {
|
if (restGuild) {
|
||||||
|
const premiumTierValue = PremiumTiers[restGuild.premiumTier];
|
||||||
|
|
||||||
const maxEmojis =
|
const maxEmojis =
|
||||||
{
|
{
|
||||||
0: 50,
|
0: 50,
|
||||||
1: 100,
|
1: 100,
|
||||||
2: 150,
|
2: 150,
|
||||||
3: 250,
|
3: 250,
|
||||||
}[restGuild.premiumTier] || 50;
|
}[premiumTierValue] ?? 50;
|
||||||
const maxStickers =
|
const maxStickers =
|
||||||
{
|
{
|
||||||
0: 0,
|
0: 0,
|
||||||
1: 15,
|
1: 15,
|
||||||
2: 30,
|
2: 30,
|
||||||
3: 60,
|
3: 60,
|
||||||
}[restGuild.premiumTier] || 0;
|
}[premiumTierValue] ?? 0;
|
||||||
|
|
||||||
otherStats.push(`Emojis: **${restGuild.emojis.cache.size}** / ${maxEmojis * 2}`);
|
otherStats.push(`Emojis: **${restGuild.emojis.cache.size}** / ${maxEmojis * 2}`);
|
||||||
otherStats.push(`Stickers: **${restGuild.stickers.cache.size}** / ${maxStickers}`);
|
otherStats.push(`Stickers: **${restGuild.stickers.cache.size}** / ${maxStickers}`);
|
||||||
|
@ -202,7 +211,9 @@ export async function getServerInfoEmbed(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thisServer) {
|
if (thisServer) {
|
||||||
otherStats.push(`Boosts: **${thisServer.premiumSubscriptionCount ?? 0}** (level ${thisServer.premiumTier})`);
|
otherStats.push(
|
||||||
|
`Boosts: **${thisServer.premiumSubscriptionCount ?? 0}** (level ${PremiumTiers[thisServer.premiumTier]})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
|
|
|
@ -36,7 +36,7 @@ export async function getUserInfoEmbed(
|
||||||
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
|
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
|
||||||
|
|
||||||
embed.author = {
|
embed.author = {
|
||||||
name: `User: ${user.tag}`,
|
name: `${user.bot ? "Bot" : "User"}: ${user.tag}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const avatarURL = user.displayAvatarURL();
|
const avatarURL = user.displayAvatarURL();
|
||||||
|
@ -54,7 +54,7 @@ export async function getUserInfoEmbed(
|
||||||
|
|
||||||
if (compact) {
|
if (compact) {
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "User information",
|
name: preEmbedPadding + `${user.bot ? "Bot" : "User"} information`,
|
||||||
value: trimLines(`
|
value: trimLines(`
|
||||||
Profile: <@!${user.id}>
|
Profile: <@!${user.id}>
|
||||||
Created: **${accountAge} ago** (\`${prettyCreatedAt}\`)
|
Created: **${accountAge} ago** (\`${prettyCreatedAt}\`)
|
||||||
|
@ -70,11 +70,12 @@ export async function getUserInfoEmbed(
|
||||||
largest: 2,
|
largest: 2,
|
||||||
round: true,
|
round: true,
|
||||||
});
|
});
|
||||||
embed.fields[0].value += `\nJoined: **${joinAge} ago** (\`${prettyJoinedAt}\`)`;
|
|
||||||
|
embed.fields[0].value += `\n${user.bot ? "Added" : "Joined"}: **${joinAge} ago** (\`${prettyJoinedAt}\`)`;
|
||||||
} else {
|
} else {
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "!! NOTE !!",
|
name: preEmbedPadding + "!! NOTE !!",
|
||||||
value: "User is not on the server",
|
value: `${user.bot ? "Bot" : "User"} is not on the server`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +83,7 @@ export async function getUserInfoEmbed(
|
||||||
}
|
}
|
||||||
|
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "User information",
|
name: preEmbedPadding + `${user.bot ? "Bot" : "User"} information`,
|
||||||
value: trimLines(`
|
value: trimLines(`
|
||||||
Name: **${user.tag}**
|
Name: **${user.tag}**
|
||||||
ID: \`${user.id}\`
|
ID: \`${user.id}\`
|
||||||
|
@ -107,7 +108,7 @@ export async function getUserInfoEmbed(
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "Member information",
|
name: preEmbedPadding + "Member information",
|
||||||
value: trimLines(`
|
value: trimLines(`
|
||||||
Joined: **${joinAge} ago** (\`${prettyJoinedAt}\`)
|
${user.bot ? "Added" : "Joined"}: **${joinAge} ago** (\`${prettyJoinedAt}\`)
|
||||||
${roles.length > 0 ? "Roles: " + roles.map(r => `<@&${r.id}>`).join(", ") : ""}
|
${roles.length > 0 ? "Roles: " + roles.map(r => `<@&${r.id}>`).join(", ") : ""}
|
||||||
`),
|
`),
|
||||||
});
|
});
|
||||||
|
@ -126,7 +127,7 @@ export async function getUserInfoEmbed(
|
||||||
} else {
|
} else {
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "Member information",
|
name: preEmbedPadding + "Member information",
|
||||||
value: "⚠ User is not on the server",
|
value: `⚠ ${user.bot ? "Bot" : "User"} is not on the server`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const cases = (await pluginData.state.cases.getByUserId(user.id)).filter(c => !c.is_hidden);
|
const cases = (await pluginData.state.cases.getByUserId(user.id)).filter(c => !c.is_hidden);
|
||||||
|
|
|
@ -68,7 +68,9 @@ export const SendWelcomeMessageEvt = welcomeMessageEvt({
|
||||||
if (!channel || !(channel instanceof TextChannel)) return;
|
if (!channel || !(channel instanceof TextChannel)) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createChunkedMessage(channel, formatted);
|
await createChunkedMessage(channel, formatted, {
|
||||||
|
parse: ["users"],
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
pluginData.getPlugin(LogsPlugin).logBotAlert({
|
pluginData.getPlugin(LogsPlugin).logBotAlert({
|
||||||
body: `Failed send a welcome message for ${verboseUserMention(member.user)} to ${verboseChannelMention(
|
body: `Failed send a welcome message for ${verboseUserMention(member.user)} to ${verboseChannelMention(
|
||||||
|
|
|
@ -388,6 +388,10 @@ const baseValues = {
|
||||||
ucfirst(arg) {
|
ucfirst(arg) {
|
||||||
return baseValues.upperFirst(arg);
|
return baseValues.upperFirst(arg);
|
||||||
},
|
},
|
||||||
|
strlen(arg) {
|
||||||
|
if (typeof arg !== "string") return 0;
|
||||||
|
return [...arg].length;
|
||||||
|
},
|
||||||
rand(from, to, seed = null) {
|
rand(from, to, seed = null) {
|
||||||
if (isNaN(from)) return 0;
|
if (isNaN(from)) return 0;
|
||||||
|
|
||||||
|
@ -406,6 +410,10 @@ const baseValues = {
|
||||||
|
|
||||||
return Math.round(randValue * (to - from) + from);
|
return Math.round(randValue * (to - from) + from);
|
||||||
},
|
},
|
||||||
|
round(arg, decimals = 0) {
|
||||||
|
if (isNaN(arg)) return 0;
|
||||||
|
return decimals === 0 ? Math.round(arg) : arg.toFixed(decimals);
|
||||||
|
},
|
||||||
add(...args) {
|
add(...args) {
|
||||||
return args.reduce((result, arg) => {
|
return args.reduce((result, arg) => {
|
||||||
if (isNaN(arg)) return result;
|
if (isNaN(arg)) return result;
|
||||||
|
|
|
@ -743,10 +743,11 @@ export function isNotNull(value): value is Exclude<typeof value, null> {
|
||||||
// discordapp.com/invite/<code>
|
// discordapp.com/invite/<code>
|
||||||
// discord.gg/invite/<code>
|
// discord.gg/invite/<code>
|
||||||
// discord.gg/<code>
|
// discord.gg/<code>
|
||||||
const quickInviteDetection = /(?:discord.com|discordapp.com)\/invite\/([a-z0-9\-]+)|discord.gg\/(?:\S+\/)?([a-z0-9\-]+)/gi;
|
// discord.com/friend-invite/<code>
|
||||||
|
const quickInviteDetection = /discord(?:app)?\.com\/(?:friend-)?invite\/([a-z0-9\-]+)|discord\.gg\/(?:\S+\/)?([a-z0-9\-]+)/gi;
|
||||||
|
|
||||||
const isInviteHostRegex = /(?:^|\.)(?:discord.gg|discord.com|discordapp.com)$/i;
|
const isInviteHostRegex = /(?:^|\.)(?:discord.gg|discord.com|discordapp.com)$/i;
|
||||||
const longInvitePathRegex = /^\/invite\/([a-z0-9\-]+)$/i;
|
const longInvitePathRegex = /^\/(?:friend-)?invite\/([a-z0-9\-]+)$/i;
|
||||||
|
|
||||||
export function getInviteCodesInString(str: string): string[] {
|
export function getInviteCodesInString(str: string): string[] {
|
||||||
const inviteCodes: string[] = [];
|
const inviteCodes: string[] = [];
|
||||||
|
@ -778,6 +779,8 @@ export function getInviteCodesInString(str: string): string[] {
|
||||||
|
|
||||||
// discord.com/invite/<code>[/anything]
|
// discord.com/invite/<code>[/anything]
|
||||||
// discordapp.com/invite/<code>[/anything]
|
// discordapp.com/invite/<code>[/anything]
|
||||||
|
// discord.com/friend-invite/<code>[/anything]
|
||||||
|
// discordapp.com/friend-invite/<code>[/anything]
|
||||||
const longInviteMatch = url.pathname.match(longInvitePathRegex);
|
const longInviteMatch = url.pathname.match(longInvitePathRegex);
|
||||||
if (longInviteMatch) {
|
if (longInviteMatch) {
|
||||||
return longInviteMatch[1];
|
return longInviteMatch[1];
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
"cwd": "./backend",
|
"cwd": "./backend",
|
||||||
"script": "npm",
|
"script": "npm",
|
||||||
"args": "run start-api-prod",
|
"args": "run start-api-prod",
|
||||||
"log_date_format": "YYYY-MM-DD HH:mm:ss"
|
"log_date_format": "YYYY-MM-DD HH:mm:ss.SSS",
|
||||||
|
"exp_backoff_restart_delay": 2500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
"cwd": "./backend",
|
"cwd": "./backend",
|
||||||
"script": "npm",
|
"script": "npm",
|
||||||
"args": "run start-bot-prod",
|
"args": "run start-bot-prod",
|
||||||
"log_date_format": "YYYY-MM-DD HH:mm:ss"
|
"log_date_format": "YYYY-MM-DD HH:mm:ss.SSS",
|
||||||
|
"exp_backoff_restart_delay": 2500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
16
update-backend-hotfix.sh
Executable file
16
update-backend-hotfix.sh
Executable file
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Load nvm
|
||||||
|
. ~/.nvm/nvm.sh
|
||||||
|
|
||||||
|
# Run hotfix update
|
||||||
|
cd backend
|
||||||
|
nvm use
|
||||||
|
git pull
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Restart processes
|
||||||
|
cd ..
|
||||||
|
nvm use
|
||||||
|
pm2 restart process-bot.json
|
||||||
|
pm2 restart process-api.json
|
25
update-backend.sh
Executable file
25
update-backend.sh
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Load nvm
|
||||||
|
. ~/.nvm/nvm.sh
|
||||||
|
|
||||||
|
# Stop current processes
|
||||||
|
nvm use
|
||||||
|
pm2 delete process-bot.json
|
||||||
|
pm2 delete process-api.json
|
||||||
|
|
||||||
|
# Run update
|
||||||
|
nvm use
|
||||||
|
git pull
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
cd backend
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
npm run migrate-prod
|
||||||
|
|
||||||
|
# Start processes again
|
||||||
|
cd ..
|
||||||
|
nvm use
|
||||||
|
pm2 start process-bot.json
|
||||||
|
pm2 start process-api.json
|
15
update-dashboard.sh
Executable file
15
update-dashboard.sh
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
TARGET_DIR=/var/www/zeppelin.gg
|
||||||
|
|
||||||
|
# Load nvm
|
||||||
|
. ~/.nvm/nvm.sh
|
||||||
|
|
||||||
|
# Update dashboard
|
||||||
|
cd dashboard
|
||||||
|
git pull
|
||||||
|
nvm use
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
rm -r "$TARGET_DIR/*"
|
||||||
|
cp -R dist/* "$TARGET_DIR"
|
11
update.sh
11
update.sh
|
@ -1,11 +1,4 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Load nvm
|
. ./update-backend.sh
|
||||||
. ~/.nvm/nvm.sh
|
. ./update-dashboard.sh
|
||||||
|
|
||||||
# Run update
|
|
||||||
nvm use
|
|
||||||
git pull
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
pm2 restart process.json
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue