3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-05-23 09:35:02 +00:00
This commit is contained in:
metal 2021-09-05 09:16:16 +00:00 committed by GitHub
commit 05256c1985
41 changed files with 405 additions and 119 deletions

View file

@ -24,7 +24,7 @@
"humanize-duration": "^3.15.0",
"io-ts": "^2.0.0",
"js-yaml": "^3.13.1",
"knub": "^30.0.0-beta.42",
"knub": "^30.0.0-beta.45",
"knub-command-manager": "^9.1.0",
"last-commit-log": "^2.1.0",
"lodash.chunk": "^4.2.0",
@ -3043,9 +3043,9 @@
}
},
"node_modules/knub": {
"version": "30.0.0-beta.42",
"resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.42.tgz",
"integrity": "sha512-y7nqQh1bzQniYwEftdv6S8Jp2qBvT5a7vn+3JeA0s0ADXobI+/rRVznpq8o0x2m0+E+EeKxo1Ch8F8Hy+VMX6w==",
"version": "30.0.0-beta.45",
"resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.45.tgz",
"integrity": "sha512-r1jtHBYthOn8zjgyILh418/Qnw8f/cUMzz5aky7+T5HLFV0BAiBzeg5TOb0UFMkn8ewIPSy8GTG1x/CIAv3s8Q==",
"dependencies": {
"discord-api-types": "^0.22.0",
"discord.js": "^13.0.1",
@ -8290,9 +8290,9 @@
}
},
"knub": {
"version": "30.0.0-beta.42",
"resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.42.tgz",
"integrity": "sha512-y7nqQh1bzQniYwEftdv6S8Jp2qBvT5a7vn+3JeA0s0ADXobI+/rRVznpq8o0x2m0+E+EeKxo1Ch8F8Hy+VMX6w==",
"version": "30.0.0-beta.45",
"resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.45.tgz",
"integrity": "sha512-r1jtHBYthOn8zjgyILh418/Qnw8f/cUMzz5aky7+T5HLFV0BAiBzeg5TOb0UFMkn8ewIPSy8GTG1x/CIAv3s8Q==",
"requires": {
"discord-api-types": "^0.22.0",
"discord.js": "^13.0.1",

View file

@ -39,7 +39,7 @@
"humanize-duration": "^3.15.0",
"io-ts": "^2.0.0",
"js-yaml": "^3.13.1",
"knub": "^30.0.0-beta.42",
"knub": "^30.0.0-beta.45",
"knub-command-manager": "^9.1.0",
"last-commit-log": "^2.1.0",
"lodash.chunk": "^4.2.0",

View file

@ -226,9 +226,6 @@ export class GuildSavedMessages extends BaseGuildRepository {
}
async createFromMsg(msg: Message, overrides = {}) {
const existingSavedMsg = await this.find(msg.id);
if (existingSavedMsg) return;
// FIXME: Hotfix
if (!msg.channel) {
return;

View file

@ -300,6 +300,22 @@ connect().then(async () => {
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();
logger.info("Bot Initialized");
logger.info("Logging in...");

View file

@ -232,7 +232,7 @@ export function getBaseUrl(pluginData: AnyPluginData<any>) {
export function isOwner(pluginData: AnyPluginData<any>, userId: string) {
const knub = pluginData.getKnubInstance() as TZeppelinKnub;
const owners = knub.getGlobalConfig().owners;
const owners = knub.getGlobalConfig()?.owners;
if (!owners) {
return false;
}

View file

@ -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>`,
]);
}
}
}
}
}

View 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);
}
},
});

View file

@ -3,6 +3,7 @@ import { AutomodActionBlueprint } from "../helpers";
import { AddRolesAction } from "./addRoles";
import { AddToCounterAction } from "./addToCounter";
import { AlertAction } from "./alert";
import { ArchiveThreadAction } from "./archiveThread";
import { BanAction } from "./ban";
import { ChangeNicknameAction } from "./changeNickname";
import { CleanAction } from "./clean";
@ -34,6 +35,7 @@ export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
set_counter: SetCounterAction,
set_slowmode: SetSlowmodeAction,
start_thread: StartThreadAction,
archive_thread: ArchiveThreadAction,
};
export const AvailableActions = t.type({
@ -53,4 +55,5 @@ export const AvailableActions = t.type({
set_counter: SetCounterAction.configType,
set_slowmode: SetSlowmodeAction.configType,
start_thread: StartThreadAction.configType,
archive_thread: ArchiveThreadAction.configType,
});

View file

@ -1,7 +1,6 @@
import { MessageOptions, Permissions, Snowflake, TextChannel, ThreadChannel, User } from "discord.js";
import * as t from "io-ts";
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { LogType } from "../../../data/LogType";
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter";
import {
convertDelayStringToMS,
@ -25,6 +24,7 @@ export const ReplyAction = automodAction({
t.type({
text: tMessageContent,
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 user = users[0];
const renderReplyText = async str =>
const renderReplyText = async (str: string) =>
renderTemplate(
str,
new TemplateSafeValueContainer({
@ -94,16 +94,26 @@ export const ReplyAction = automodAction({
}
const messageContent = validateAndParseMessageContent(formatted);
const replyMsg = await channel.send({
const messageOpts: MessageOptions = {
...messageContent,
allowedMentions: {
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) {
const delay = convertDelayStringToMS(String(actionConfig.auto_delete))!;
setTimeout(() => replyMsg.delete().catch(noop), delay);
setTimeout(() => !replyMsg.deleted && replyMsg.delete().catch(noop), delay);
}
}
}

View file

@ -14,8 +14,8 @@ export const RunAutomodOnMemberUpdate = typedGuildEventListener<AutomodPluginTyp
if (isEqual(oldRoles, newRoles)) return;
const addedRoles = diff(oldRoles, newRoles);
const removedRoles = diff(newRoles, oldRoles);
const addedRoles = diff(newRoles, oldRoles);
const removedRoles = diff(oldRoles, newRoles);
if (addedRoles.length || removedRoles.length) {
const context: AutomodContext = {

View file

@ -1,4 +1,4 @@
import { Constants } from "discord.js";
import { Constants, MessageEmbed } from "discord.js";
import { GuildPluginData } from "knub";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { resolveMember } from "../../../utils";
@ -32,9 +32,9 @@ export async function* matchMultipleTextTypesOnMessage(
yield ["message", msg.data.content];
}
if (trigger.match_embeds && msg.data.embeds && msg.data.embeds.length) {
const copiedEmbed = JSON.parse(JSON.stringify(msg.data.embeds[0]));
if (copiedEmbed.type === "video") {
if (trigger.match_embeds && msg.data.embeds?.length) {
const copiedEmbed: MessageEmbed = JSON.parse(JSON.stringify(msg.data.embeds[0]));
if (copiedEmbed.video) {
copiedEmbed.description = ""; // The description is not rendered, hence it doesn't need to be matched
}
yield ["embed", JSON.stringify(copiedEmbed)];

View file

@ -11,6 +11,7 @@ import { KickTrigger } from "./kick";
import { LineSpamTrigger } from "./lineSpam";
import { LinkSpamTrigger } from "./linkSpam";
import { MatchAttachmentTypeTrigger } from "./matchAttachmentType";
import { MatchMimeTypeTrigger } from "./matchMimeType";
import { MatchInvitesTrigger } from "./matchInvites";
import { MatchLinksTrigger } from "./matchLinks";
import { MatchRegexTrigger } from "./matchRegex";
@ -37,6 +38,7 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
match_invites: MatchInvitesTrigger,
match_links: MatchLinksTrigger,
match_attachment_type: MatchAttachmentTypeTrigger,
match_mime_type: MatchMimeTypeTrigger,
member_join: MemberJoinTrigger,
role_added: RoleAddedTrigger,
role_removed: RoleRemovedTrigger,
@ -72,6 +74,7 @@ export const AvailableTriggers = t.type({
match_invites: MatchInvitesTrigger.configType,
match_links: MatchLinksTrigger.configType,
match_attachment_type: MatchAttachmentTypeTrigger.configType,
match_mime_type: MatchMimeTypeTrigger.configType,
member_join: MemberJoinTrigger.configType,
member_leave: MemberLeaveTrigger.configType,
role_added: RoleAddedTrigger.configType,

View 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!)
);
},
});

View file

@ -18,11 +18,13 @@ import { ReloadServerCmd } from "./commands/ReloadServerCmd";
import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd";
import { ServersCmd } from "./commands/ServersCmd";
import { BotControlPluginType, ConfigSchema } from "./types";
import { PerformanceCmd } from "./commands/PerformanceCmd";
const defaultOptions = {
config: {
can_use: false,
can_eligible: false,
can_performance: false,
update_cmd: null,
},
};
@ -45,6 +47,7 @@ export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()({
ListDashboardUsersCmd,
ListDashboardPermsCmd,
EligibleCmd,
PerformanceCmd,
],
async afterLoad(pluginData) {

View 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);
},
});

View file

@ -9,6 +9,7 @@ import { tNullable } from "../../utils";
export const ConfigSchema = t.type({
can_use: t.boolean,
can_eligible: t.boolean,
can_performance: t.boolean,
update_cmd: tNullable(t.string),
});
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;

View file

@ -51,7 +51,7 @@ export const CustomEventsPlugin = zeppelinGuildPlugin<CustomEventsPluginType>()(
}
const values = createTypedTemplateSafeValueContainer({
...args,
...safeArgs,
msg: messageToTemplateSafeMessage(message),
});

View file

@ -28,9 +28,11 @@ export async function addRoleAction(
if (event.trigger.type === "command" && !canActOn(pluginData, eventData.msg.member, target)) {
throw new ActionError("Missing permissions");
}
const rolesToAdd = Array.isArray(action.role) ? action.role : [action.role];
await target.edit({
roles: Array.from(new Set([...target.roles.cache.values(), ...rolesToAdd])) as Snowflake[],
});
const rolesToAdd = (Array.isArray(action.role) ? action.role : [action.role]).filter(
id => !target.roles.cache.has(id),
);
if (rolesToAdd.length === 0) {
throw new ActionError("Target already has the role(s) specified");
}
await target.roles.add(rolesToAdd);
}

View file

@ -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 { GuildPluginData } from "knub";
import { ActionError } from "../ActionError";
@ -32,10 +32,17 @@ export async function setChannelPermissionOverridesAction(
}
for (const override of action.overrides) {
channel.permissionOverwrites.create(
override.id as Snowflake,
new Permissions(BigInt(override.allow)).remove(BigInt(override.deny)).serialize(),
);
const allow = new Permissions(BigInt(override.allow)).serialize();
const deny = new Permissions(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(

View file

@ -17,6 +17,8 @@ export const LogsGuildMemberUpdateEvt = logsEvt({
const pluginData = meta.pluginData;
const oldMember = meta.args.oldMember;
const member = meta.args.newMember;
const oldRoles = [...oldMember.roles.cache.keys()];
const currentRoles = [...member.roles.cache.keys()];
if (!oldMember || oldMember.partial) {
return;
@ -30,9 +32,9 @@ export const LogsGuildMemberUpdateEvt = logsEvt({
});
}
if (!isEqual(oldMember.roles, member.roles)) {
const addedRoles = diff([...member.roles.cache.keys()], [...oldMember.roles.cache.keys()]);
const removedRoles = diff([...oldMember.roles.cache.keys()], [...member.roles.cache.keys()]);
if (!isEqual(oldRoles, currentRoles)) {
const addedRoles = diff(currentRoles, oldRoles);
const removedRoles = diff(oldRoles, currentRoles);
let skip = false;
if (

View file

@ -21,6 +21,9 @@ const defaultOptions: PluginOptions<MessageSaverPluginType> = {
],
};
let debugId = 0;
const debugGuilds = ["877581055920603238", "348468156597010432", "134286179121102848"];
export const MessageSaverPlugin = zeppelinGuildPlugin<MessageSaverPluginType>()({
name: "message_saver",
showInDocs: false,
@ -45,6 +48,18 @@ export const MessageSaverPlugin = zeppelinGuildPlugin<MessageSaverPluginType>()(
beforeLoad(pluginData) {
const { state, guild } = pluginData;
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}`,
);
}
},
});

View file

@ -1,14 +1,21 @@
import { Constants, Message, MessageType, Snowflake } from "discord.js";
import { messageSaverEvt } from "../types";
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;
setInterval(() => {
const toDelete = recentlyCreatedMessages.length - recentlyCreatedMessagesToKeep;
if (toDelete > 0) {
recentlyCreatedMessages.splice(0, toDelete);
let toDelete = recentlyCreatedMessages.size - recentlyCreatedMessagesToKeep;
for (const key of recentlyCreatedMessages.keys()) {
if (toDelete === 0) {
break;
}
recentlyCreatedMessages.delete(key);
toDelete--;
}
}, 60 * SECONDS);
@ -29,17 +36,25 @@ export const MessageCreateEvt = messageSaverEvt({
return;
}
meta.pluginData.state.queue.add(async () => {
if (recentlyCreatedMessages.includes(meta.args.message.id)) {
console.warn(
`Tried to save duplicate message from messageCreate event: ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`,
);
// Don't save the bot's own messages
if (meta.args.message.author.id === meta.pluginData.client.user?.id) {
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);
});
},
});
@ -57,9 +72,7 @@ export const MessageUpdateEvt = messageSaverEvt({
return;
}
meta.pluginData.state.queue.add(async () => {
await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.newMessage as Message);
});
},
});
@ -74,9 +87,7 @@ export const MessageDeleteEvt = messageSaverEvt({
return;
}
meta.pluginData.state.queue.add(async () => {
await meta.pluginData.state.savedMessages.markAsDeleted(msg.id);
});
},
});
@ -87,8 +98,6 @@ export const MessageDeleteBulkEvt = messageSaverEvt({
async listener(meta) {
const ids = meta.args.messages.map(m => m.id);
meta.pluginData.state.queue.add(async () => {
await meta.pluginData.state.savedMessages.markBulkAsDeleted(ids);
});
},
});

View file

@ -12,7 +12,7 @@ export interface MessageSaverPluginType extends BasePluginType {
config: TConfigSchema;
state: {
savedMessages: GuildSavedMessages;
queue: Queue;
debugId: number;
};
}

View file

@ -16,7 +16,7 @@ export const StoreDataEvt = persistEvt({
const persistedRoles = config.persisted_roles;
if (persistedRoles.length && member.roles) {
const rolesToPersist = intersection(persistedRoles, member.roles);
const rolesToPersist = intersection(persistedRoles, [...member.roles.cache.keys()]);
if (rolesToPersist.length) {
persist = true;
persistData.roles = rolesToPersist;

View file

@ -25,7 +25,7 @@ export async function applyReactionRoleReactionsToMessage(
let targetMessage;
try {
targetMessage = channel.messages.fetch(messageId, { force: true });
targetMessage = await channel.messages.fetch(messageId, { force: true });
} catch (e) {
if (isDiscordAPIError(e)) {
if (e.code === 10008) {

View file

@ -63,7 +63,7 @@ export const RemoveRoleCmd = rolesCmd({
sendSuccessMessage(
pluginData,
msg.channel,
`Removed role **${role.name}** removed from ${verboseUserMention(args.member.user)}!`,
`Removed role **${role.name}** from ${verboseUserMention(args.member.user)}!`,
);
},
});

View file

@ -9,9 +9,6 @@ export const StarboardReactionAddEvt = starboardEvt({
event: "messageReactionAdd",
async listener(meta) {
// FIXME: Temporarily disabled
return;
const pluginData = meta.pluginData;
let msg = meta.args.reaction.message as Message;

View file

@ -5,9 +5,6 @@ export const StarboardReactionRemoveEvt = starboardEvt({
event: "messageReactionRemove",
async listener(meta) {
// FIXME: Temporarily disabled
return;
const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock());
await meta.pluginData.state.starboardReactions.deleteStarboardReaction(
meta.args.reaction.message.id,
@ -21,9 +18,6 @@ export const StarboardReactionRemoveAllEvt = starboardEvt({
event: "messageReactionRemoveAll",
async listener(meta) {
// FIXME: Temporarily disabled
return;
const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock());
await meta.pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(meta.args.message.id);
boardLock.unlock();

View file

@ -1,6 +1,19 @@
import { GuildPluginData } from "knub";
import { StarboardMessage } from "../../../data/entities/StarboardMessage";
import { noop } from "../../../utils";
import { StarboardPluginType } from "../types";
export async function removeMessageFromStarboard(pluginData, msg: StarboardMessage) {
await pluginData.client.deleteMessage(msg.starboard_channel_id, msg.starboard_message_id).catch(noop);
export async function removeMessageFromStarboard(
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);
}

View file

@ -65,7 +65,7 @@ export async function getInviteInfoEmbed(
`),
inline: true,
});
if (invite.channel) {
const channelName =
invite.channel.type === ChannelTypeStrings.VOICE ? `🔉 ${invite.channel.name}` : `#${invite.channel.name}`;
@ -91,6 +91,7 @@ export async function getInviteInfoEmbed(
value: channelInfo,
inline: true,
});
}
if (invite.inviter) {
embed.fields.push({

View file

@ -1,4 +1,4 @@
import { MessageEmbedOptions, Snowflake } from "discord.js";
import { MessageEmbedOptions, PremiumTier, Snowflake } from "discord.js";
import humanizeDuration from "humanize-duration";
import { GuildPluginData } from "knub";
import moment from "moment-timezone";
@ -19,6 +19,13 @@ import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
import { UtilityPluginType } from "../types";
import { getGuildPreview } from "./getGuildPreview";
const PremiumTiers: Record<PremiumTier, number> = {
NONE: 0,
TIER_1: 1,
TIER_2: 2,
TIER_3: 3,
};
export async function getServerInfoEmbed(
pluginData: GuildPluginData<UtilityPluginType>,
serverId: string,
@ -179,20 +186,22 @@ export async function getServerInfoEmbed(
}
if (restGuild) {
const premiumTierValue = PremiumTiers[restGuild.premiumTier];
const maxEmojis =
{
0: 50,
1: 100,
2: 150,
3: 250,
}[restGuild.premiumTier] || 50;
}[premiumTierValue] ?? 50;
const maxStickers =
{
0: 0,
1: 15,
2: 30,
3: 60,
}[restGuild.premiumTier] || 0;
}[premiumTierValue] ?? 0;
otherStats.push(`Emojis: **${restGuild.emojis.cache.size}** / ${maxEmojis * 2}`);
otherStats.push(`Stickers: **${restGuild.stickers.cache.size}** / ${maxStickers}`);
@ -202,7 +211,9 @@ export async function getServerInfoEmbed(
}
if (thisServer) {
otherStats.push(`Boosts: **${thisServer.premiumSubscriptionCount ?? 0}** (level ${thisServer.premiumTier})`);
otherStats.push(
`Boosts: **${thisServer.premiumSubscriptionCount ?? 0}** (level ${PremiumTiers[thisServer.premiumTier]})`,
);
}
embed.fields.push({

View file

@ -36,7 +36,7 @@ export async function getUserInfoEmbed(
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
embed.author = {
name: `User: ${user.tag}`,
name: `${user.bot ? "Bot" : "User"}: ${user.tag}`,
};
const avatarURL = user.displayAvatarURL();
@ -54,7 +54,7 @@ export async function getUserInfoEmbed(
if (compact) {
embed.fields.push({
name: preEmbedPadding + "User information",
name: preEmbedPadding + `${user.bot ? "Bot" : "User"} information`,
value: trimLines(`
Profile: <@!${user.id}>
Created: **${accountAge} ago** (\`${prettyCreatedAt}\`)
@ -70,11 +70,12 @@ export async function getUserInfoEmbed(
largest: 2,
round: true,
});
embed.fields[0].value += `\nJoined: **${joinAge} ago** (\`${prettyJoinedAt}\`)`;
embed.fields[0].value += `\n${user.bot ? "Added" : "Joined"}: **${joinAge} ago** (\`${prettyJoinedAt}\`)`;
} else {
embed.fields.push({
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({
name: preEmbedPadding + "User information",
name: preEmbedPadding + `${user.bot ? "Bot" : "User"} information`,
value: trimLines(`
Name: **${user.tag}**
ID: \`${user.id}\`
@ -107,7 +108,7 @@ export async function getUserInfoEmbed(
embed.fields.push({
name: preEmbedPadding + "Member information",
value: trimLines(`
Joined: **${joinAge} ago** (\`${prettyJoinedAt}\`)
${user.bot ? "Added" : "Joined"}: **${joinAge} ago** (\`${prettyJoinedAt}\`)
${roles.length > 0 ? "Roles: " + roles.map(r => `<@&${r.id}>`).join(", ") : ""}
`),
});
@ -126,7 +127,7 @@ export async function getUserInfoEmbed(
} else {
embed.fields.push({
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);

View file

@ -68,7 +68,9 @@ export const SendWelcomeMessageEvt = welcomeMessageEvt({
if (!channel || !(channel instanceof TextChannel)) return;
try {
await createChunkedMessage(channel, formatted);
await createChunkedMessage(channel, formatted, {
parse: ["users"],
});
} catch {
pluginData.getPlugin(LogsPlugin).logBotAlert({
body: `Failed send a welcome message for ${verboseUserMention(member.user)} to ${verboseChannelMention(

View file

@ -388,6 +388,10 @@ const baseValues = {
ucfirst(arg) {
return baseValues.upperFirst(arg);
},
strlen(arg) {
if (typeof arg !== "string") return 0;
return [...arg].length;
},
rand(from, to, seed = null) {
if (isNaN(from)) return 0;
@ -406,6 +410,10 @@ const baseValues = {
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) {
return args.reduce((result, arg) => {
if (isNaN(arg)) return result;

View file

@ -743,10 +743,11 @@ export function isNotNull(value): value is Exclude<typeof value, null> {
// discordapp.com/invite/<code>
// discord.gg/invite/<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 longInvitePathRegex = /^\/invite\/([a-z0-9\-]+)$/i;
const longInvitePathRegex = /^\/(?:friend-)?invite\/([a-z0-9\-]+)$/i;
export function getInviteCodesInString(str: string): string[] {
const inviteCodes: string[] = [];
@ -778,6 +779,8 @@ export function getInviteCodesInString(str: string): string[] {
// discord.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);
if (longInviteMatch) {
return longInviteMatch[1];

View file

@ -5,7 +5,8 @@
"cwd": "./backend",
"script": "npm",
"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
}
]
}

View file

@ -5,7 +5,8 @@
"cwd": "./backend",
"script": "npm",
"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
View 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
View 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
View 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"

View file

@ -1,11 +1,4 @@
#!/bin/bash
# Load nvm
. ~/.nvm/nvm.sh
# Run update
nvm use
git pull
npm ci
npm run build
pm2 restart process.json
. ./update-backend.sh
. ./update-dashboard.sh