Add Starboard plugin
This commit is contained in:
parent
33d2026556
commit
0c8efedb8c
7 changed files with 550 additions and 5 deletions
84
src/data/GuildStarboards.ts
Normal file
84
src/data/GuildStarboards.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { BaseRepository } from "./BaseRepository";
|
||||||
|
import { getRepository, Repository } from "typeorm";
|
||||||
|
import { Starboard } from "./entities/Starboard";
|
||||||
|
import { StarboardMessage } from "./entities/StarboardMessage";
|
||||||
|
|
||||||
|
export class GuildStarboards extends BaseRepository {
|
||||||
|
private starboards: Repository<Starboard>;
|
||||||
|
private starboardMessages: Repository<StarboardMessage>;
|
||||||
|
|
||||||
|
constructor(guildId) {
|
||||||
|
super(guildId);
|
||||||
|
this.starboards = getRepository(Starboard);
|
||||||
|
this.starboardMessages = getRepository(StarboardMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStarboardByChannelId(channelId): Promise<Starboard> {
|
||||||
|
return this.starboards.findOne({
|
||||||
|
where: {
|
||||||
|
guild_id: this.guildId,
|
||||||
|
channel_id: channelId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getStarboardsByEmoji(emoji): Promise<Starboard[]> {
|
||||||
|
return this.starboards.find({
|
||||||
|
where: {
|
||||||
|
guild_id: this.guildId,
|
||||||
|
emoji
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getStarboardMessageByStarboardIdAndMessageId(starboardId, messageId): Promise<StarboardMessage> {
|
||||||
|
return this.starboardMessages.findOne({
|
||||||
|
relations: this.getRelations(),
|
||||||
|
where: {
|
||||||
|
starboard_id: starboardId,
|
||||||
|
message_id: messageId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getStarboardMessagesByMessageId(id): Promise<StarboardMessage[]> {
|
||||||
|
return this.starboardMessages.find({
|
||||||
|
relations: this.getRelations(),
|
||||||
|
where: {
|
||||||
|
message_id: id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createStarboardMessage(starboardId, messageId, starboardMessageId): Promise<void> {
|
||||||
|
await this.starboardMessages.insert({
|
||||||
|
starboard_id: starboardId,
|
||||||
|
message_id: messageId,
|
||||||
|
starboard_message_id: starboardMessageId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteStarboardMessage(starboardId, messageId): Promise<void> {
|
||||||
|
await this.starboardMessages.delete({
|
||||||
|
starboard_id: starboardId,
|
||||||
|
message_id: messageId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(channelId: string, channelWhitelist: string[], emoji: string, reactionsRequired: number): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
await this.starboards.delete({
|
||||||
|
guild_id: this.guildId,
|
||||||
|
channel_id: channelId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
23
src/data/entities/Starboard.ts
Normal file
23
src/data/entities/Starboard.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
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[];
|
||||||
|
}
|
25
src/data/entities/StarboardMessage.ts
Normal file
25
src/data/entities/StarboardMessage.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
@PrimaryColumn()
|
||||||
|
message_id: string;
|
||||||
|
|
||||||
|
@Column() starboard_message_id: string;
|
||||||
|
|
||||||
|
@ManyToOne(type => Starboard, sb => sb.starboardMessages)
|
||||||
|
@JoinColumn({ name: "starboard_id" })
|
||||||
|
starboard: Starboard;
|
||||||
|
|
||||||
|
@OneToOne(type => SavedMessage)
|
||||||
|
@JoinColumn({ name: "message_id" })
|
||||||
|
message: SavedMessage;
|
||||||
|
}
|
|
@ -60,6 +60,7 @@ import { MessageSaverPlugin } from "./plugins/MessageSaver";
|
||||||
import { CasesPlugin } from "./plugins/Cases";
|
import { CasesPlugin } from "./plugins/Cases";
|
||||||
import { MutesPlugin } from "./plugins/Mutes";
|
import { MutesPlugin } from "./plugins/Mutes";
|
||||||
import { SlowmodePlugin } from "./plugins/Slowmode";
|
import { SlowmodePlugin } from "./plugins/Slowmode";
|
||||||
|
import { StarboardPlugin } from "./plugins/Starboard";
|
||||||
|
|
||||||
// Run latest database migrations
|
// Run latest database migrations
|
||||||
logger.info("Running database migrations");
|
logger.info("Running database migrations");
|
||||||
|
@ -90,7 +91,8 @@ connect().then(async conn => {
|
||||||
persist: PersistPlugin,
|
persist: PersistPlugin,
|
||||||
spam: SpamPlugin,
|
spam: SpamPlugin,
|
||||||
tags: TagsPlugin,
|
tags: TagsPlugin,
|
||||||
slowmode: SlowmodePlugin
|
slowmode: SlowmodePlugin,
|
||||||
|
starboard: StarboardPlugin
|
||||||
},
|
},
|
||||||
globalPlugins: {
|
globalPlugins: {
|
||||||
bot_control: BotControlPlugin,
|
bot_control: BotControlPlugin,
|
||||||
|
|
85
src/migrations/1544887946307-CreateStarboardTable.ts
Normal file
85
src/migrations/1544887946307-CreateStarboardTable.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { MigrationInterface, QueryRunner, Table } from "typeorm";
|
||||||
|
|
||||||
|
export class CreateStarboardTable1544887946307 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: "starboard_messages",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: "starboard_id",
|
||||||
|
type: "int",
|
||||||
|
unsigned: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "message_id",
|
||||||
|
type: "bigint",
|
||||||
|
unsigned: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "starboard_message_id",
|
||||||
|
type: "bigint",
|
||||||
|
unsigned: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await queryRunner.createPrimaryKey("starboard_messages", ["starboard_id", "message_id"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.dropTable("starboards", true);
|
||||||
|
await queryRunner.dropTable("starboard_messages", true);
|
||||||
|
}
|
||||||
|
}
|
322
src/plugins/Starboard.ts
Normal file
322
src/plugins/Starboard.ts
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
import { decorators as d, waitForReply, utils as knubUtils } from "knub";
|
||||||
|
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||||
|
import { GuildStarboards } from "../data/GuildStarboards";
|
||||||
|
import { GuildChannel, Message, TextChannel } from "eris";
|
||||||
|
import { customEmojiRegex, errorMessage, getEmojiInString, noop, snowflakeRegex, successMessage } from "../utils";
|
||||||
|
import { Starboard } from "../data/entities/Starboard";
|
||||||
|
import path from "path";
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
import { GuildSavedMessages } from "../data/GuildSavedMessages";
|
||||||
|
import { SavedMessage } from "../data/entities/SavedMessage";
|
||||||
|
|
||||||
|
export class StarboardPlugin extends ZeppelinPlugin {
|
||||||
|
protected starboards: GuildStarboards;
|
||||||
|
protected savedMessages: GuildSavedMessages;
|
||||||
|
|
||||||
|
private onMessageDeleteFn;
|
||||||
|
|
||||||
|
getDefaultOptions() {
|
||||||
|
return {
|
||||||
|
permissions: {
|
||||||
|
manage: false
|
||||||
|
},
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
level: ">=100",
|
||||||
|
permissions: {
|
||||||
|
manage: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
this.starboards = GuildStarboards.getInstance(this.guildId);
|
||||||
|
this.savedMessages = GuildSavedMessages.getInstance(this.guildId);
|
||||||
|
|
||||||
|
this.onMessageDeleteFn = this.onMessageDelete.bind(this);
|
||||||
|
this.savedMessages.events.on("delete", this.onMessageDeleteFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnload() {
|
||||||
|
this.savedMessages.events.off("delete", this.onMessageDeleteFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interactive setup for creating a starboard
|
||||||
|
*/
|
||||||
|
@d.command("starboard create")
|
||||||
|
@d.permission("manage")
|
||||||
|
@d.nonBlocking()
|
||||||
|
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?");
|
||||||
|
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];
|
||||||
|
if (emoji.match(customEmojiRegex)) {
|
||||||
|
// <:name:id> to name:id, as Eris puts them in the message reactions object
|
||||||
|
emoji = emoji.substr(2, emoji.length - 1);
|
||||||
|
}
|
||||||
|
} 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("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")
|
||||||
|
async onMessageReactionAdd(msg: Message, emoji: { id: string; name: string }) {
|
||||||
|
if (!msg.author) {
|
||||||
|
// Message is not cached, fetch it
|
||||||
|
msg = await msg.channel.getMessage(msg.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojiStr = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name;
|
||||||
|
const applicableStarboards = await this.starboards.getStarboardsByEmoji(emojiStr);
|
||||||
|
|
||||||
|
for (const starboard of applicableStarboards) {
|
||||||
|
// 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 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,
|
||||||
|
msg.id
|
||||||
|
);
|
||||||
|
if (existingSavedMessage) return;
|
||||||
|
|
||||||
|
const reactionsCount = await this.countReactions(msg, emojiStr);
|
||||||
|
|
||||||
|
if (reactionsCount >= starboard.reactions_required) {
|
||||||
|
await this.saveMessageToStarboard(msg, starboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a reaction is removed from a message, check if there are any applicable starboards and if the message in
|
||||||
|
* question had already been posted there. If it has already been posted there, and the reaction count is now lower
|
||||||
|
* than the required threshold, remove the post from the starboard.
|
||||||
|
*/
|
||||||
|
@d.event("messageReactionRemove")
|
||||||
|
async onMessageReactionRemove(msg: Message, emoji: { id: string; name: string }) {
|
||||||
|
if (!msg.author) {
|
||||||
|
// Message is not cached, fetch it
|
||||||
|
msg = await msg.channel.getMessage(msg.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojiStr = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name;
|
||||||
|
const applicableStarboards = await this.starboards.getStarboardsByEmoji(emojiStr);
|
||||||
|
|
||||||
|
for (const starboard of applicableStarboards) {
|
||||||
|
const existingSavedMessage = await this.starboards.getStarboardMessageByStarboardIdAndMessageId(
|
||||||
|
starboard.id,
|
||||||
|
msg.id
|
||||||
|
);
|
||||||
|
if (!existingSavedMessage) return;
|
||||||
|
|
||||||
|
const reactionsCount = await this.countReactions(msg, emojiStr);
|
||||||
|
|
||||||
|
if (reactionsCount < starboard.reactions_required) {
|
||||||
|
await this.removeMessageFromStarboard(msg.id, starboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
// Ignore self-stars
|
||||||
|
const reactors = await msg.getReaction(reaction);
|
||||||
|
if (reactors.some(u => u.id === msg.author.id)) reactionsCount--;
|
||||||
|
|
||||||
|
return reactionsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
if (!channel) return;
|
||||||
|
|
||||||
|
const time = moment(msg.timestamp, "x").format("YYYY-MM-DD [at] HH:mm:ss [UTC]");
|
||||||
|
|
||||||
|
const embed: any = {
|
||||||
|
footer: {
|
||||||
|
text: `#${(msg.channel as GuildChannel).name} - ${time}`
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
name: `${msg.author.username}#${msg.author.discriminator}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (msg.author.avatarURL) {
|
||||||
|
embed.author.icon_url = msg.author.avatarURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.content) {
|
||||||
|
embed.description = msg.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.attachments.length) {
|
||||||
|
const attachment = msg.attachments[0];
|
||||||
|
const ext = path
|
||||||
|
.extname(attachment.filename)
|
||||||
|
.slice(1)
|
||||||
|
.toLowerCase();
|
||||||
|
if (["jpeg", "jpg", "png", "gif", "webp"].includes(ext)) {
|
||||||
|
embed.image = { url: attachment.url };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const starboardMessage = await (channel as TextChannel).createMessage({ embed });
|
||||||
|
await this.starboards.createStarboardMessage(starboard.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;
|
||||||
|
|
||||||
|
await this.bot.deleteMessage(starboard.channel_id, starboardMessage.starboard_message_id).catch(noop);
|
||||||
|
await this.starboards.deleteStarboardMessage(starboard.id, msgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a message is deleted, also delete it from any starboards it's been posted in.
|
||||||
|
* This function is called in response to GuildSavedMessages events.
|
||||||
|
*/
|
||||||
|
async onMessageDelete(msg: SavedMessage) {
|
||||||
|
const starboardMessages = await this.starboards.with("starboard").getStarboardMessagesByMessageId(msg.id);
|
||||||
|
if (!starboardMessages.length) return;
|
||||||
|
|
||||||
|
for (const starboardMessage of starboardMessages) {
|
||||||
|
if (!starboardMessage.starboard) continue;
|
||||||
|
this.removeMessageFromStarboard(starboardMessage.message_id, starboardMessage.starboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/utils.ts
12
src/utils.ts
|
@ -81,8 +81,11 @@ export function formatTemplateString(str: string, values) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const snowflakeRegex = /[1-9][0-9]{5,19}/;
|
||||||
|
|
||||||
|
const isSnowflakeRegex = new RegExp(`^${snowflakeRegex}$`);
|
||||||
export function isSnowflake(v: string): boolean {
|
export function isSnowflake(v: string): boolean {
|
||||||
return /^\d{17,20}$/.test(v);
|
return isSnowflakeRegex.test(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sleep(ms: number): Promise<void> {
|
export function sleep(ms: number): Promise<void> {
|
||||||
|
@ -166,11 +169,12 @@ export function getInviteCodesInString(str: string): string[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unicodeEmojiRegex = emojiRegex();
|
export const unicodeEmojiRegex = emojiRegex();
|
||||||
export const customEmojiRegex = /<:(?:.*?):(\d+)>/g;
|
export const customEmojiRegex = /<:(?:.*?):(\d+)>/;
|
||||||
export const anyEmojiRegex = new RegExp(`(?:(?:${unicodeEmojiRegex.source})|(?:${customEmojiRegex.source}))`, "g");
|
export const anyEmojiRegex = new RegExp(`(?:(?:${unicodeEmojiRegex.source})|(?:${customEmojiRegex.source}))`);
|
||||||
|
|
||||||
|
const matchAllEmojiRegex = new RegExp(anyEmojiRegex.source, "g");
|
||||||
export function getEmojiInString(str: string): string[] {
|
export function getEmojiInString(str: string): string[] {
|
||||||
return str.match(anyEmojiRegex) || [];
|
return str.match(matchAllEmojiRegex) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function trimLines(str: string) {
|
export function trimLines(str: string) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue