Merge branch 'newStarboard' of https://github.com/DarkView/ZeppelinBot into DarkView-newStarboard
This commit is contained in:
commit
b36fcb41fe
8 changed files with 463 additions and 243 deletions
54
backend/src/data/GuildStarboardMessages.ts
Normal file
54
backend/src/data/GuildStarboardMessages.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
43
backend/src/data/GuildStarboardReactions.ts
Normal file
43
backend/src/data/GuildStarboardReactions.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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[];
|
||||
}
|
|
@ -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" })
|
||||
|
|
22
backend/src/data/entities/StarboardReaction.ts
Normal file
22
backend/src/data/entities/StarboardReaction.ts
Normal 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;
|
||||
}
|
103
backend/src/migrations/1573248462469-MoveStarboardsToConfig.ts
Normal file
103
backend/src/migrations/1573248462469-MoveStarboardsToConfig.ts
Normal 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,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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!"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue