From 2a83d0962dfaadfee5135ca4ea872d3169a14d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Bl=C3=B6meke?= Date: Sat, 27 Jul 2019 22:29:29 +0200 Subject: [PATCH] Fully functioning and loaded from the config --- src/data/GuildStarboardMessages.ts | 54 +++ src/data/GuildStarboards.ts | 84 ----- src/data/entities/Starboard.ts | 23 -- src/data/entities/StarboardMessage.ts | 15 +- .../1564150449297-moveStarboardsToConfig.ts | 103 ++++++ src/plugins/Starboard.ts | 331 +++++++----------- 6 files changed, 293 insertions(+), 317 deletions(-) create mode 100644 src/data/GuildStarboardMessages.ts delete mode 100644 src/data/GuildStarboards.ts delete mode 100644 src/data/entities/Starboard.ts create mode 100644 src/migrations/1564150449297-moveStarboardsToConfig.ts diff --git a/src/data/GuildStarboardMessages.ts b/src/data/GuildStarboardMessages.ts new file mode 100644 index 00000000..c3b4efa1 --- /dev/null +++ b/src/data/GuildStarboardMessages.ts @@ -0,0 +1,54 @@ +import { BaseGuildRepository } from "./BaseGuildRepository"; +import { getRepository, Repository } from "typeorm"; +import { StarboardMessage } from "./entities/StarboardMessage"; + +export class GuildStarboardMessages extends BaseGuildRepository { + private allStarboardMessages: Repository; + + constructor(guildId) { + super(guildId); + this.allStarboardMessages = getRepository(StarboardMessage); + } + + async getStarboardMessagesForMessageId(messageId: string) { + return this.allStarboardMessages + .createQueryBuilder() + .where("guild_id = :gid", { gid: this.guildId }) + .andWhere("message_id = :msgid", { msgid: messageId }) + .getMany(); + } + + async getStarboardMessagesForStarboardMessageId(starboardMessageId: string) { + return this.allStarboardMessages + .createQueryBuilder() + .where("guild_id = :gid", { gid: this.guildId }) + .andWhere("starboard_message_id = :messageId", { messageId: starboardMessageId }) + .getMany(); + } + + async getMessagesForStarboardIdAndSourceMessageId(starboardId: string, sourceMessageId: string) { + return await this.allStarboardMessages + .createQueryBuilder() + .where("guild_id = :gid", { gid: this.guildId }) + .andWhere("message_id = :msgId", { msgId: sourceMessageId }) + .andWhere("starboard_channel_id = :sbId", { sbId: starboardId }) + .getMany(); + } + + async createStarboardMessage(starboardId: string, messageId: string, starboardMessageId: string) { + await this.allStarboardMessages.insert({ + message_id: messageId, + starboard_message_id: starboardMessageId, + starboard_channel_id: starboardId, + guild_id: this.guildId, + }); + } + + async deleteStarboardMessage(starboardMessageId: string, starboardChannelId: string) { + await this.allStarboardMessages.delete({ + guild_id: this.guildId, + starboard_message_id: starboardMessageId, + starboard_channel_id: starboardChannelId, + }); + } +} diff --git a/src/data/GuildStarboards.ts b/src/data/GuildStarboards.ts deleted file mode 100644 index b5f86e08..00000000 --- a/src/data/GuildStarboards.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; -import { getRepository, Repository } from "typeorm"; -import { Starboard } from "./entities/Starboard"; -import { StarboardMessage } from "./entities/StarboardMessage"; - -export class GuildStarboards extends BaseGuildRepository { - private starboards: Repository; - private starboardMessages: Repository; - - constructor(guildId) { - super(guildId); - this.starboards = getRepository(Starboard); - this.starboardMessages = getRepository(StarboardMessage); - } - - getStarboardByChannelId(channelId): Promise { - return this.starboards.findOne({ - where: { - guild_id: this.guildId, - channel_id: channelId, - }, - }); - } - - getStarboardsByEmoji(emoji): Promise { - return this.starboards.find({ - where: { - guild_id: this.guildId, - emoji, - }, - }); - } - - getStarboardMessageByStarboardIdAndMessageId(starboardId, messageId): Promise { - return this.starboardMessages.findOne({ - relations: this.getRelations(), - where: { - starboard_id: starboardId, - message_id: messageId, - }, - }); - } - - getStarboardMessagesByMessageId(id): Promise { - return this.starboardMessages.find({ - relations: this.getRelations(), - where: { - message_id: id, - }, - }); - } - - async createStarboardMessage(starboardId, messageId, starboardMessageId): Promise { - await this.starboardMessages.insert({ - starboard_id: starboardId, - message_id: messageId, - starboard_message_id: starboardMessageId, - }); - } - - async deleteStarboardMessage(starboardId, messageId): Promise { - await this.starboardMessages.delete({ - starboard_id: starboardId, - message_id: messageId, - }); - } - - async create(channelId: string, channelWhitelist: string[], emoji: string, reactionsRequired: number): Promise { - await this.starboards.insert({ - guild_id: this.guildId, - channel_id: channelId, - channel_whitelist: channelWhitelist ? channelWhitelist.join(",") : null, - emoji, - reactions_required: reactionsRequired, - }); - } - - async delete(channelId: string): Promise { - await this.starboards.delete({ - guild_id: this.guildId, - channel_id: channelId, - }); - } -} diff --git a/src/data/entities/Starboard.ts b/src/data/entities/Starboard.ts deleted file mode 100644 index af170f48..00000000 --- a/src/data/entities/Starboard.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Entity, Column, PrimaryColumn, OneToMany } from "typeorm"; -import { CaseNote } from "./CaseNote"; -import { StarboardMessage } from "./StarboardMessage"; - -@Entity("starboards") -export class Starboard { - @Column() - @PrimaryColumn() - id: number; - - @Column() guild_id: string; - - @Column() channel_id: string; - - @Column() channel_whitelist: string; - - @Column() emoji: string; - - @Column() reactions_required: number; - - @OneToMany(type => StarboardMessage, msg => msg.starboard) - starboardMessages: StarboardMessage[]; -} diff --git a/src/data/entities/StarboardMessage.ts b/src/data/entities/StarboardMessage.ts index 0b1b37a9..50c9c241 100644 --- a/src/data/entities/StarboardMessage.ts +++ b/src/data/entities/StarboardMessage.ts @@ -1,23 +1,20 @@ import { Entity, Column, PrimaryColumn, OneToMany, ManyToOne, JoinColumn, OneToOne } from "typeorm"; -import { Starboard } from "./Starboard"; -import { Case } from "./Case"; import { SavedMessage } from "./SavedMessage"; @Entity("starboard_messages") export class StarboardMessage { @Column() - @PrimaryColumn() - starboard_id: number; + message_id: string; @Column() @PrimaryColumn() - message_id: string; + starboard_message_id: string; - @Column() starboard_message_id: string; + @Column() + starboard_channel_id: string; - @ManyToOne(type => Starboard, sb => sb.starboardMessages) - @JoinColumn({ name: "starboard_id" }) - starboard: Starboard; + @Column() + guild_id: string; @OneToOne(type => SavedMessage) @JoinColumn({ name: "message_id" }) diff --git a/src/migrations/1564150449297-moveStarboardsToConfig.ts b/src/migrations/1564150449297-moveStarboardsToConfig.ts new file mode 100644 index 00000000..2ebe6254 --- /dev/null +++ b/src/migrations/1564150449297-moveStarboardsToConfig.ts @@ -0,0 +1,103 @@ +import { MigrationInterface, QueryRunner, Table, TableColumn, createQueryBuilder } from "typeorm"; + +export class moveStarboardsToConfig1564150449297 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // Create the new column for the channels id + const chanid_column = new TableColumn({ + name: "starboard_channel_id", + type: "bigint", + unsigned: true, + }); + await queryRunner.addColumn("starboard_messages", chanid_column); + + // Since we are removing the guild_id with the starboards table, we might want it here + const guid_column = new TableColumn({ + name: "guild_id", + type: "bigint", + unsigned: true, + }); + await queryRunner.addColumn("starboard_messages", guid_column); + + // Migrate the old starboard_id to the new starboard_channel_id + await queryRunner.query(` + UPDATE starboard_messages AS sm + JOIN starboards AS sb + ON sm.starboard_id = sb.id + SET sm.starboard_channel_id = sb.channel_id, sm.guild_id = sb.guild_id; + `); + + // Drop the starboard_id column as it is now obsolete + await queryRunner.dropColumn("starboard_messages", "starboard_id"); + // Set new Primary Key + await queryRunner.dropPrimaryKey("starboard_messages"); + await queryRunner.createPrimaryKey("starboard_messages", ["starboard_message_id"]); + // Finally, drop the starboards channel as it is now obsolete + await queryRunner.dropTable("starboards", true); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn("starboard_messages", "starboard_channel_id"); + await queryRunner.dropColumn("starboard_messages", "guild_id"); + + const sbId = new TableColumn({ + name: "starboard_id", + type: "int", + unsigned: true, + }); + await queryRunner.addColumn("starboard_messages", sbId); + + await queryRunner.dropPrimaryKey("starboard_messages"); + await queryRunner.createPrimaryKey("starboard_messages", ["starboard_id", "message_id"]); + + await queryRunner.createTable( + new Table({ + name: "starboards", + columns: [ + { + name: "id", + type: "int", + unsigned: true, + isGenerated: true, + generationStrategy: "increment", + isPrimary: true, + }, + { + name: "guild_id", + type: "bigint", + unsigned: true, + }, + { + name: "channel_id", + type: "bigint", + unsigned: true, + }, + { + name: "channel_whitelist", + type: "text", + isNullable: true, + default: null, + }, + { + name: "emoji", + type: "varchar", + length: "64", + }, + { + name: "reactions_required", + type: "smallint", + unsigned: true, + }, + ], + indices: [ + { + columnNames: ["guild_id", "emoji"], + }, + { + columnNames: ["guild_id", "channel_id"], + isUnique: true, + }, + ], + }), + ); + } +} diff --git a/src/plugins/Starboard.ts b/src/plugins/Starboard.ts index 2556acc3..91846826 100644 --- a/src/plugins/Starboard.ts +++ b/src/plugins/Starboard.ts @@ -1,35 +1,49 @@ -import { decorators as d, waitForReply, utils as knubUtils, IBasePluginConfig, IPluginOptions } from "knub"; +import { decorators as d, IPluginOptions } from "knub"; import { ZeppelinPlugin } from "./ZeppelinPlugin"; -import { GuildStarboards } from "../data/GuildStarboards"; import { GuildChannel, Message, TextChannel } from "eris"; -import { - customEmojiRegex, - errorMessage, - getEmojiInString, - getUrlsInString, - noop, - snowflakeRegex, - successMessage, -} from "../utils"; -import { Starboard } from "../data/entities/Starboard"; +import { errorMessage, getUrlsInString, noop, successMessage, tNullable } from "../utils"; import path from "path"; import moment from "moment-timezone"; import { GuildSavedMessages } from "../data/GuildSavedMessages"; import { SavedMessage } from "../data/entities/SavedMessage"; import * as t from "io-ts"; +import { GuildStarboardMessages } from "../data/GuildStarboardMessages"; +import { StarboardMessage } from "src/data/entities/StarboardMessage"; + +const StarboardOpts = t.type({ + source_channel_ids: t.array(t.string), + starboard_channel_id: t.string, + positive_emojis: tNullable(t.array(t.string)), + positive_required: tNullable(t.number), + allow_multistar: tNullable(t.boolean), + negative_emojis: tNullable(t.array(t.string)), + bot_reacts: tNullable(t.boolean), + enabled: tNullable(t.boolean), +}); +type TStarboardOpts = t.TypeOf; const ConfigSchema = t.type({ + entries: t.record(t.string, StarboardOpts), + can_manage: t.boolean, }); type TConfigSchema = t.TypeOf; +const defaultStarboardOpts: Partial = { + positive_emojis: ["⭐"], + positive_required: 5, + allow_multistar: false, + negative_emojis: [], + enabled: true, +}; + export class StarboardPlugin extends ZeppelinPlugin { public static pluginName = "starboard"; public static showInDocs = false; public static configSchema = ConfigSchema; - protected starboards: GuildStarboards; protected savedMessages: GuildSavedMessages; + protected starboardMessages: GuildStarboardMessages; private onMessageDeleteFn; @@ -37,6 +51,7 @@ export class StarboardPlugin extends ZeppelinPlugin { return { config: { can_manage: false, + entries: {}, }, overrides: [ @@ -50,9 +65,23 @@ export class StarboardPlugin extends ZeppelinPlugin { }; } + protected getStarboardOptsForSourceChannelId(sourceChannel): TStarboardOpts[] { + const config = this.getConfigForChannel(sourceChannel); + return Object.values(config.entries) + .filter(opts => opts.source_channel_ids.includes(sourceChannel.id)) + .map(opts => Object.assign({}, defaultStarboardOpts, opts)); + } + + protected getStarboardOptsForStarboardChannelId(starboardChannel): TStarboardOpts[] { + const config = this.getConfigForChannel(starboardChannel); + return Object.values(config.entries) + .filter(opts => opts.starboard_channel_id === starboardChannel.id) + .map(opts => Object.assign({}, defaultStarboardOpts, opts)); + } + onLoad() { - this.starboards = GuildStarboards.getGuildInstance(this.guildId); this.savedMessages = GuildSavedMessages.getGuildInstance(this.guildId); + this.starboardMessages = GuildStarboardMessages.getGuildInstance(this.guildId); this.onMessageDeleteFn = this.onMessageDelete.bind(this); this.savedMessages.events.on("delete", this.onMessageDeleteFn); @@ -62,136 +91,6 @@ export class StarboardPlugin extends ZeppelinPlugin { this.savedMessages.events.off("delete", this.onMessageDeleteFn); } - /** - * An interactive setup for creating a starboard - */ - @d.command("starboard create") - @d.permission("can_manage") - async setupCmd(msg: Message) { - const cancelMsg = () => msg.channel.createMessage("Cancelled"); - - msg.channel.createMessage( - `⭐ Let's make a starboard! What channel should we use as the board? ("cancel" to cancel)`, - ); - - let starboardChannel; - do { - const reply = await waitForReply(this.bot, msg.channel as TextChannel, msg.author.id, 60000); - if (reply.content == null || reply.content === "cancel") return cancelMsg(); - - starboardChannel = knubUtils.resolveChannel(this.guild, reply.content || ""); - if (!starboardChannel) { - msg.channel.createMessage("Invalid channel. Try again?"); - continue; - } - - const existingStarboard = await this.starboards.getStarboardByChannelId(starboardChannel.id); - if (existingStarboard) { - msg.channel.createMessage("That channel already has a starboard. Try again?"); - starboardChannel = null; - continue; - } - } while (starboardChannel == null); - - msg.channel.createMessage(`Ok. Which emoji should we use as the trigger? ("cancel" to cancel)`); - - let emoji; - do { - const reply = await waitForReply(this.bot, msg.channel as TextChannel, msg.author.id); - if (reply.content == null || reply.content === "cancel") return cancelMsg(); - - const allEmojis = getEmojiInString(reply.content || ""); - if (!allEmojis.length) { - msg.channel.createMessage("Invalid emoji. Try again?"); - continue; - } - - emoji = allEmojis[0]; - - const customEmojiMatch = emoji.match(customEmojiRegex); - if (customEmojiMatch) { - // <:name:id> to name:id, as Eris puts them in the message reactions object - emoji = `${customEmojiMatch[1]}:${customEmojiMatch[2]}`; - } - } while (emoji == null); - - msg.channel.createMessage( - `And how many reactions are required to immortalize a message in the starboard? ("cancel" to cancel)`, - ); - - let requiredReactions; - do { - const reply = await waitForReply(this.bot, msg.channel as TextChannel, msg.author.id); - if (reply.content == null || reply.content === "cancel") return cancelMsg(); - - requiredReactions = parseInt(reply.content || "", 10); - - if (Number.isNaN(requiredReactions)) { - msg.channel.createMessage("Invalid number. Try again?"); - continue; - } - - if (typeof requiredReactions === "number") { - if (requiredReactions <= 0) { - msg.channel.createMessage("The number must be higher than 0. Try again?"); - continue; - } else if (requiredReactions > 65536) { - msg.channel.createMessage("The number must be smaller than 65536. Try again?"); - continue; - } - } - } while (requiredReactions == null); - - msg.channel.createMessage( - `And finally, which channels can messages be starred in? "All" for any channel. ("cancel" to cancel)`, - ); - - let channelWhitelist; - do { - const reply = await waitForReply(this.bot, msg.channel as TextChannel, msg.author.id); - if (reply.content == null || reply.content === "cancel") return cancelMsg(); - - if (reply.content.toLowerCase() === "all") { - channelWhitelist = null; - break; - } - - channelWhitelist = reply.content.match(new RegExp(snowflakeRegex, "g")); - - let hasInvalidChannels = false; - for (const id of channelWhitelist) { - const channel = this.guild.channels.get(id); - if (!channel || !(channel instanceof TextChannel)) { - msg.channel.createMessage(`Couldn't recognize channel <#${id}> (\`${id}\`). Try again?`); - hasInvalidChannels = true; - break; - } - } - if (hasInvalidChannels) continue; - } while (channelWhitelist == null); - - await this.starboards.create(starboardChannel.id, channelWhitelist, emoji, requiredReactions); - - msg.channel.createMessage(successMessage("Starboard created!")); - } - - /** - * Deletes the starboard from the specified channel. The already-posted starboard messages are retained. - */ - @d.command("starboard delete", "") - @d.permission("can_manage") - async deleteCmd(msg: Message, args: { channelId: string }) { - const starboard = await this.starboards.getStarboardByChannelId(args.channelId); - if (!starboard) { - msg.channel.createMessage(errorMessage(`Channel <#${args.channelId}> doesn't have a starboard!`)); - return; - } - - await this.starboards.delete(starboard.channel_id); - - msg.channel.createMessage(successMessage(`Starboard deleted from <#${args.channelId}>!`)); - } - /** * When a reaction is added to a message, check if there are any applicable starboards and if the reactions reach * the required threshold. If they do, post the message in the starboard channel. @@ -209,52 +108,65 @@ export class StarboardPlugin extends ZeppelinPlugin { } } - const emojiStr = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name; - const applicableStarboards = await this.starboards.getStarboardsByEmoji(emojiStr); + const applicableStarboards = await this.getStarboardOptsForSourceChannelId(msg.channel); for (const starboard of applicableStarboards) { + // Instantly continue if the starboard is disabled + if (!starboard.enabled) continue; // Can't star messages in the starboard channel itself - if (msg.channel.id === starboard.channel_id) continue; - - if (starboard.channel_whitelist) { - const allowedChannelIds = starboard.channel_whitelist.split(","); - if (!allowedChannelIds.includes(msg.channel.id)) continue; - } - + if (msg.channel.id === starboard.starboard_channel_id) continue; // If the message has already been posted to this starboard, we don't need to do anything else here - const existingSavedMessage = await this.starboards.getStarboardMessageByStarboardIdAndMessageId( - starboard.id, + const starboardMessages = await this.starboardMessages.getMessagesForStarboardIdAndSourceMessageId( + starboard.starboard_channel_id, msg.id, ); - if (existingSavedMessage) return; + if (starboardMessages.length > 0) continue; - const reactionsCount = await this.countReactions(msg, emojiStr); - - if (reactionsCount >= starboard.reactions_required) { - await this.saveMessageToStarboard(msg, starboard); + const reactionsCount = await this.countReactions(msg, starboard.positive_emojis, starboard.allow_multistar); + if (reactionsCount >= starboard.positive_required) { + await this.saveMessageToStarboard(msg, starboard.starboard_channel_id); } } } /** - * Counts the specific reactions in the message, ignoring the message author + * Tallys the reaction count of ALL reactions in the array */ - async countReactions(msg: Message, reaction) { - let reactionsCount = (msg.reactions[reaction] && msg.reactions[reaction].count) || 0; + async countReactions(msg: Message, counted: string[], countDouble: boolean) { + let totalCount = []; + countDouble = countDouble || false; - // Ignore self-stars + for (const emoji of counted) { + totalCount = await this.countReactionsForEmoji(msg, emoji, totalCount, countDouble); + } + + return totalCount.length; + } + + /** + * Counts the emoji specific reactions in the message, ignoring the message author and the bot + */ + async countReactionsForEmoji(msg: Message, reaction, usersAlreadyCounted: string[], countDouble: boolean) { + countDouble = countDouble || false; + + // Ignore self-stars, bot-stars and multi-stars const reactors = await msg.getReaction(reaction); - if (reactors.some(u => u.id === msg.author.id)) reactionsCount--; + for (const user of reactors) { + if (user.id === msg.author.id) continue; + if (user.id === this.bot.user.id) continue; + if (!countDouble && usersAlreadyCounted.includes(user.id)) continue; + usersAlreadyCounted.push(user.id); + } - return reactionsCount; + return usersAlreadyCounted; } /** * Saves/posts a message to the specified starboard. The message is posted as an embed and image attachments are * included as the embed image. */ - async saveMessageToStarboard(msg: Message, starboard: Starboard) { - const channel = this.guild.channels.get(starboard.channel_id); + async saveMessageToStarboard(msg: Message, starboardChannelId: string) { + const channel = this.guild.channels.get(starboardChannelId); if (!channel) return; const time = moment(msg.timestamp, "x").format("YYYY-MM-DD [at] HH:mm:ss [UTC]"); @@ -308,18 +220,18 @@ export class StarboardPlugin extends ZeppelinPlugin { content: `https://discordapp.com/channels/${this.guildId}/${msg.channel.id}/${msg.id}`, embed, }); - await this.starboards.createStarboardMessage(starboard.id, msg.id, starboardMessage.id); + await this.starboardMessages.createStarboardMessage(channel.id, msg.id, starboardMessage.id); } /** * Remove a message from the specified starboard */ - async removeMessageFromStarboard(msgId: string, starboard: Starboard) { - const starboardMessage = await this.starboards.getStarboardMessageByStarboardIdAndMessageId(starboard.id, msgId); - if (!starboardMessage) return; + async removeMessageFromStarboard(msg: StarboardMessage) { + await this.bot.deleteMessage(msg.starboard_channel_id, msg.starboard_message_id).catch(noop); + } - await this.bot.deleteMessage(starboard.channel_id, starboardMessage.starboard_message_id).catch(noop); - await this.starboards.deleteStarboardMessage(starboard.id, msgId); + async removeMessageFromStarboardMessages(starboard_message_id: string, starboard_channel_id: string) { + await this.starboardMessages.deleteStarboardMessage(starboard_message_id, starboard_channel_id); } /** @@ -328,44 +240,61 @@ export class StarboardPlugin extends ZeppelinPlugin { * TODO: When a message is removed from the starboard itself, i.e. the bot's embed is removed, also remove that message from the starboard_messages database table */ async onMessageDelete(msg: SavedMessage) { - const starboardMessages = await this.starboards.with("starboard").getStarboardMessagesByMessageId(msg.id); - if (!starboardMessages.length) return; + let messages = await this.starboardMessages.getStarboardMessagesForMessageId(msg.id); + if (messages.length > 0) { + for (const starboardMessage of messages) { + if (!starboardMessage.starboard_message_id) continue; + this.removeMessageFromStarboard(starboardMessage); + } + } else { + messages = await this.starboardMessages.getStarboardMessagesForStarboardMessageId(msg.id); + if (messages.length === 0) return; - for (const starboardMessage of starboardMessages) { - if (!starboardMessage.starboard) continue; - this.removeMessageFromStarboard(starboardMessage.message_id, starboardMessage.starboard); + for (const starboardMessage of messages) { + if (!starboardMessage.starboard_channel_id) continue; + this.removeMessageFromStarboardMessages( + starboardMessage.starboard_message_id, + starboardMessage.starboard_channel_id, + ); + } } } @d.command("starboard migrate_pins", " ") async migratePinsCmd(msg: Message, args: { pinChannelId: string; starboardChannelId }) { - const starboard = await this.starboards.getStarboardByChannelId(args.starboardChannelId); - if (!starboard) { - msg.channel.createMessage(errorMessage("The specified channel doesn't have a starboard!")); - return; - } + try { + const starboards = await this.getStarboardOptsForStarboardChannelId(this.bot.getChannel(args.starboardChannelId)); + if (!starboards) { + msg.channel.createMessage(errorMessage("The specified channel doesn't have a starboard!")); + return; + } - const channel = (await this.guild.channels.get(args.pinChannelId)) as GuildChannel & TextChannel; - if (!channel) { - msg.channel.createMessage(errorMessage("Could not find the specified channel to migrate pins from!")); - return; - } + const channel = (await this.guild.channels.get(args.pinChannelId)) as GuildChannel & TextChannel; + if (!channel) { + msg.channel.createMessage(errorMessage("Could not find the specified channel to migrate pins from!")); + return; + } - msg.channel.createMessage(`Migrating pins from <#${channel.id}> to <#${args.starboardChannelId}>...`); + msg.channel.createMessage(`Migrating pins from <#${channel.id}> to <#${args.starboardChannelId}>...`); - const pins = await channel.getPins(); - pins.reverse(); // Migrate pins starting from the oldest message + const pins = await channel.getPins(); + pins.reverse(); // Migrate pins starting from the oldest message - for (const pin of pins) { - const existingStarboardMessage = await this.starboards.getStarboardMessageByStarboardIdAndMessageId( - starboard.id, - pin.id, + for (const pin of pins) { + const existingStarboardMessage = await this.starboardMessages.getMessagesForStarboardIdAndSourceMessageId( + args.starboardChannelId, + pin.id, + ); + if (existingStarboardMessage.length > 0) continue; + await this.saveMessageToStarboard(pin, args.starboardChannelId); + } + + msg.channel.createMessage(successMessage("Pins migrated!")); + } catch (error) { + this.sendErrorMessage( + msg.channel, + "Sorry, but something went wrong!\nSyntax: `starboard migrate_pins `", ); - if (existingStarboardMessage) continue; - - await this.saveMessageToStarboard(pin, starboard); } - - msg.channel.createMessage(successMessage("Pins migrated!")); } }