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",
|
||||
"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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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...");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 { 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,
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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)];
|
||||
|
|
|
@ -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,
|
||||
|
|
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 { 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) {
|
||||
|
|
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({
|
||||
can_use: t.boolean,
|
||||
can_eligible: t.boolean,
|
||||
can_performance: t.boolean,
|
||||
update_cmd: tNullable(t.string),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
|
|
@ -51,7 +51,7 @@ export const CustomEventsPlugin = zeppelinGuildPlugin<CustomEventsPluginType>()(
|
|||
}
|
||||
|
||||
const values = createTypedTemplateSafeValueContainer({
|
||||
...args,
|
||||
...safeArgs,
|
||||
msg: messageToTemplateSafeMessage(message),
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
recentlyCreatedMessages.push(meta.args.message.id);
|
||||
// Don't save the bot's own messages
|
||||
if (meta.args.message.author.id === meta.pluginData.client.user?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message);
|
||||
});
|
||||
// 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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
await meta.pluginData.state.savedMessages.markBulkAsDeleted(ids);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ export interface MessageSaverPluginType extends BasePluginType {
|
|||
config: TConfigSchema;
|
||||
state: {
|
||||
savedMessages: GuildSavedMessages;
|
||||
queue: Queue;
|
||||
debugId: number;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)}!`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -65,32 +65,33 @@ export async function getInviteInfoEmbed(
|
|||
`),
|
||||
inline: true,
|
||||
});
|
||||
if (invite.channel) {
|
||||
const channelName =
|
||||
invite.channel.type === ChannelTypeStrings.VOICE ? `🔉 ${invite.channel.name}` : `#${invite.channel.name}`;
|
||||
|
||||
const channelName =
|
||||
invite.channel.type === ChannelTypeStrings.VOICE ? `🔉 ${invite.channel.name}` : `#${invite.channel.name}`;
|
||||
const channelCreatedAtTimestamp = snowflakeToTimestamp(invite.channel.id);
|
||||
const channelCreatedAt = moment.utc(channelCreatedAtTimestamp, "x");
|
||||
const channelAge = humanizeDuration(Date.now() - channelCreatedAtTimestamp, {
|
||||
largest: 2,
|
||||
round: true,
|
||||
});
|
||||
|
||||
const channelCreatedAtTimestamp = snowflakeToTimestamp(invite.channel.id);
|
||||
const channelCreatedAt = moment.utc(channelCreatedAtTimestamp, "x");
|
||||
const channelAge = humanizeDuration(Date.now() - channelCreatedAtTimestamp, {
|
||||
largest: 2,
|
||||
round: true,
|
||||
});
|
||||
|
||||
let channelInfo = trimLines(`
|
||||
let channelInfo = trimLines(`
|
||||
Name: **${channelName}**
|
||||
ID: \`${invite.channel.id}\`
|
||||
Created: **${channelAge} ago**
|
||||
`);
|
||||
|
||||
if (invite.channel.type !== ChannelTypeStrings.VOICE) {
|
||||
channelInfo += `\nMention: <#${invite.channel.id}>`;
|
||||
}
|
||||
if (invite.channel.type !== ChannelTypeStrings.VOICE) {
|
||||
channelInfo += `\nMention: <#${invite.channel.id}>`;
|
||||
}
|
||||
|
||||
embed.fields.push({
|
||||
name: preEmbedPadding + "Channel information",
|
||||
value: channelInfo,
|
||||
inline: true,
|
||||
});
|
||||
embed.fields.push({
|
||||
name: preEmbedPadding + "Channel information",
|
||||
value: channelInfo,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (invite.inviter) {
|
||||
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 { 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({
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
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
|
||||
|
||||
# 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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue