Start move to d.js

This commit is contained in:
Dark 2021-05-31 03:30:55 +02:00
parent a9e0466e33
commit 9fc045cd38
No known key found for this signature in database
GPG key ID: 384C4B4F5B1E25A8
17 changed files with 339 additions and 6317 deletions

6365
backend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -25,21 +25,21 @@
"dependencies": { "dependencies": {
"@types/sharp": "^0.23.1", "@types/sharp": "^0.23.1",
"@types/twemoji": "^12.1.0", "@types/twemoji": "^12.1.0",
"bufferutil": "^4.0.1", "bufferutil": "^4.0.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"deep-diff": "^1.0.2", "deep-diff": "^1.0.2",
"discord.js": "github:monbrey/discord.js#9c42f571093b2565df28b756fdca4ac59cad0fe3",
"dotenv": "^4.0.0", "dotenv": "^4.0.0",
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"eris": "^0.15.1", "erlpack": "github:discord/erlpack",
"erlpack": "github:abalabahaha/erlpack",
"escape-string-regexp": "^1.0.5", "escape-string-regexp": "^1.0.5",
"express": "^4.17.0", "express": "^4.17.0",
"fp-ts": "^2.0.1", "fp-ts": "^2.0.1",
"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.37", "knub": "^30.0.0-beta.38",
"knub-command-manager": "^8.1.2", "knub-command-manager": "^8.1.2",
"last-commit-log": "^2.1.0", "last-commit-log": "^2.1.0",
"lodash.chunk": "^4.2.0", "lodash.chunk": "^4.2.0",
@ -66,6 +66,7 @@
"tsconfig-paths": "^3.9.0", "tsconfig-paths": "^3.9.0",
"twemoji": "^12.1.4", "twemoji": "^12.1.4",
"typeorm": "^0.2.31", "typeorm": "^0.2.31",
"utf-8-validate": "^5.0.5",
"uuid": "^3.3.2", "uuid": "^3.3.2",
"yawn-yaml": "github:dragory/yawn-yaml#string-number-fix-build", "yawn-yaml": "github:dragory/yawn-yaml#string-number-fix-build",
"zlib-sync": "^0.1.7" "zlib-sync": "^0.1.7"

View file

@ -11,7 +11,6 @@ import { Configs } from "./data/Configs";
// Always use UTC internally // Always use UTC internally
// This is also enforced for the database in data/db.ts // This is also enforced for the database in data/db.ts
import moment from "moment-timezone"; import moment from "moment-timezone";
import { Client, DiscordHTTPError, TextChannel } from "eris";
import { connect } from "./data/db"; import { connect } from "./data/db";
import { baseGuildPlugins, globalPlugins, guildPlugins } from "./plugins/availablePlugins"; import { baseGuildPlugins, globalPlugins, guildPlugins } from "./plugins/availablePlugins";
import { errorMessage, isDiscordHTTPError, isDiscordRESTError, MINUTES, successMessage } from "./utils"; import { errorMessage, isDiscordHTTPError, isDiscordRESTError, MINUTES, successMessage } from "./utils";
@ -21,10 +20,10 @@ import { ZeppelinGlobalConfig, ZeppelinGuildConfig } from "./types";
import { RecoverablePluginError } from "./RecoverablePluginError"; import { RecoverablePluginError } from "./RecoverablePluginError";
import { GuildLogs } from "./data/GuildLogs"; import { GuildLogs } from "./data/GuildLogs";
import { LogType } from "./data/LogType"; import { LogType } from "./data/LogType";
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
import { logger } from "./logger"; import { logger } from "./logger";
import { PluginLoadError } from "knub/dist/plugins/PluginLoadError"; import { PluginLoadError } from "knub/dist/plugins/PluginLoadError";
import { ErisError } from "./ErisError"; import { ErisError } from "./ErisError";
import { Client, Intents, TextChannel } from "discord.js";
const fsp = fs.promises; const fsp = fs.promises;
@ -96,7 +95,7 @@ function errorHandler(err) {
} }
} }
if (err instanceof DiscordHTTPError && err.code >= 500) { if (isDiscordHTTPError(err) && err.code >= 500) {
// Don't need stack traces on HTTP 500 errors // Don't need stack traces on HTTP 500 errors
// These also shouldn't count towards RECENT_DISCORD_ERROR_EXIT_THRESHOLD because they don't indicate an error in our code // These also shouldn't count towards RECENT_DISCORD_ERROR_EXIT_THRESHOLD because they don't indicate an error in our code
console.error(err.message); console.error(err.message);
@ -151,36 +150,32 @@ moment.tz.setDefault("UTC");
logger.info("Connecting to database"); logger.info("Connecting to database");
connect().then(async () => { connect().then(async () => {
const client = new Client(`Bot ${process.env.TOKEN}`, { const client = new Client({
getAllUsers: false, partials: ["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"],
restMode: true, restTimeOffset: 150,
compress: false, restGlobalRateLimit: 50,
guildCreateTimeout: 0,
rest: {
ratelimiterOffset: 150,
},
// Disable mentions by default // Disable mentions by default
allowedMentions: { allowedMentions: {
everyone: false, parse: [],
users: false, users: [],
roles: false, roles: [],
repliedUser: false, repliedUser: false,
}, },
intents: [ intents: [
// Privileged // Privileged
"guildMembers", Intents.FLAGS.GUILD_MEMBERS,
// "guildPresences", // "guildPresences",
"guildMessageTyping", Intents.FLAGS.GUILD_MESSAGE_TYPING,
// Regular // Regular
"directMessages", Intents.FLAGS.DIRECT_MESSAGES,
"guildBans", Intents.FLAGS.GUILD_BANS,
"guildEmojis", Intents.FLAGS.GUILD_EMOJIS,
"guildInvites", Intents.FLAGS.GUILD_INVITES,
"guildMessageReactions", Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
"guildMessages", Intents.FLAGS.GUILD_MESSAGES,
"guilds", Intents.FLAGS.GUILDS,
"guildVoiceStates", Intents.FLAGS.GUILD_VOICE_STATES,
], ],
}); });
client.setMaxListeners(200); client.setMaxListeners(200);
@ -191,8 +186,8 @@ connect().then(async () => {
} }
}); });
client.on("error", (err, shardId) => { client.on("error", err => {
errorHandler(new ErisError(err.message, (err as any).code, shardId)); errorHandler(new ErisError(err.message, (err as any).code, 0));
}); });
const allowedGuilds = new AllowedGuilds(); const allowedGuilds = new AllowedGuilds();
@ -257,13 +252,13 @@ connect().then(async () => {
sendSuccessMessageFn(channel, body) { sendSuccessMessageFn(channel, body) {
const guildId = channel instanceof TextChannel ? channel.guild.id : undefined; const guildId = channel instanceof TextChannel ? channel.guild.id : undefined;
const emoji = guildId ? bot.getLoadedGuild(guildId)!.config.success_emoji : undefined; const emoji = guildId ? bot.getLoadedGuild(guildId)!.config.success_emoji : undefined;
channel.createMessage(successMessage(body, emoji)); channel.send(successMessage(body, emoji));
}, },
sendErrorMessageFn(channel, body) { sendErrorMessageFn(channel, body) {
const guildId = channel instanceof TextChannel ? channel.guild.id : undefined; const guildId = channel instanceof TextChannel ? channel.guild.id : undefined;
const emoji = guildId ? bot.getLoadedGuild(guildId)!.config.error_emoji : undefined; const emoji = guildId ? bot.getLoadedGuild(guildId)!.config.error_emoji : undefined;
channel.createMessage(errorMessage(body, emoji)); channel.send(errorMessage(body, emoji));
}, },
}, },
}); });
@ -273,5 +268,5 @@ connect().then(async () => {
}); });
logger.info("Starting the bot"); logger.info("Starting the bot");
bot.run(); bot.initialize();
}); });

View file

@ -64,3 +64,15 @@ export interface CommandInfo {
[key: string]: TMarkdown; [key: string]: TMarkdown;
}; };
} }
export enum ChannelTypeStrings {
TEXT = "text",
DM = "dm",
VOICE = "voice",
GROUP = "group",
CATEGORY = "category",
NEWS = "news",
STORE = "store",
STAGE = "stage",
UNKNOWN = "unknown",
}

View file

@ -1,25 +1,3 @@
import {
AllowedMentions,
Attachment,
Client,
Constants,
Embed,
EmbedOptions,
Emoji,
Guild,
GuildAuditLog,
GuildAuditLogEntry,
GuildChannel,
Invite,
InvitePartialChannel,
Member,
Message,
MessageContent,
PossiblyUncachedMessage,
TextableChannel,
TextChannel,
User,
} from "eris";
import { URL } from "url"; import { URL } from "url";
import tlds from "tlds"; import tlds from "tlds";
import emojiRegex from "emoji-regex"; import emojiRegex from "emoji-regex";
@ -38,6 +16,29 @@ import { logger } from "./logger";
import { unsafeCoerce } from "fp-ts/lib/function"; import { unsafeCoerce } from "fp-ts/lib/function";
import { sendDM } from "./utils/sendDM"; import { sendDM } from "./utils/sendDM";
import { LogType } from "./data/LogType"; import { LogType } from "./data/LogType";
import {
APIMessage,
Channel,
Client,
Constants,
Emoji,
Guild,
GuildAuditLogs,
GuildAuditLogsEntry,
GuildChannel,
GuildMember,
Invite,
Message,
MessageAttachment,
MessageEmbed,
MessageEmbedOptions,
MessageMentionOptions,
MessageOptions,
StringResolvable,
TextChannel,
User,
} from "discord.js";
import { ChannelTypeStrings } from "./types";
const fsp = fs.promises; const fsp = fs.promises;
@ -202,7 +203,7 @@ export type InviteOpts = "withMetadata" | "withCount" | "withoutCount";
export type GuildInvite<CT extends InviteOpts = "withMetadata"> = Invite<CT> & { guild: Guild }; export type GuildInvite<CT extends InviteOpts = "withMetadata"> = Invite<CT> & { guild: Guild };
export type GroupDMInvite<CT extends InviteOpts = "withMetadata"> = Invite<CT> & { export type GroupDMInvite<CT extends InviteOpts = "withMetadata"> = Invite<CT> & {
channel: InvitePartialChannel; channel: InvitePartialChannel;
type: typeof Constants.ChannelTypes.GROUP_DM; type: typeof Constants.ChannelTypes.GROUP;
}; };
/** /**
@ -269,9 +270,15 @@ export const tEmbed = t.type({
), ),
}); });
export type EmbedWith<T extends keyof EmbedOptions> = EmbedOptions & Pick<Required<EmbedOptions>, T>; export type EmbedWith<T extends keyof MessageEmbedOptions> = MessageEmbedOptions &
Pick<Required<MessageEmbedOptions>, T>;
export type StrictMessageContent = { content?: string; tts?: boolean; disableEveryone?: boolean; embed?: EmbedOptions }; export type StrictMessageContent = {
content?: string;
tts?: boolean;
disableEveryone?: boolean;
embed?: MessageEmbedOptions;
};
export const tStrictMessageContent = t.type({ export const tStrictMessageContent = t.type({
content: tNullable(t.string), content: tNullable(t.string),
@ -458,14 +465,14 @@ export async function findRelevantAuditLogEntry(
userId: string, userId: string,
attempts: number = 3, attempts: number = 3,
attemptDelay: number = 3000, attemptDelay: number = 3000,
): Promise<GuildAuditLogEntry | null> { ): Promise<GuildAuditLogsEntry | null> {
if (auditLogNextAttemptAfterFail.has(guild.id) && auditLogNextAttemptAfterFail.get(guild.id)! > Date.now()) { if (auditLogNextAttemptAfterFail.has(guild.id) && auditLogNextAttemptAfterFail.get(guild.id)! > Date.now()) {
return null; return null;
} }
let auditLogs: GuildAuditLog | null = null; let auditLogs: GuildAuditLogs | null = null;
try { try {
auditLogs = await guild.getAuditLogs(5, undefined, actionType); auditLogs = await guild.fetchAuditLogs({ limit: 5, type: actionType });
} catch (e) { } catch (e) {
if (isDiscordRESTError(e) && e.code === 50013) { if (isDiscordRESTError(e) && e.code === 50013) {
// If we don't have permission to read audit log, set audit log requests on cooldown // If we don't have permission to read audit log, set audit log requests on cooldown
@ -830,13 +837,13 @@ export function chunkMessageLines(str: string, maxChunkLength = 1990): string[]
} }
export async function createChunkedMessage( export async function createChunkedMessage(
channel: TextableChannel, channel: TextChannel | User,
messageText: string, messageText: string,
allowedMentions?: AllowedMentions, allowedMentions?: MessageMentionOptions,
) { ) {
const chunks = chunkMessageLines(messageText); const chunks = chunkMessageLines(messageText);
for (const chunk of chunks) { for (const chunk of chunks) {
await channel.createMessage({ content: chunk, allowedMentions }); await channel.send({ content: chunk, allowedMentions });
} }
} }
@ -1011,7 +1018,7 @@ export async function notifyUser(
} }
} else if (method.type === "channel") { } else if (method.type === "channel") {
try { try {
await method.channel.createMessage({ await method.channel.send({
content: `<@!${user.id}> ${body}`, content: `<@!${user.id}> ${body}`,
allowedMentions: { users: [user.id] }, allowedMentions: { users: [user.id] },
}); });
@ -1130,7 +1137,7 @@ export function resolveUserId(bot: Client, value: string) {
*/ */
export function getUser(client: Client, userResolvable: string): User | UnknownUser { export function getUser(client: Client, userResolvable: string): User | UnknownUser {
const id = resolveUserId(client, userResolvable); const id = resolveUserId(client, userResolvable);
return id ? client.users.get(id) || new UnknownUser({ id }) : new UnknownUser(); return id ? client.users.resolve(id) || new UnknownUser({ id }) : new UnknownUser();
} }
/** /**
@ -1176,13 +1183,18 @@ export async function resolveUser<T>(bot, value) {
* Resolves a guild Member from the passed user id, user mention, or full username (with discriminator). * Resolves a guild Member from the passed user id, user mention, or full username (with discriminator).
* If the member is not found in the cache, it's fetched from the API. * If the member is not found in the cache, it's fetched from the API.
*/ */
export async function resolveMember(bot: Client, guild: Guild, value: string, fresh = false): Promise<Member | null> { export async function resolveMember(
bot: Client,
guild: Guild,
value: string,
fresh = false,
): Promise<GuildMember | null> {
const userId = resolveUserId(bot, value); const userId = resolveUserId(bot, value);
if (!userId) return null; if (!userId) return null;
// If we have the member cached, return that directly // If we have the member cached, return that directly
if (guild.members.has(userId) && !fresh) { if (guild.members.cache.has(userId) && !fresh) {
return guild.members.get(userId) || null; return guild.members.cache.get(userId) || null;
} }
// We don't want to spam the API by trying to fetch unknown members again and again, // We don't want to spam the API by trying to fetch unknown members again and again,
@ -1192,9 +1204,9 @@ export async function resolveMember(bot: Client, guild: Guild, value: string, fr
return null; return null;
} }
const freshMember = await bot.getRESTGuildMember(guild.id, userId).catch(noop); const freshMember = await guild.members.fetch({ user: userId, force: true }).catch(noop);
if (freshMember) { if (freshMember) {
freshMember.id = userId; // freshMember.id = userId; // I dont even know why this is here -Dark
return freshMember; return freshMember;
} }
@ -1222,7 +1234,7 @@ export async function resolveRoleId(bot: Client, guildId: string, value: string)
} }
// Role name // Role name
const roleList = await bot.getRESTGuildRoles(guildId); const roleList = await (await bot.guilds.fetch(guildId)).roles.cache;
const role = roleList.filter(x => x.name.toLocaleLowerCase() === value.toLocaleLowerCase()); const role = roleList.filter(x => x.name.toLocaleLowerCase() === value.toLocaleLowerCase());
if (role[0]) { if (role[0]) {
return role[0].id; return role[0].id;
@ -1236,7 +1248,7 @@ export async function resolveRoleId(bot: Client, guildId: string, value: string)
return null; return null;
} }
const inviteCache = new SimpleCache<Promise<Invite<any> | null>>(10 * MINUTES, 200); const inviteCache = new SimpleCache<Promise<Invite | null>>(10 * MINUTES, 200);
type ResolveInviteReturnType<T extends boolean> = Promise< type ResolveInviteReturnType<T extends boolean> = Promise<
(T extends true ? Invite<"withCount" | "withMetadata"> : Invite<"withMetadata">) | null (T extends true ? Invite<"withCount" | "withMetadata"> : Invite<"withMetadata">) | null
@ -1259,8 +1271,13 @@ export async function resolveInvite<T extends boolean>(
return promise as ResolveInviteReturnType<T>; return promise as ResolveInviteReturnType<T>;
} }
export async function confirm(bot: Client, channel: TextableChannel, userId: string, content: MessageContent) { export async function confirm(
const msg = await channel.createMessage(content); bot: Client,
channel: TextChannel,
userId: string,
content: StringResolvable | MessageOptions,
) {
const msg = await channel.send(content);
const reply = await helpers.waitForReaction(bot, msg, ["✅", "❌"], userId); const reply = await helpers.waitForReaction(bot, msg, ["✅", "❌"], userId);
msg.delete().catch(noop); msg.delete().catch(noop);
return reply && reply.name === "✅"; return reply && reply.name === "✅";
@ -1271,13 +1288,15 @@ export function messageSummary(msg: SavedMessage) {
let result = "```\n" + (msg.data.content ? disableCodeBlocks(msg.data.content) : "<no text content>") + "```"; let result = "```\n" + (msg.data.content ? disableCodeBlocks(msg.data.content) : "<no text content>") + "```";
// Rich embed // Rich embed
const richEmbed = (msg.data.embeds || []).find(e => (e as Embed).type === "rich"); const richEmbed = (msg.data.embeds || []).find(e => (e as MessageEmbed).type === "rich");
if (richEmbed) result += "Embed:```" + disableCodeBlocks(JSON.stringify(richEmbed)) + "```"; if (richEmbed) result += "Embed:```" + disableCodeBlocks(JSON.stringify(richEmbed)) + "```";
// Attachments // Attachments
if (msg.data.attachments) { if (msg.data.attachments) {
result += result +=
"Attachments:\n" + msg.data.attachments.map((a: Attachment) => disableLinkPreviews(a.url)).join("\n") + "\n"; "Attachments:\n" +
msg.data.attachments.map((a: MessageAttachment) => disableLinkPreviews(a.url)).join("\n") +
"\n";
} }
return result; return result;
@ -1300,10 +1319,7 @@ export function verboseUserName(user: User | UnknownUser): string {
} }
export function verboseChannelMention(channel: GuildChannel): string { export function verboseChannelMention(channel: GuildChannel): string {
const plainTextName = const plainTextName = channel.type === "voice" || channel.type === "stage" ? channel.name : `#${channel.name}`;
channel.type === Constants.ChannelTypes.GUILD_VOICE || channel.type === Constants.ChannelTypes.GUILD_STAGE
? channel.name
: `#${channel.name}`;
return `<#${channel.id}> (**${plainTextName}**, \`${channel.id}\`)`; return `<#${channel.id}> (**${plainTextName}**, \`${channel.id}\`)`;
} }
@ -1396,8 +1412,8 @@ export function canUseEmoji(client: Client, emoji: string): boolean {
if (isUnicodeEmoji(emoji)) { if (isUnicodeEmoji(emoji)) {
return true; return true;
} else if (isSnowflake(emoji)) { } else if (isSnowflake(emoji)) {
for (const guild of client.guilds.values()) { for (const guild of client.guilds.cache) {
if (guild.emojis.some(e => (e as any).id === emoji)) { if (guild[1].emojis.cache.some(e => (e as any).id === emoji)) {
return true; return true;
} }
} }
@ -1429,10 +1445,10 @@ export function isGuildInvite<CT extends InviteOpts>(invite: Invite<CT>): invite
} }
export function isGroupDMInvite<CT extends InviteOpts>(invite: Invite<CT>): invite is GroupDMInvite<CT> { export function isGroupDMInvite<CT extends InviteOpts>(invite: Invite<CT>): invite is GroupDMInvite<CT> {
return invite.guild == null && invite.channel?.type === Constants.ChannelTypes.GROUP_DM; return invite.guild == null && invite.channel?.type === "group";
} }
export function inviteHasCounts(invite: Invite<any>): invite is Invite<"withCount"> { export function inviteHasCounts(invite: Invite): invite is Invite<"withCount"> {
return invite.memberCount != null; return invite.memberCount != null;
} }

View file

@ -1,4 +1,4 @@
import { Client, Emoji, MemberPartial, Message, MessageContent, TextableChannel } from "eris"; import { APIMessage, Client, Message, MessageReaction, PartialUser, TextChannel, User } from "discord.js";
import { Awaitable } from "knub/dist/utils"; import { Awaitable } from "knub/dist/utils";
import { MINUTES, noop } from "../utils"; import { MINUTES, noop } from "../utils";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
@ -17,19 +17,19 @@ const defaultOpts: PaginateMessageOpts = {
export async function createPaginatedMessage( export async function createPaginatedMessage(
client: Client, client: Client,
channel: TextableChannel, channel: TextChannel | User,
totalPages: number, totalPages: number,
loadPageFn: LoadPageFn, loadPageFn: LoadPageFn,
opts: Partial<PaginateMessageOpts> = {}, opts: Partial<PaginateMessageOpts> = {},
): Promise<Message> { ): Promise<Message> {
const fullOpts = { ...defaultOpts, ...opts } as PaginateMessageOpts; const fullOpts = { ...defaultOpts, ...opts } as PaginateMessageOpts;
const firstPageContent = await loadPageFn(1); const firstPageContent = await loadPageFn(1);
const message = await channel.createMessage(firstPageContent); const message = await channel.send(firstPageContent);
let page = 1; let page = 1;
let pageLoadId = 0; // Used to avoid race conditions when rapidly switching pages let pageLoadId = 0; // Used to avoid race conditions when rapidly switching pages
const reactionListener = async (reactionMessage: Message, emoji: Emoji, reactor: MemberPartial) => { const reactionListener = async (reactionMessage: MessageReaction, reactor: User | PartialUser) => {
if (reactionMessage.id !== message.id) { if (reactionMessage.message.id !== message.id) {
return; return;
} }
@ -37,14 +37,14 @@ export async function createPaginatedMessage(
return; return;
} }
if (reactor.id === client.user.id) { if (reactor.id === client.user!.id) {
return; return;
} }
let pageDelta = 0; let pageDelta = 0;
if (emoji.name === "⬅️") { if (reactionMessage.emoji.name === "⬅️") {
pageDelta = -1; pageDelta = -1;
} else if (emoji.name === "➡️") { } else if (reactionMessage.emoji.name === "➡️") {
pageDelta = 1; pageDelta = 1;
} }
@ -65,7 +65,7 @@ export async function createPaginatedMessage(
} }
message.edit(newPageContent).catch(noop); message.edit(newPageContent).catch(noop);
message.removeReaction(emoji.name, reactor.id); reactionMessage.users.remove(reactor.id).catch(noop);
refreshTimeout(); refreshTimeout();
}; };
client.on("messageReactionAdd", reactionListener); client.on("messageReactionAdd", reactionListener);
@ -76,7 +76,7 @@ export async function createPaginatedMessage(
const refreshTimeout = () => { const refreshTimeout = () => {
clearTimeout(timeout); clearTimeout(timeout);
timeout = setTimeout(() => { timeout = setTimeout(() => {
message.removeReactions().catch(noop); message.reactions.removeAll().catch(noop);
client.off("messageReactionAdd", reactionListener); client.off("messageReactionAdd", reactionListener);
}, fullOpts.timeout); }, fullOpts.timeout);
}; };
@ -84,8 +84,8 @@ export async function createPaginatedMessage(
refreshTimeout(); refreshTimeout();
// Add reactions // Add reactions
message.addReaction("⬅️").catch(noop); message.react("⬅️").catch(noop);
message.addReaction("➡️").catch(noop); message.react("➡️").catch(noop);
return message; return message;
} }

View file

@ -1,4 +1,4 @@
import { EmbedField } from "eris"; import { EmbedField } from "discord.js";
import { chunkMessageLines, emptyEmbedValue } from "../utils"; import { chunkMessageLines, emptyEmbedValue } from "../utils";
export function getChunkedEmbedFields(name: string, value: string, inline?: boolean): EmbedField[] { export function getChunkedEmbedFields(name: string, value: string, inline?: boolean): EmbedField[] {
@ -10,11 +10,13 @@ export function getChunkedEmbedFields(name: string, value: string, inline?: bool
fields.push({ fields.push({
name, name,
value: chunks[i], value: chunks[i],
inline: false,
}); });
} else { } else {
fields.push({ fields.push({
name: emptyEmbedValue, name: emptyEmbedValue,
value: chunks[i], value: chunks[i],
inline: false,
}); });
} }
} }

View file

@ -1,4 +1,4 @@
import { Constants, GuildChannel, Member, Permission } from "eris"; import { GuildMember, GuildChannel } from "discord.js";
import { getMissingPermissions } from "./getMissingPermissions"; import { getMissingPermissions } from "./getMissingPermissions";
/** /**
@ -6,10 +6,11 @@ import { getMissingPermissions } from "./getMissingPermissions";
* @return Bitmask of missing permissions * @return Bitmask of missing permissions
*/ */
export function getMissingChannelPermissions( export function getMissingChannelPermissions(
member: Member, member: GuildMember,
channel: GuildChannel, channel: GuildChannel,
requiredPermissions: number | bigint, requiredPermissions: number | bigint,
): bigint { ): bigint {
const memberChannelPermissions = channel.permissionsOf(member.id); const memberChannelPermissions = channel.permissionsFor(member.id);
if (!memberChannelPermissions) return BigInt(requiredPermissions);
return getMissingPermissions(memberChannelPermissions, requiredPermissions); return getMissingPermissions(memberChannelPermissions, requiredPermissions);
} }

View file

@ -1,15 +1,18 @@
import { Constants, Permission } from "eris"; import { PermissionOverwrites, Permissions } from "discord.js";
/** /**
* @param resolvedPermissions A Permission object from e.g. GuildChannel#permissionsOf() or Member#permission * @param resolvedPermissions A Permission object from e.g. GuildChannel#permissionsFor() or Member#permission
* @param requiredPermissions Bitmask of required permissions * @param requiredPermissions Bitmask of required permissions
* @return Bitmask of missing permissions * @return Bitmask of missing permissions
*/ */
export function getMissingPermissions(resolvedPermissions: Permission, requiredPermissions: number | bigint): bigint { export function getMissingPermissions(
const allowedPermissions = BigInt(resolvedPermissions.allow); resolvedPermissions: Permissions | Readonly<Permissions>,
requiredPermissions: number | bigint,
): bigint {
const allowedPermissions = BigInt(resolvedPermissions);
const nRequiredPermissions = BigInt(requiredPermissions); const nRequiredPermissions = BigInt(requiredPermissions);
if (Boolean(allowedPermissions & BigInt(Constants.Permissions.administrator))) { if (Boolean(allowedPermissions & BigInt(Permissions.FLAGS.ADMINISTRATOR))) {
return BigInt(0); return BigInt(0);
} }

View file

@ -1,4 +1,4 @@
import { Constants } from "eris"; import { Permissions } from "discord.js";
const camelCaseToTitleCase = str => const camelCaseToTitleCase = str =>
str str
@ -10,9 +10,9 @@ const camelCaseToTitleCase = str =>
const permissionNumberToName: Map<bigint, string> = new Map(); const permissionNumberToName: Map<bigint, string> = new Map();
const ignoredPermissionConstants = ["all", "allGuild", "allText", "allVoice"]; const ignoredPermissionConstants = ["all", "allGuild", "allText", "allVoice"];
for (const key in Constants.Permissions) { for (const key in Permissions.FLAGS) {
if (ignoredPermissionConstants.includes(key)) continue; if (ignoredPermissionConstants.includes(key)) continue;
permissionNumberToName.set(BigInt(Constants.Permissions[key]), camelCaseToTitleCase(key)); permissionNumberToName.set(BigInt(Permissions.FLAGS[key]), camelCaseToTitleCase(key));
} }
/** /**

View file

@ -1,14 +1,14 @@
import { Constants, Permission } from "eris"; import { PermissionOverwrites, Permissions } from "discord.js";
/** /**
* @param resolvedPermissions A Permission object from e.g. GuildChannel#permissionsOf() or Member#permission * @param resolvedPermissions A Permission object from e.g. GuildChannel#permissionsOf() or Member#permission
* @param requiredPermissions Bitmask of required permissions * @param requiredPermissions Bitmask of required permissions
*/ */
export function hasDiscordPermissions(resolvedPermissions: Permission, requiredPermissions: number | bigint) { export function hasDiscordPermissions(resolvedPermissions: PermissionOverwrites, requiredPermissions: number | bigint) {
const allowedPermissions = BigInt(resolvedPermissions.allow); const allowedPermissions = BigInt(resolvedPermissions.allow);
const nRequiredPermissions = BigInt(requiredPermissions); const nRequiredPermissions = BigInt(requiredPermissions);
if (Boolean(allowedPermissions & BigInt(Constants.Permissions.administrator))) { if (Boolean(allowedPermissions & BigInt(Permissions.FLAGS.ADMINISTRATOR))) {
return true; return true;
} }

View file

@ -1,11 +1,11 @@
import { Member, Message, User } from "eris"; import { GuildMember, Message, User } from "discord.js";
import { SavedMessage } from "../data/entities/SavedMessage"; import { SavedMessage } from "../data/entities/SavedMessage";
export function allStarboardsLock() { export function allStarboardsLock() {
return `starboards`; return `starboards`;
} }
export function banLock(user: Member | User | { id: string }) { export function banLock(user: GuildMember | User | { id: string }) {
return `ban-${user.id}`; return `ban-${user.id}`;
} }
@ -13,7 +13,7 @@ export function counterIdLock(counterId: number | string) {
return `counter-${counterId}`; return `counter-${counterId}`;
} }
export function memberRolesLock(member: Member | User | { id: string }) { export function memberRolesLock(member: GuildMember | User | { id: string }) {
return `member-roles-${member.id}`; return `member-roles-${member.id}`;
} }
@ -21,6 +21,6 @@ export function messageLock(message: Message | SavedMessage | { id: string }) {
return `message-${message.id}`; return `message-${message.id}`;
} }
export function muteLock(user: Member | User | { id: string }) { export function muteLock(user: GuildMember | User | { id: string }) {
return `mute-${user.id}`; return `mute-${user.id}`;
} }

View file

@ -1,4 +1,4 @@
import { MessageContent } from "eris"; import { APIMessage, MessageOptions } from "discord.js";
function embedHasContent(embed: any) { function embedHasContent(embed: any) {
for (const [key, value] of Object.entries(embed)) { for (const [key, value] of Object.entries(embed)) {
@ -18,7 +18,7 @@ function embedHasContent(embed: any) {
return false; return false;
} }
export function messageHasContent(content: MessageContent): boolean { export function messageHasContent(content: string | MessageOptions): boolean {
if (typeof content === "string") { if (typeof content === "string") {
return content.trim() !== ""; return content.trim() !== "";
} }

View file

@ -1,6 +1,6 @@
import { MessageContent } from "eris"; import { MessageOptions } from "discord.js";
import { messageHasContent } from "./messageHasContent"; import { messageHasContent } from "./messageHasContent";
export function messageIsEmpty(content: MessageContent): boolean { export function messageIsEmpty(content: string | MessageOptions): boolean {
return !messageHasContent(content); return !messageHasContent(content);
} }

View file

@ -1,9 +1,9 @@
import { Constants } from "eris"; import { Permissions } from "discord.js";
/** /**
* Bitmask of permissions required to read messages in a channel * Bitmask of permissions required to read messages in a channel
*/ */
export const readChannelPermissions = Constants.Permissions.readMessages | Constants.Permissions.readMessageHistory; export const readChannelPermissions = Permissions.FLAGS.VIEW_CHANNEL | Permissions.FLAGS.READ_MESSAGE_HISTORY;
/** /**
* Bitmask of permissions required to read messages in a channel (bigint) * Bitmask of permissions required to read messages in a channel (bigint)

View file

@ -1,7 +1,7 @@
import { disableInlineCode, isSnowflake } from "../utils"; import { disableInlineCode, isSnowflake } from "../utils";
import { getChannelIdFromMessageId } from "../data/getChannelIdFromMessageId"; import { getChannelIdFromMessageId } from "../data/getChannelIdFromMessageId";
import { GuildPluginData, TypeConversionError } from "knub"; import { GuildPluginData, TypeConversionError } from "knub";
import { TextChannel } from "eris"; import { TextChannel } from "discord.js";
const channelAndMessageIdRegex = /^(\d+)[\-\/](\d+)$/; const channelAndMessageIdRegex = /^(\d+)[\-\/](\d+)$/;
const messageLinkRegex = /^https:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/\d+\/(\d+)\/(\d+)$/i; const messageLinkRegex = /^https:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/\d+\/(\d+)\/(\d+)$/i;
@ -46,7 +46,7 @@ export async function resolveMessageTarget(pluginData: GuildPluginData<any>, val
return null; return null;
} }
const channel = pluginData.guild.channels.get(result.channelId); const channel = pluginData.guild.channels.resolve(result.channelId);
if (!channel || !(channel instanceof TextChannel)) { if (!channel || !(channel instanceof TextChannel)) {
return null; return null;
} }

View file

@ -1,7 +1,7 @@
import { MessageContent, MessageFile, User } from "eris";
import { createChunkedMessage, HOURS, isDiscordRESTError } from "../utils"; import { createChunkedMessage, HOURS, isDiscordRESTError } from "../utils";
import { logger } from "../logger"; import { logger } from "../logger";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
import { APIMessage, User } from "discord.js";
let dmsDisabled = false; let dmsDisabled = false;
let dmsDisabledTimeout: Timeout; let dmsDisabledTimeout: Timeout;
@ -16,7 +16,7 @@ export class DMError extends Error {}
const error20026 = "The bot cannot currently send DMs"; const error20026 = "The bot cannot currently send DMs";
export async function sendDM(user: User, content: MessageContent, source: string) { export async function sendDM(user: User, content: string | APIMessage, source: string) {
if (dmsDisabled) { if (dmsDisabled) {
throw new DMError(error20026); throw new DMError(error20026);
} }
@ -24,15 +24,10 @@ export async function sendDM(user: User, content: MessageContent, source: string
logger.debug(`Sending ${source} DM to ${user.id}`); logger.debug(`Sending ${source} DM to ${user.id}`);
try { try {
const dmChannel = await user.getDMChannel();
if (!dmChannel) {
throw new DMError("Unable to open DM channel");
}
if (typeof content === "string") { if (typeof content === "string") {
await createChunkedMessage(dmChannel, content); await createChunkedMessage(user, content);
} else { } else {
await dmChannel.createMessage(content); await user.send(content);
} }
} catch (e) { } catch (e) {
if (isDiscordRESTError(e) && e.code === 20026) { if (isDiscordRESTError(e) && e.code === 20026) {