bridging emojis from revolt to discord
This commit is contained in:
parent
5d20b0ca12
commit
e0bc136a37
5 changed files with 156 additions and 3 deletions
|
@ -1,7 +1,12 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { GuildEmoji } from "discord.js";
|
||||||
import JSON5 from 'json5';
|
import JSON5 from 'json5';
|
||||||
|
import { BRIDGED_EMOJIS, logger } from "..";
|
||||||
|
import { client } from "./client";
|
||||||
|
|
||||||
const EMOJI_DICT_URL = 'https://raw.githubusercontent.com/revoltchat/revite/master/src/assets/emojis.ts';
|
const EMOJI_DICT_URL = 'https://raw.githubusercontent.com/revoltchat/revite/master/src/assets/emojis.ts';
|
||||||
|
const EMOJI_URL_BASE = 'https://dl.insrt.uk/projects/revolt/emotes/';
|
||||||
|
const EMOJI_SERVERS = process.env.EMOJI_SERVERS?.split(',') || [];
|
||||||
|
|
||||||
async function fetchEmojiList(): Promise<Record<string, string>> {
|
async function fetchEmojiList(): Promise<Record<string, string>> {
|
||||||
const file: string = (await axios.get(EMOJI_DICT_URL)).data;
|
const file: string = (await axios.get(EMOJI_DICT_URL)).data;
|
||||||
|
@ -10,3 +15,129 @@ async function fetchEmojiList(): Promise<Record<string, string>> {
|
||||||
|
|
||||||
return JSON5.parse(file.substring(start, end).trim());
|
return JSON5.parse(file.substring(start, end).trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emojiUpdate = async () => {
|
||||||
|
try {
|
||||||
|
if (!EMOJI_SERVERS.length) return logger.info('$EMOJI_SERVERS not set, not bridging emojis.');
|
||||||
|
|
||||||
|
if (!client.readyAt) await new Promise(r => client.once('ready', r));
|
||||||
|
logger.info('Updating bridged emojis. Due to Discord rate limits, this can take a few hours to complete.');
|
||||||
|
|
||||||
|
const emojis = await fetchEmojiList();
|
||||||
|
logger.info(`Downloaded emoji list: ${Object.keys(emojis).length} emojis.`);
|
||||||
|
|
||||||
|
const servers = await Promise.all(EMOJI_SERVERS.map(id => client.guilds.fetch(id)));
|
||||||
|
await Promise.all(servers.map(server => server.emojis.fetch())); // Make sure all emojis are cached
|
||||||
|
|
||||||
|
const findFreeServer = (animated: boolean) => servers.find(
|
||||||
|
server => server.emojis.cache
|
||||||
|
.filter(e => e.animated == animated)
|
||||||
|
.size < 50
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove unknown emojis from servers
|
||||||
|
for (const server of servers) {
|
||||||
|
for (const emoji of server.emojis.cache) {
|
||||||
|
const dbEmoji = await BRIDGED_EMOJIS.findOne({
|
||||||
|
emojiid: emoji[1].id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!dbEmoji) {
|
||||||
|
try {
|
||||||
|
logger.info('Found unknown emoji; deleting.');
|
||||||
|
await emoji[1].delete('Unknown emoji');
|
||||||
|
} catch(e) {
|
||||||
|
logger.warn('Failed to delete emoji: ' + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const emoji of Object.entries(emojis)) {
|
||||||
|
const dbEmoji = await BRIDGED_EMOJIS.findOne({
|
||||||
|
$or: [
|
||||||
|
{ name: emoji[0] },
|
||||||
|
{ originalFileUrl: emoji[1] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!dbEmoji) {
|
||||||
|
// Upload to Discord
|
||||||
|
logger.debug('Uploading emoji: ' + emoji[1]);
|
||||||
|
|
||||||
|
const fileurl = EMOJI_URL_BASE + emoji[1].replace('custom:', '');
|
||||||
|
const server = findFreeServer(emoji[1].endsWith('.gif'));
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
logger.warn('Could not find a server with free emoji slots for ' + emoji[1]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let e: GuildEmoji;
|
||||||
|
try {
|
||||||
|
e = await server.emojis.create(fileurl, emoji[0], { reason: 'Bridged Emoji' });
|
||||||
|
} catch(e) {
|
||||||
|
logger.warn(emoji[0] + ': Failed to upload emoji: ' + e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await BRIDGED_EMOJIS.insert({
|
||||||
|
animated: e.animated || false,
|
||||||
|
emojiid: e.id,
|
||||||
|
name: emoji[0],
|
||||||
|
originalFileUrl: fileurl,
|
||||||
|
server: e.guild.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Double check if emoji exists
|
||||||
|
let exists = false;
|
||||||
|
for (const server of servers) {
|
||||||
|
if (server.emojis.cache.find(e => e.id == dbEmoji.emojiid)) {
|
||||||
|
exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
logger.info(`Emoji ${emoji[0]} does not exist; reuploading.`);
|
||||||
|
await BRIDGED_EMOJIS.remove({ emojiid: dbEmoji.emojiid });
|
||||||
|
|
||||||
|
const fileurl = EMOJI_URL_BASE + emoji[1].replace('custom:', '');
|
||||||
|
const server = findFreeServer(emoji[1].endsWith('.gif'));
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
logger.warn('Could not find a server with free emoji slots for ' + emoji[1]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let e: GuildEmoji;
|
||||||
|
try {
|
||||||
|
e = await server.emojis.create(fileurl, emoji[0], { reason: 'Bridged Emoji' });
|
||||||
|
} catch(e) {
|
||||||
|
logger.warn(emoji[0] + ': Failed to upload emoji: ' + e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await BRIDGED_EMOJIS.insert({
|
||||||
|
animated: e.animated || false,
|
||||||
|
emojiid: e.id,
|
||||||
|
name: emoji[0],
|
||||||
|
originalFileUrl: fileurl,
|
||||||
|
server: e.guild.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.done('Emoji update finished.');
|
||||||
|
} catch(e) {
|
||||||
|
logger.error('Updating bridged emojis failed');
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
emojiUpdate();
|
||||||
|
setInterval(emojiUpdate, 1000 * 60 * 60 * 6); // Every 6h
|
||||||
|
|
||||||
|
export { fetchEmojiList }
|
||||||
|
|
|
@ -24,5 +24,6 @@ const login = () => new Promise((resolve: (value: Discord.Client) => void) => {
|
||||||
|
|
||||||
import('./events');
|
import('./events');
|
||||||
import('./commands');
|
import('./commands');
|
||||||
|
import('./bridgeEmojis');
|
||||||
|
|
||||||
export { client, login }
|
export { client, login }
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { ICollection } from 'monk';
|
||||||
import BridgeConfig from './types/BridgeConfig';
|
import BridgeConfig from './types/BridgeConfig';
|
||||||
import BridgedMessage from './types/BridgedMessage';
|
import BridgedMessage from './types/BridgedMessage';
|
||||||
import BridgeRequest from './types/BridgeRequest';
|
import BridgeRequest from './types/BridgeRequest';
|
||||||
|
import DiscordBridgedEmoji from './types/DiscordBridgedEmoji';
|
||||||
|
|
||||||
config();
|
config();
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ const db = getDb();
|
||||||
const BRIDGED_MESSAGES: ICollection<BridgedMessage> = db.get('bridged_messages');
|
const BRIDGED_MESSAGES: ICollection<BridgedMessage> = db.get('bridged_messages');
|
||||||
const BRIDGE_CONFIG: ICollection<BridgeConfig> = db.get('bridge_config');
|
const BRIDGE_CONFIG: ICollection<BridgeConfig> = db.get('bridge_config');
|
||||||
const BRIDGE_REQUESTS: ICollection<BridgeRequest> = db.get('bridge_requests');
|
const BRIDGE_REQUESTS: ICollection<BridgeRequest> = db.get('bridge_requests');
|
||||||
|
const BRIDGED_EMOJIS: ICollection<DiscordBridgedEmoji> = db.get('bridged_emojis');
|
||||||
|
|
||||||
for (const v of [ 'REVOLT_TOKEN', 'DISCORD_TOKEN', 'DB_STRING' ]) {
|
for (const v of [ 'REVOLT_TOKEN', 'DISCORD_TOKEN', 'DB_STRING' ]) {
|
||||||
if (!process.env[v]) {
|
if (!process.env[v]) {
|
||||||
|
@ -31,4 +33,4 @@ for (const v of [ 'REVOLT_TOKEN', 'DISCORD_TOKEN', 'DB_STRING' ]) {
|
||||||
]);
|
]);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export { logger, db, BRIDGED_MESSAGES, BRIDGE_CONFIG, BRIDGE_REQUESTS }
|
export { logger, db, BRIDGED_MESSAGES, BRIDGE_CONFIG, BRIDGE_REQUESTS, BRIDGED_EMOJIS }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { BRIDGED_MESSAGES, BRIDGE_CONFIG, logger } from "..";
|
import { BRIDGED_EMOJIS, BRIDGED_MESSAGES, BRIDGE_CONFIG, logger } from "..";
|
||||||
import { AUTUMN_URL, client } from "./client";
|
import { AUTUMN_URL, client } from "./client";
|
||||||
import { client as discordClient } from "../discord/client";
|
import { client as discordClient } from "../discord/client";
|
||||||
import { MessageEmbed, MessagePayload, TextChannel, WebhookClient, WebhookMessageOptions } from "discord.js";
|
import { MessageEmbed, MessagePayload, TextChannel, WebhookClient, WebhookMessageOptions } from "discord.js";
|
||||||
|
@ -7,9 +7,17 @@ import { SendableEmbed } from "revolt-api";
|
||||||
import { clipText, discordFetchMessage, revoltFetchUser } from "../util";
|
import { clipText, discordFetchMessage, revoltFetchUser } from "../util";
|
||||||
import { smartReplace } from "smart-replace";
|
import { smartReplace } from "smart-replace";
|
||||||
import { metrics } from "../metrics";
|
import { metrics } from "../metrics";
|
||||||
|
import { fetchEmojiList } from "../discord/bridgeEmojis";
|
||||||
|
|
||||||
const RE_MENTION_USER = /<@[0-9A-HJ-KM-NP-TV-Z]{26}>/g;
|
const RE_MENTION_USER = /<@[0-9A-HJ-KM-NP-TV-Z]{26}>/g;
|
||||||
const RE_MENTION_CHANNEL = /<#[0-9A-HJ-KM-NP-TV-Z]{26}>/g;
|
const RE_MENTION_CHANNEL = /<#[0-9A-HJ-KM-NP-TV-Z]{26}>/g;
|
||||||
|
const RE_EMOJI = /:[^\s]+/g;
|
||||||
|
|
||||||
|
const KNOWN_EMOJI_NAMES: string[] = [];
|
||||||
|
|
||||||
|
fetchEmojiList()
|
||||||
|
.then(emojis => Object.keys(emojis).forEach(name => KNOWN_EMOJI_NAMES.push(name)))
|
||||||
|
.catch(e => console.error(e));
|
||||||
|
|
||||||
client.on('message/delete', async id => {
|
client.on('message/delete', async id => {
|
||||||
try {
|
try {
|
||||||
|
@ -252,7 +260,16 @@ async function renderMessageBody(message: string): Promise<string> {
|
||||||
return discordChannel ? `<#${discordChannel.id}>` : `#${channel?.name || id}`;
|
return discordChannel ? `<#${discordChannel.id}>` : `#${channel?.name || id}`;
|
||||||
}, { cacheMatchResults: true, maxMatches: 10 });
|
}, { cacheMatchResults: true, maxMatches: 10 });
|
||||||
|
|
||||||
// TODO: fetch emojis and upload them to Discord or smth
|
message = await smartReplace(message, RE_EMOJI, async (match) => {
|
||||||
|
const emojiName = match.replace(/(^:)|(:$)/g, '');
|
||||||
|
|
||||||
|
if (!KNOWN_EMOJI_NAMES.includes(emojiName)) return match;
|
||||||
|
|
||||||
|
const dbEmoji = await BRIDGED_EMOJIS.findOne({ name: emojiName });
|
||||||
|
if (!dbEmoji) return match;
|
||||||
|
|
||||||
|
return `<${dbEmoji.animated ? 'a' : ''}:${emojiName}:${dbEmoji.emojiid}>`;
|
||||||
|
}, { cacheMatchResults: true, maxMatches: 40 });
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
export default class {
|
export default class {
|
||||||
name: string;
|
name: string;
|
||||||
emojiid: string;
|
emojiid: string;
|
||||||
|
server: string;
|
||||||
animated: boolean;
|
animated: boolean;
|
||||||
|
originalFileUrl: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue