zappyzep/backend/src/data/GuildSavedMessages.ts

358 lines
10 KiB
TypeScript
Raw Normal View History

import { GuildChannel, Message } from "discord.js";
import moment from "moment-timezone";
2023-05-08 21:33:40 +03:00
import { Repository, getRepository } from "typeorm";
import { QueuedEventEmitter } from "../QueuedEventEmitter";
import { noop } from "../utils";
import { asyncMap } from "../utils/async";
Update djs & knub (#395) * update pkgs Signed-off-by: GitHub <noreply@github.com> * new knub typings Signed-off-by: GitHub <noreply@github.com> * more pkg updates Signed-off-by: GitHub <noreply@github.com> * more fixes Signed-off-by: GitHub <noreply@github.com> * channel typings Signed-off-by: GitHub <noreply@github.com> * more message utils typings fixes Signed-off-by: GitHub <noreply@github.com> * migrate permissions Signed-off-by: GitHub <noreply@github.com> * fix: InternalPoster webhookables Signed-off-by: GitHub <noreply@github.com> * djs typings: Attachment & Util Signed-off-by: GitHub <noreply@github.com> * more typings Signed-off-by: GitHub <noreply@github.com> * fix: rename permissionNames Signed-off-by: GitHub <noreply@github.com> * more fixes Signed-off-by: GitHub <noreply@github.com> * half the number of errors * knub commands => messageCommands Signed-off-by: GitHub <noreply@github.com> * configPreprocessor => configParser Signed-off-by: GitHub <noreply@github.com> * fix channel.messages Signed-off-by: GitHub <noreply@github.com> * revert automod any typing Signed-off-by: GitHub <noreply@github.com> * more configParser typings Signed-off-by: GitHub <noreply@github.com> * revert Signed-off-by: GitHub <noreply@github.com> * remove knub type params Signed-off-by: GitHub <noreply@github.com> * fix more MessageEmbed / MessageOptions Signed-off-by: GitHub <noreply@github.com> * dumb commit for @almeidx to see why this is stupid Signed-off-by: GitHub <noreply@github.com> * temp disable custom_events Signed-off-by: GitHub <noreply@github.com> * more minor typings fixes - 23 err left Signed-off-by: GitHub <noreply@github.com> * update djs dep * +debug build method (revert this) Signed-off-by: GitHub <noreply@github.com> * Revert "+debug build method (revert this)" This reverts commit a80af1e729b742d1aad1097df538d224fbd32ce7. * Redo +debug build (Revert this) Signed-off-by: GitHub <noreply@github.com> * uniform before/after Load shorthands Signed-off-by: GitHub <noreply@github.com> * remove unused imports & add prettier plugin Signed-off-by: GitHub <noreply@github.com> * env fixes for web platform hosting Signed-off-by: GitHub <noreply@github.com> * feat: knub v32-next; related fixes * fix: allow legacy keys in change_perms action * fix: request Message Content intent * fix: use Knub's config validation logic in API * fix(dashboard): fix error when there are no message and/or slash commands in a plugin * fix(automod): start_thread action thread options * fix(CustomEvents): message command types * chore: remove unneeded type annotation * feat: add forum channel icon; use thread icon for news threads * chore: make tslint happy * chore: fix formatting --------- Signed-off-by: GitHub <noreply@github.com> Co-authored-by: almeidx <almeidx@pm.me> Co-authored-by: Dragory <2606411+Dragory@users.noreply.github.com>
2023-04-01 12:58:17 +01:00
import { decryptJson, encryptJson } from "../utils/cryptHelpers";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { buildEntity } from "./buildEntity";
import { ISavedMessageData, SavedMessage } from "./entities/SavedMessage";
export class GuildSavedMessages extends BaseGuildRepository<SavedMessage> {
private messages: Repository<SavedMessage>;
protected toBePermanent: Set<string>;
public events: QueuedEventEmitter;
constructor(guildId) {
super(guildId);
this.messages = getRepository(SavedMessage);
this.events = new QueuedEventEmitter();
this.toBePermanent = new Set();
}
protected msgToSavedMessageData(msg: Message): ISavedMessageData {
const data: ISavedMessageData = {
2018-11-24 14:18:48 +02:00
author: {
username: msg.author.username,
discriminator: msg.author.discriminator,
2018-11-24 14:18:48 +02:00
},
content: msg.content,
2021-05-31 21:12:24 +02:00
timestamp: msg.createdTimestamp,
2018-11-24 14:18:48 +02:00
};
2021-08-18 01:51:42 +03:00
if (msg.attachments.size) {
2021-09-11 19:06:51 +03:00
data.attachments = Array.from(msg.attachments.values()).map((att) => ({
2021-08-18 01:51:42 +03:00
id: att.id,
contentType: att.contentType,
name: att.name,
proxyURL: att.proxyURL,
size: att.size,
spoiler: att.spoiler,
url: att.url,
width: att.width,
}));
}
if (msg.embeds.length) {
2021-09-11 19:06:51 +03:00
data.embeds = msg.embeds.map((embed) => ({
2021-08-18 01:51:42 +03:00
title: embed.title,
description: embed.description,
url: embed.url,
Update djs & knub (#395) * update pkgs Signed-off-by: GitHub <noreply@github.com> * new knub typings Signed-off-by: GitHub <noreply@github.com> * more pkg updates Signed-off-by: GitHub <noreply@github.com> * more fixes Signed-off-by: GitHub <noreply@github.com> * channel typings Signed-off-by: GitHub <noreply@github.com> * more message utils typings fixes Signed-off-by: GitHub <noreply@github.com> * migrate permissions Signed-off-by: GitHub <noreply@github.com> * fix: InternalPoster webhookables Signed-off-by: GitHub <noreply@github.com> * djs typings: Attachment & Util Signed-off-by: GitHub <noreply@github.com> * more typings Signed-off-by: GitHub <noreply@github.com> * fix: rename permissionNames Signed-off-by: GitHub <noreply@github.com> * more fixes Signed-off-by: GitHub <noreply@github.com> * half the number of errors * knub commands => messageCommands Signed-off-by: GitHub <noreply@github.com> * configPreprocessor => configParser Signed-off-by: GitHub <noreply@github.com> * fix channel.messages Signed-off-by: GitHub <noreply@github.com> * revert automod any typing Signed-off-by: GitHub <noreply@github.com> * more configParser typings Signed-off-by: GitHub <noreply@github.com> * revert Signed-off-by: GitHub <noreply@github.com> * remove knub type params Signed-off-by: GitHub <noreply@github.com> * fix more MessageEmbed / MessageOptions Signed-off-by: GitHub <noreply@github.com> * dumb commit for @almeidx to see why this is stupid Signed-off-by: GitHub <noreply@github.com> * temp disable custom_events Signed-off-by: GitHub <noreply@github.com> * more minor typings fixes - 23 err left Signed-off-by: GitHub <noreply@github.com> * update djs dep * +debug build method (revert this) Signed-off-by: GitHub <noreply@github.com> * Revert "+debug build method (revert this)" This reverts commit a80af1e729b742d1aad1097df538d224fbd32ce7. * Redo +debug build (Revert this) Signed-off-by: GitHub <noreply@github.com> * uniform before/after Load shorthands Signed-off-by: GitHub <noreply@github.com> * remove unused imports & add prettier plugin Signed-off-by: GitHub <noreply@github.com> * env fixes for web platform hosting Signed-off-by: GitHub <noreply@github.com> * feat: knub v32-next; related fixes * fix: allow legacy keys in change_perms action * fix: request Message Content intent * fix: use Knub's config validation logic in API * fix(dashboard): fix error when there are no message and/or slash commands in a plugin * fix(automod): start_thread action thread options * fix(CustomEvents): message command types * chore: remove unneeded type annotation * feat: add forum channel icon; use thread icon for news threads * chore: make tslint happy * chore: fix formatting --------- Signed-off-by: GitHub <noreply@github.com> Co-authored-by: almeidx <almeidx@pm.me> Co-authored-by: Dragory <2606411+Dragory@users.noreply.github.com>
2023-04-01 12:58:17 +01:00
timestamp: embed.timestamp ? Date.parse(embed.timestamp) : null,
2021-08-18 01:51:42 +03:00
color: embed.color,
2021-09-11 19:06:51 +03:00
fields: embed.fields.map((field) => ({
2021-08-18 01:51:42 +03:00
name: field.name,
value: field.value,
Update djs & knub (#395) * update pkgs Signed-off-by: GitHub <noreply@github.com> * new knub typings Signed-off-by: GitHub <noreply@github.com> * more pkg updates Signed-off-by: GitHub <noreply@github.com> * more fixes Signed-off-by: GitHub <noreply@github.com> * channel typings Signed-off-by: GitHub <noreply@github.com> * more message utils typings fixes Signed-off-by: GitHub <noreply@github.com> * migrate permissions Signed-off-by: GitHub <noreply@github.com> * fix: InternalPoster webhookables Signed-off-by: GitHub <noreply@github.com> * djs typings: Attachment & Util Signed-off-by: GitHub <noreply@github.com> * more typings Signed-off-by: GitHub <noreply@github.com> * fix: rename permissionNames Signed-off-by: GitHub <noreply@github.com> * more fixes Signed-off-by: GitHub <noreply@github.com> * half the number of errors * knub commands => messageCommands Signed-off-by: GitHub <noreply@github.com> * configPreprocessor => configParser Signed-off-by: GitHub <noreply@github.com> * fix channel.messages Signed-off-by: GitHub <noreply@github.com> * revert automod any typing Signed-off-by: GitHub <noreply@github.com> * more configParser typings Signed-off-by: GitHub <noreply@github.com> * revert Signed-off-by: GitHub <noreply@github.com> * remove knub type params Signed-off-by: GitHub <noreply@github.com> * fix more MessageEmbed / MessageOptions Signed-off-by: GitHub <noreply@github.com> * dumb commit for @almeidx to see why this is stupid Signed-off-by: GitHub <noreply@github.com> * temp disable custom_events Signed-off-by: GitHub <noreply@github.com> * more minor typings fixes - 23 err left Signed-off-by: GitHub <noreply@github.com> * update djs dep * +debug build method (revert this) Signed-off-by: GitHub <noreply@github.com> * Revert "+debug build method (revert this)" This reverts commit a80af1e729b742d1aad1097df538d224fbd32ce7. * Redo +debug build (Revert this) Signed-off-by: GitHub <noreply@github.com> * uniform before/after Load shorthands Signed-off-by: GitHub <noreply@github.com> * remove unused imports & add prettier plugin Signed-off-by: GitHub <noreply@github.com> * env fixes for web platform hosting Signed-off-by: GitHub <noreply@github.com> * feat: knub v32-next; related fixes * fix: allow legacy keys in change_perms action * fix: request Message Content intent * fix: use Knub's config validation logic in API * fix(dashboard): fix error when there are no message and/or slash commands in a plugin * fix(automod): start_thread action thread options * fix(CustomEvents): message command types * chore: remove unneeded type annotation * feat: add forum channel icon; use thread icon for news threads * chore: make tslint happy * chore: fix formatting --------- Signed-off-by: GitHub <noreply@github.com> Co-authored-by: almeidx <almeidx@pm.me> Co-authored-by: Dragory <2606411+Dragory@users.noreply.github.com>
2023-04-01 12:58:17 +01:00
inline: field.inline ?? false,
2021-08-18 01:51:42 +03:00
})),
author: embed.author
? {
name: embed.author.name,
url: embed.author.url,
iconURL: embed.author.iconURL,
proxyIconURL: embed.author.proxyIconURL,
}
: undefined,
thumbnail: embed.thumbnail
? {
url: embed.thumbnail.url,
proxyURL: embed.thumbnail.proxyURL,
height: embed.thumbnail.height,
width: embed.thumbnail.width,
}
: undefined,
image: embed.image
? {
url: embed.image.url,
proxyURL: embed.image.proxyURL,
height: embed.image.height,
width: embed.image.width,
}
: undefined,
video: embed.video
? {
url: embed.video.url,
proxyURL: embed.video.proxyURL,
height: embed.video.height,
width: embed.video.width,
}
: undefined,
footer: embed.footer
? {
text: embed.footer.text,
iconURL: embed.footer.iconURL,
proxyIconURL: embed.footer.proxyIconURL,
}
: undefined,
}));
}
if (msg.stickers?.size) {
2021-09-11 19:06:51 +03:00
data.stickers = Array.from(msg.stickers.values()).map((sticker) => ({
2021-08-18 01:51:42 +03:00
format: sticker.format,
guildId: sticker.guildId,
id: sticker.id,
name: sticker.name,
description: sticker.description,
available: sticker.available,
type: sticker.type,
}));
}
return data;
2018-11-24 14:18:48 +02:00
}
protected async _processEntityFromDB(entity: SavedMessage | undefined) {
if (entity == null) {
return entity;
}
entity.data = (await decryptJson(entity.data as unknown as string)) as ISavedMessageData;
return entity;
}
protected async _processEntityToDB(entity: Partial<SavedMessage>) {
if (entity.data) {
entity.data = (await encryptJson(entity.data)) as any;
}
return entity;
}
async find(id: string, includeDeleted = false): Promise<SavedMessage | undefined> {
let query = this.messages
.createQueryBuilder()
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("id = :id", { id });
if (!includeDeleted) {
query = query.andWhere("deleted_at IS NULL");
}
const result = await query.getOne();
return this.processEntityFromDB(result);
}
async getUserMessagesByChannelAfterId(userId, channelId, afterId, limit?: number): Promise<SavedMessage[]> {
let query = this.messages
.createQueryBuilder()
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("channel_id = :channel_id", { channel_id: channelId })
2020-06-01 21:28:07 +03:00
.andWhere("user_id = :user_id", { user_id: userId })
.andWhere("id > :afterId", { afterId })
.andWhere("deleted_at IS NULL");
if (limit != null) {
query = query.limit(limit);
}
const results = await query.getMany();
return this.processMultipleEntitiesFromDB(results);
}
async getMultiple(messageIds: string[]): Promise<SavedMessage[]> {
const results = await this.messages
.createQueryBuilder()
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("id IN (:messageIds)", { messageIds })
.getMany();
return this.processMultipleEntitiesFromDB(results);
}
async createFromMsg(msg: Message, overrides = {}): Promise<void> {
2021-08-18 23:03:28 +03:00
// FIXME: Hotfix
if (!msg.channel) {
return;
}
// Don't actually save bot messages. Just pass them through as if they were saved.
if (msg.author.bot) {
const fakeSavedMessage = buildEntity(SavedMessage, await this.msgToInsertReadyEntity(msg));
this.fireCreateEvents(fakeSavedMessage);
return;
}
await this.createFromMessages([msg], overrides);
}
async createFromMessages(messages: Message[], overrides = {}): Promise<void> {
const items = await asyncMap(messages, async (msg) => ({
...(await this.msgToInsertReadyEntity(msg)),
...overrides,
}));
await this.insertBulk(items);
}
protected async msgToInsertReadyEntity(msg: Message): Promise<Partial<SavedMessage>> {
2018-11-24 14:18:48 +02:00
const savedMessageData = this.msgToSavedMessageData(msg);
2021-05-31 21:12:24 +02:00
const postedAt = moment.utc(msg.createdTimestamp, "x").format("YYYY-MM-DD HH:mm:ss");
2018-11-24 14:18:48 +02:00
2021-10-09 14:30:46 +03:00
return {
2018-11-24 14:18:48 +02:00
id: msg.id,
guild_id: (msg.channel as GuildChannel).guild.id,
channel_id: msg.channel.id,
user_id: msg.author.id,
is_bot: msg.author.bot,
data: savedMessageData,
posted_at: postedAt,
2021-10-09 14:30:46 +03:00
};
2018-11-24 14:18:48 +02:00
}
protected async insertBulk(items: Array<Partial<SavedMessage>>): Promise<void> {
for (const item of items) {
if (this.toBePermanent.has(item.id!)) {
item.is_permanent = true;
this.toBePermanent.delete(item.id!);
}
}
2021-10-09 14:30:46 +03:00
const itemsToInsert = await asyncMap(items, (item) => this.processEntityToDB({ ...item }));
await this.messages.createQueryBuilder().insert().values(itemsToInsert).execute().catch(noop);
for (const item of items) {
// perf: save a db lookup and message content decryption by building the entity manually
const inserted = buildEntity(SavedMessage, item);
this.fireCreateEvents(inserted);
}
}
protected async fireCreateEvents(message: SavedMessage) {
this.events.emit("create", [message]);
this.events.emit(`create:${message.id}`, [message]);
}
async markAsDeleted(id): Promise<void> {
await this.messages
.createQueryBuilder("messages")
.update()
.set({
deleted_at: () => "NOW(3)",
})
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("id = :id", { id })
.execute();
const deleted = await this.find(id, true);
if (deleted) {
this.events.emit("delete", [deleted]);
this.events.emit(`delete:${id}`, [deleted]);
}
}
/**
* Marks the specified messages as deleted in the database (if they weren't already marked before).
* If any messages were marked as deleted, also emits the deleteBulk event.
*/
async markBulkAsDeleted(ids) {
const deletedAt = moment.utc().format("YYYY-MM-DD HH:mm:ss");
await this.messages
.createQueryBuilder()
.update()
.set({ deleted_at: deletedAt })
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("id IN (:ids)", { ids })
.andWhere("deleted_at IS NULL")
.execute();
let deleted = await this.messages
.createQueryBuilder()
.where("id IN (:ids)", { ids })
.andWhere("deleted_at = :deletedAt", { deletedAt })
.getMany();
deleted = await this.processMultipleEntitiesFromDB(deleted);
if (deleted.length) {
this.events.emit("deleteBulk", [deleted]);
}
}
async saveEdit(id, newData: ISavedMessageData): Promise<void> {
const oldMessage = await this.find(id);
if (!oldMessage) return;
const newMessage = { ...oldMessage, data: newData };
2021-06-02 19:35:44 +02:00
// @ts-ignore
const updateData = await this.processEntityToDB({
data: newData,
});
await this.messages.update({ id }, updateData);
this.events.emit("update", [newMessage, oldMessage]);
this.events.emit(`update:${id}`, [newMessage, oldMessage]);
}
2018-11-24 14:18:48 +02:00
async saveEditFromMsg(msg: Message): Promise<void> {
2018-11-24 14:18:48 +02:00
const newData = this.msgToSavedMessageData(msg);
await this.saveEdit(msg.id, newData);
2018-11-24 14:18:48 +02:00
}
async setPermanent(id: string): Promise<void> {
const savedMsg = await this.find(id);
if (savedMsg) {
await this.messages.update(
{ id },
{
is_permanent: true,
},
);
} else {
this.toBePermanent.add(id);
}
}
async onceMessageAvailable(
id: string,
handler: (msg?: SavedMessage) => any,
timeout: number = 60 * 1000,
): Promise<void> {
let called = false;
let onceEventListener;
let timeoutFn;
const callHandler = async (msg?: SavedMessage) => {
this.events.off(`create:${id}`, onceEventListener);
clearTimeout(timeoutFn);
if (called) return;
called = true;
await handler(msg);
};
onceEventListener = this.events.once(`create:${id}`, callHandler);
timeoutFn = setTimeout(() => {
called = true;
callHandler(undefined);
}, timeout);
const messageInDB = await this.find(id);
if (messageInDB) {
callHandler(messageInDB);
}
}
}