translate spoiler blocks between discord/revolt

This commit is contained in:
JandereDev 2022-08-19 18:43:31 +02:00
parent 48de9db224
commit 229e2ecb34
No known key found for this signature in database
GPG key ID: 5D5E18ACB990F57A
3 changed files with 151 additions and 78 deletions

View file

@ -1,3 +1,5 @@
{ {
"editor.formatOnSave": false "editor.formatOnSave": true,
"editor.formatOnSaveMode": "modifications",
"prettier.tabWidth": 4
} }

View file

@ -242,97 +242,141 @@ client.on('messageCreate', async message => {
} }
}); });
client.on('guildCreate', async server => { client.on("guildCreate", async (server) => {
try { try {
const me = server.me || await server.members.fetch({ user: client.user!.id }); const me =
const channels = Array.from(server.channels.cache.filter( server.me ||
c => c.permissionsFor(me).has('SEND_MESSAGES') && c.isText() (await server.members.fetch({ user: client.user!.id }));
)); const channels = Array.from(
server.channels.cache.filter(
(c) => c.permissionsFor(me).has("SEND_MESSAGES") && c.isText()
)
);
if (!channels.length) return; if (!channels.length) return;
const channel = (channels.find(c => c[0] == server.systemChannel?.id) || channels[0])?.[1] as TextChannel; const channel = (channels.find(
(c) => c[0] == server.systemChannel?.id
) || channels[0])?.[1] as TextChannel;
const message = const message =
':wave: Hi there!\n\n' + ":wave: Hi there!\n\n" +
'Thanks for adding AutoMod to this server! Please note that despite it\'s name, this bot only provides ' + "Thanks for adding AutoMod to this server! Please note that despite its name, this bot only provides " +
'bridge integration with the AutoMod bot on Revolt (<https://revolt.chat>) and does not offer any moderation ' + "bridge integration with the AutoMod bot on Revolt (<https://revolt.chat>) and does not offer any moderation " +
'features on Discord. To get started, run the `/bridge help` command!\n\n' + "features on Discord. To get started, run the `/bridge help` command!\n\n" +
'Before using AutoMod, please make sure you have read the privacy policy: <https://github.com/janderedev/automod/wiki/Privacy-Policy>\n\n' + "Before using AutoMod, please make sure you have read the privacy policy: <https://github.com/janderedev/automod/wiki/Privacy-Policy>\n\n" +
'A note to this server\'s administrators: When using the bridge, please make sure to also provide your members ' + "A note to this server's administrators: When using the bridge, please make sure to also provide your members " +
'with a link to AutoMod\'s privacy policy in an accessible place like your rules channel.'; "with a link to AutoMod's privacy policy in an accessible place like your rules channel.";
if (channel.permissionsFor(me).has('EMBED_LINKS')) { if (channel.permissionsFor(me).has("EMBED_LINKS")) {
await channel.send({ await channel.send({
embeds: [ embeds: [
new MessageEmbed() new MessageEmbed()
.setDescription(message) .setDescription(message)
.setColor('#ff6e6d') .setColor("#ff6e6d"),
] ],
}); });
} } else {
else {
await channel.send(message); await channel.send(message);
} }
} catch(e) { } catch (e) {
console.error(e); console.error(e);
} }
}); });
// Replaces @mentions and #channel mentions // Replaces @mentions and #channel mentions and modifies body to make markdown render on Revolt
async function renderMessageBody(message: string): Promise<string> { async function renderMessageBody(message: string): Promise<string> {
// Replace Tenor URLs so they render properly. // Replace Tenor URLs so they render properly.
// We have to download the page first, then extract // We have to download the page first, then extract
// the `c.tenor.com` URL from the meta tags. // the `c.tenor.com` URL from the meta tags.
// Could query autumn but that's too much effort and I already wrote this. // Could query autumn but that's too much effort and I already wrote this.
if (RE_TENOR.test(message)) { if (RE_TENOR.test(message)) {
try { try {
logger.debug('Replacing tenor URL'); logger.debug("Replacing tenor URL");
const res = await axios.get( const res = await axios.get(message, {
message,
{
headers: { headers: {
'User-Agent': 'AutoMod/1.0; https://github.com/janderedev/automod', "User-Agent":
} "AutoMod/1.0; https://github.com/janderedev/automod",
} },
); });
const metaTag = RE_TENOR_META.exec(res.data as string)?.[0]; const metaTag = RE_TENOR_META.exec(res.data as string)?.[0];
if (metaTag) { if (metaTag) {
return metaTag return metaTag
.replace('<meta class="dynamic" property="og:url" content="', '') .replace(
.replace('">', ''); '<meta class="dynamic" property="og:url" content="',
""
)
.replace('">', "");
}
} catch (e) {
logger.warn(`Replacing tenor URL failed: ${e}`);
} }
} catch(e) { logger.warn(`Replacing tenor URL failed: ${e}`) }
} }
// @mentions // @mentions
message = await smartReplace(message, RE_MENTION_USER, async (match: string) => { message = await smartReplace(
const id = match.replace('<@!', '').replace('<@', '').replace('>', ''); message,
RE_MENTION_USER,
async (match: string) => {
const id = match
.replace("<@!", "")
.replace("<@", "")
.replace(">", "");
const user = await discordFetchUser(id); const user = await discordFetchUser(id);
return `@${user?.username || id}`; return `@${user?.username || id}`;
}, { cacheMatchResults: true, maxMatches: 10 }); },
{ cacheMatchResults: true, maxMatches: 10 }
);
// #channels // #channels
message = await smartReplace(message, RE_MENTION_CHANNEL, async (match: string) => { message = await smartReplace(
const id = match.replace('<#', '').replace('>', ''); message,
RE_MENTION_CHANNEL,
async (match: string) => {
const id = match.replace("<#", "").replace(">", "");
const channel = client.channels.cache.get(id); const channel = client.channels.cache.get(id);
const bridgeCfg = channel ? await BRIDGE_CONFIG.findOne({ discord: channel.id }) : undefined; const bridgeCfg = channel
? await BRIDGE_CONFIG.findOne({ discord: channel.id })
: undefined;
const revoltChannel = bridgeCfg?.revolt const revoltChannel = bridgeCfg?.revolt
? revoltClient.channels.get(bridgeCfg.revolt) ? revoltClient.channels.get(bridgeCfg.revolt)
: undefined; : undefined;
return revoltChannel ? `<#${revoltChannel._id}>` : `#${(channel as TextChannel)?.name || id}`; return revoltChannel
}, { cacheMatchResults: true, maxMatches: 10 }); ? `<#${revoltChannel._id}>`
: `#${(channel as TextChannel)?.name || id}`;
},
{ cacheMatchResults: true, maxMatches: 10 }
);
// :emojis: // :emojis:
message = await smartReplace(message, RE_EMOJI, async (match: string) => { message = await smartReplace(
message,
RE_EMOJI,
async (match: string) => {
return match return match
.replace(/<(a?)?:/, ':\u200b') // We don't want to accidentally send an unrelated emoji, so we add a zero width space here .replace(/<(a?)?:/, ":\u200b") // We don't want to accidentally send an unrelated emoji, so we add a zero width space here
.replace(/(:\d{18}?>)/, ':'); .replace(/(:\d{18}?>)/, ":");
}, { cacheMatchResults: true }); },
{ cacheMatchResults: true }
);
message = message
// "Escape" !!Revite style spoilers!!
.replace(
/!!.+!!/g,
(match) => `!\u200b!${match.substring(2, match.length - 2)}!!`
)
// Translate ||Discord spoilers|| to !!Revite spoilers!!, while making sure multiline spoilers continue working
.replace(/\|\|.+\|\|/gs, (match) => {
return match
.substring(2, match.length - 2)
.split("\n")
.map((line) => `!!${line.replace(/!!/g, "!\u200b!")}!!`)
.join("\n");
});
return message; return message;
} }

View file

@ -258,41 +258,68 @@ client.on('message', async message => {
} }
}); });
// Replaces @mentions, #channel mentions and :emojis: // Replaces @mentions, #channel mentions, :emojis: and makes markdown features work on Discord
async function renderMessageBody(message: string): Promise<string> { async function renderMessageBody(message: string): Promise<string> {
// We don't want users to generate large amounts of database and API queries
let failsafe = 0;
// @mentions // @mentions
message = await smartReplace(message, RE_MENTION_USER, async (match) => { message = await smartReplace(
const id = match.replace('<@', '').replace('>', ''); message,
RE_MENTION_USER,
async (match) => {
const id = match.replace("<@", "").replace(">", "");
const user = await revoltFetchUser(id); const user = await revoltFetchUser(id);
return `@${user?.username || id}`; return `@${user?.username || id}`;
}, { cacheMatchResults: true, maxMatches: 10 }); },
{ cacheMatchResults: true, maxMatches: 10 }
);
// #channels // #channels
message = await smartReplace(message, RE_MENTION_CHANNEL, async (match) => { message = await smartReplace(
const id = match.replace('<#', '').replace('>', ''); message,
RE_MENTION_CHANNEL,
async (match) => {
const id = match.replace("<#", "").replace(">", "");
const channel = client.channels.get(id); const channel = client.channels.get(id);
const bridgeCfg = channel ? await BRIDGE_CONFIG.findOne({ revolt: channel._id }) : undefined; const bridgeCfg = channel
? await BRIDGE_CONFIG.findOne({ revolt: channel._id })
: undefined;
const discordChannel = bridgeCfg?.discord const discordChannel = bridgeCfg?.discord
? discordClient.channels.cache.get(bridgeCfg.discord) ? discordClient.channels.cache.get(bridgeCfg.discord)
: undefined; : undefined;
return discordChannel ? `<#${discordChannel.id}>` : `#${channel?.name || id}`; return discordChannel
}, { cacheMatchResults: true, maxMatches: 10 }); ? `<#${discordChannel.id}>`
: `#${channel?.name || id}`;
},
{ cacheMatchResults: true, maxMatches: 10 }
);
message = await smartReplace(message, RE_EMOJI, async (match) => { message = await smartReplace(
const emojiName = match.replace(/(^:)|(:$)/g, ''); message,
RE_EMOJI,
async (match) => {
const emojiName = match.replace(/(^:)|(:$)/g, "");
if (!KNOWN_EMOJI_NAMES.includes(emojiName)) return match; if (!KNOWN_EMOJI_NAMES.includes(emojiName)) return match;
const dbEmoji = await BRIDGED_EMOJIS.findOne({ name: emojiName }); const dbEmoji = await BRIDGED_EMOJIS.findOne({ name: emojiName });
if (!dbEmoji) return match; if (!dbEmoji) return match;
return `<${dbEmoji.animated ? 'a' : ''}:${emojiName}:${dbEmoji.emojiid}>`; return `<${dbEmoji.animated ? "a" : ""}:${emojiName}:${
}, { cacheMatchResults: true, maxMatches: 40 }); dbEmoji.emojiid
}>`;
},
{ cacheMatchResults: true, maxMatches: 40 }
);
message = message
// Escape ||Discord style spoilers|| since Revite doesn't support them
.replace(/\|\|.+\|\|/gs, (match) => "\\" + match)
// Translate !!Revite spoilers!! to ||Discord spoilers||
.replace(
/!!.+!!/g,
(match) => `||${match.substring(2, match.length - 2)}||`
);
return message; return message;
} }