3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-15 05:41:51 +00:00

Merge branch 'DarkView-newStarboard'

This commit is contained in:
Dragory 2019-11-27 20:47:53 +02:00
commit f0f4f1bd6c
8 changed files with 463 additions and 243 deletions

View file

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

View file

@ -0,0 +1,43 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { Repository, getRepository } from "typeorm";
import { StarboardReaction } from "./entities/StarboardReaction";
export class GuildStarboardReactions extends BaseGuildRepository {
private allStarboardReactions: Repository<StarboardReaction>;
constructor(guildId) {
super(guildId);
this.allStarboardReactions = getRepository(StarboardReaction);
}
async getAllReactionsForMessageId(messageId: string) {
return this.allStarboardReactions
.createQueryBuilder()
.where("guild_id = :gid", { gid: this.guildId })
.andWhere("message_id = :msgid", { msgid: messageId })
.getMany();
}
async createStarboardReaction(messageId: string, reactorId: string) {
await this.allStarboardReactions.insert({
message_id: messageId,
reactor_id: reactorId,
guild_id: this.guildId,
});
}
async deleteAllStarboardReactionsForMessageId(messageId: string) {
await this.allStarboardReactions.delete({
guild_id: this.guildId,
message_id: messageId,
});
}
async deleteStarboardReaction(messageId: string, reactorId: string) {
await this.allStarboardReactions.delete({
guild_id: this.guildId,
reactor_id: reactorId,
message_id: messageId,
});
}
}

View file

@ -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[];
}

View file

@ -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" })

View file

@ -0,0 +1,22 @@
import { Entity, Column, PrimaryColumn, JoinColumn, OneToOne } from "typeorm";
import { SavedMessage } from "./SavedMessage";
@Entity("starboard_reactions")
export class StarboardReaction {
@Column()
@PrimaryColumn()
id: string;
@Column()
guild_id: string;
@Column()
message_id: string;
@Column()
reactor_id: string;
@OneToOne(type => SavedMessage)
@JoinColumn({ name: "message_id" })
message: SavedMessage;
}

View file

@ -0,0 +1,103 @@
import { MigrationInterface, QueryRunner, Table, TableColumn, createQueryBuilder } from "typeorm";
export class MoveStarboardsToConfig1573248462469 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
// 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<any> {
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,
},
],
}),
);
}
}

View file

@ -0,0 +1,44 @@
import { MigrationInterface, QueryRunner, Table } from "typeorm";
export class CreateStarboardReactionsTable1573248794313 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.createTable(
new Table({
name: "starboard_reactions",
columns: [
{
name: "id",
type: "int",
isGenerated: true,
generationStrategy: "increment",
isPrimary: true,
},
{
name: "guild_id",
type: "bigint",
unsigned: true,
},
{
name: "message_id",
type: "bigint",
unsigned: true,
},
{
name: "reactor_id",
type: "bigint",
unsigned: true,
},
],
indices: [
{
columnNames: ["reactor_id", "message_id"],
},
],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.dropTable("starboard_reactions", true, false, true);
}
}

View file

@ -1,58 +1,147 @@
import { decorators as d, waitForReply, utils as knubUtils, IBasePluginConfig, IPluginOptions } from "knub";
import { ZeppelinPlugin } from "./ZeppelinPlugin";
import { GuildStarboards } from "../data/GuildStarboards";
import { decorators as d, IPluginOptions } from "knub";
import { ZeppelinPlugin, trimPluginDescription } from "./ZeppelinPlugin";
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 "../data/entities/StarboardMessage";
import { GuildStarboardReactions } from "../data/GuildStarboardReactions";
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),
enabled: tNullable(t.boolean),
});
type TStarboardOpts = t.TypeOf<typeof StarboardOpts>;
const ConfigSchema = t.type({
can_manage: t.boolean,
entries: t.record(t.string, StarboardOpts),
can_migrate: t.boolean,
});
type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
const defaultStarboardOpts: Partial<TStarboardOpts> = {
positive_emojis: ["⭐"],
positive_required: 5,
enabled: true,
};
export class StarboardPlugin extends ZeppelinPlugin<TConfigSchema> {
public static pluginName = "starboard";
public static showInDocs = false;
public static configSchema = ConfigSchema;
protected starboards: GuildStarboards;
public static pluginInfo = {
prettyName: "Starboards",
description: trimPluginDescription(`
This plugin contains all functionality needed to use discord channels as starboards.
`),
configurationGuide: trimPluginDescription(`
You can customize multiple settings for starboards.
Any emoji that you want available needs to be put into the config in its raw form.
To obtain a raw form of an emoji, please write out the emoji and put a backslash in front of it.
Example with default emoji: "\:star:" => "⭐"
Example with custom emoji: "\:mrvnSmile:" => "<:mrvnSmile:543000534102310933>"
Now, past the result into the config, but make sure to exclude all less-than and greater-than signs like in the second example.
### Starboard with one source channel
All messages in the source channel that get enough positive reactions will be posted into the starboard channel.
The only positive reaction counted here is the default emoji "⭐".
Only users with a role matching the allowed_roles role-id will be counted.
~~~yml
starboard:
config:
entries:
exampleOne:
source_channel_ids: ["604342623569707010"]
starboard_channel_id: "604342689038729226"
positive_emojis: ["⭐"]
positive_required: 5
allowed_roles: ["556110793058287637"]
enabled: true
~~~
### Starboard with two sources and two emoji
All messages in any of the source channels that get enough positive reactions will be posted into the starboard channel.
Both the default emoji "⭐" and the custom emoji ":mrvnSmile:543000534102310933" are counted.
~~~yml
starboard:
config:
entries:
exampleTwo:
source_channel_ids: ["604342623569707010", "604342649251561487"]
starboard_channel_id: "604342689038729226"
positive_emojis: ["⭐", ":mrvnSmile:543000534102310933"]
positive_required: 10
enabled: true
~~~
`),
};
protected savedMessages: GuildSavedMessages;
protected starboardMessages: GuildStarboardMessages;
protected starboardReactions: GuildStarboardReactions;
private onMessageDeleteFn;
public static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_manage: false,
can_migrate: false,
entries: {},
},
overrides: [
{
level: ">=100",
config: {
can_manage: true,
can_migrate: true,
},
},
],
};
}
protected getStarboardOptsForSourceChannel(sourceChannel): TStarboardOpts[] {
const config = this.getConfigForChannel(sourceChannel);
const configs = Object.values(config.entries).filter(opts => opts.source_channel_ids.includes(sourceChannel.id));
configs.forEach(cfg => {
if (cfg.enabled == null) cfg.enabled = defaultStarboardOpts.enabled;
if (cfg.positive_emojis == null) cfg.positive_emojis = defaultStarboardOpts.positive_emojis;
if (cfg.positive_required == null) cfg.positive_required = defaultStarboardOpts.positive_required;
});
return configs;
}
protected getStarboardOptsForStarboardChannel(starboardChannel): TStarboardOpts[] {
const config = this.getConfigForChannel(starboardChannel);
const configs = Object.values(config.entries).filter(opts => opts.starboard_channel_id === starboardChannel.id);
configs.forEach(cfg => {
if (cfg.enabled == null) cfg.enabled = defaultStarboardOpts.enabled;
if (cfg.positive_emojis == null) cfg.positive_emojis = defaultStarboardOpts.positive_emojis;
if (cfg.positive_required == null) cfg.positive_required = defaultStarboardOpts.positive_required;
});
return configs;
}
onLoad() {
this.starboards = GuildStarboards.getGuildInstance(this.guildId);
this.savedMessages = GuildSavedMessages.getGuildInstance(this.guildId);
this.starboardMessages = GuildStarboardMessages.getGuildInstance(this.guildId);
this.starboardReactions = GuildStarboardReactions.getGuildInstance(this.guildId);
this.onMessageDeleteFn = this.onMessageDelete.bind(this);
this.savedMessages.events.on("delete", this.onMessageDeleteFn);
@ -62,143 +151,13 @@ export class StarboardPlugin extends ZeppelinPlugin<TConfigSchema> {
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", "<channelId:channelId>")
@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.
*/
@d.event("messageReactionAdd")
@d.lock("starboardReaction")
async onMessageReactionAdd(msg: Message, emoji: { id: string; name: string }) {
async onMessageReactionAdd(msg: Message, emoji: { id: string; name: string }, userId: string) {
if (!msg.author) {
// Message is not cached, fetch it
try {
@ -209,52 +168,46 @@ export class StarboardPlugin extends ZeppelinPlugin<TConfigSchema> {
}
}
const emojiStr = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name;
const applicableStarboards = await this.starboards.getStarboardsByEmoji(emojiStr);
const applicableStarboards = await this.getStarboardOptsForSourceChannel(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;
// Move reaction into DB at this point
await this.starboardReactions.createStarboardReaction(msg.id, userId).catch();
// 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 reactions = await this.starboardReactions.getAllReactionsForMessageId(msg.id);
const reactionsCount = reactions.length;
if (reactionsCount >= starboard.positive_required) {
await this.saveMessageToStarboard(msg, starboard.starboard_channel_id);
}
}
}
/**
* Counts the specific reactions in the message, ignoring the message author
*/
async countReactions(msg: Message, reaction) {
let reactionsCount = (msg.reactions[reaction] && msg.reactions[reaction].count) || 0;
@d.event("messageReactionRemove")
async onStarboardReactionRemove(msg: Message, emoji: { id: string; name: string }, userId: string) {
await this.starboardReactions.deleteStarboardReaction(msg.id, userId);
}
// Ignore self-stars
const reactors = await msg.getReaction(reaction);
if (reactors.some(u => u.id === msg.author.id)) reactionsCount--;
return reactionsCount;
@d.event("messageReactionRemoveAll")
async onMessageReactionRemoveAll(msg: Message) {
await this.starboardReactions.deleteAllStarboardReactionsForMessageId(msg.id);
}
/**
* 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 +261,18 @@ export class StarboardPlugin extends ZeppelinPlugin<TConfigSchema> {
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 +281,71 @@ export class StarboardPlugin extends ZeppelinPlugin<TConfigSchema> {
* 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).catch(noop);
}
} 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,
).catch(noop);
}
}
}
@d.command("starboard migrate_pins", "<pinChannelId:channelId> <starboardChannelId:channelId>")
@d.command("starboard migrate_pins", "<pinChannelId:channelId> <starboardChannelId:channelId>", {
extra: {
info: {
description:
"Migrates all of a channels pins to starboard messages, posting them in the starboard channel. The old pins are not unpinned.",
},
},
})
@d.permission("can_migrate")
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.getStarboardOptsForStarboardChannel(this.bot.getChannel(args.starboardChannelId));
if (!starboards) {
msg.channel.createMessage(errorMessage("The specified channel doesn't have a starboard!")).catch(noop);
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!"))
.catch(noop);
return;
}
msg.channel.createMessage(`Migrating pins from <#${channel.id}> to <#${args.starboardChannelId}>...`);
msg.channel.createMessage(`Migrating pins from <#${channel.id}> to <#${args.starboardChannelId}>...`).catch(noop);
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(noop);
} catch (error) {
this.sendErrorMessage(
msg.channel,
"Sorry, but something went wrong!\nSyntax: `starboard migrate_pins <sourceChannelId> <starboardChannelid>`",
);
if (existingStarboardMessage) continue;
await this.saveMessageToStarboard(pin, starboard);
}
msg.channel.createMessage(successMessage("Pins migrated!"));
}
}