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,36 +242,41 @@ client.on('messageCreate', async message => {
}
});
client.on('guildCreate', async server => {
client.on("guildCreate", async (server) => {
try {
const me = server.me || 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()
));
const me =
server.me ||
(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;
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 =
':wave: Hi there!\n\n' +
'Thanks for adding AutoMod to this server! Please note that despite it\'s name, this bot only provides ' +
'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' +
'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 ' +
'with a link to AutoMod\'s privacy policy in an accessible place like your rules channel.';
":wave: Hi there!\n\n" +
"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 " +
"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" +
"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.";
if (channel.permissionsFor(me).has('EMBED_LINKS')) {
if (channel.permissionsFor(me).has("EMBED_LINKS")) {
await channel.send({
embeds: [
new MessageEmbed()
.setDescription(message)
.setColor('#ff6e6d')
]
.setColor("#ff6e6d"),
],
});
}
else {
} else {
await channel.send(message);
}
} catch (e) {
@ -279,60 +284,99 @@ client.on('guildCreate', async server => {
}
});
// 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> {
// Replace Tenor URLs so they render properly.
// We have to download the page first, then extract
// the `c.tenor.com` URL from the meta tags.
// Could query autumn but that's too much effort and I already wrote this.
if (RE_TENOR.test(message)) {
try {
logger.debug('Replacing tenor URL');
logger.debug("Replacing tenor URL");
const res = await axios.get(
message,
{
const res = await axios.get(message, {
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];
if (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
message = await smartReplace(message, RE_MENTION_USER, async (match: string) => {
const id = match.replace('<@!', '').replace('<@', '').replace('>', '');
message = await smartReplace(
message,
RE_MENTION_USER,
async (match: string) => {
const id = match
.replace("<@!", "")
.replace("<@", "")
.replace(">", "");
const user = await discordFetchUser(id);
return `@${user?.username || id}`;
}, { cacheMatchResults: true, maxMatches: 10 });
},
{ cacheMatchResults: true, maxMatches: 10 }
);
// #channels
message = await smartReplace(message, RE_MENTION_CHANNEL, async (match: string) => {
const id = match.replace('<#', '').replace('>', '');
message = await smartReplace(
message,
RE_MENTION_CHANNEL,
async (match: string) => {
const id = match.replace("<#", "").replace(">", "");
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
? revoltClient.channels.get(bridgeCfg.revolt)
: undefined;
return revoltChannel ? `<#${revoltChannel._id}>` : `#${(channel as TextChannel)?.name || id}`;
}, { cacheMatchResults: true, maxMatches: 10 });
return revoltChannel
? `<#${revoltChannel._id}>`
: `#${(channel as TextChannel)?.name || id}`;
},
{ cacheMatchResults: true, maxMatches: 10 }
);
// :emojis:
message = await smartReplace(message, RE_EMOJI, async (match: string) => {
message = await smartReplace(
message,
RE_EMOJI,
async (match: string) => {
return match
.replace(/<(a?)?:/, ':\u200b') // We don't want to accidentally send an unrelated emoji, so we add a zero width space here
.replace(/(:\d{18}?>)/, ':');
}, { cacheMatchResults: true });
.replace(/<(a?)?:/, ":\u200b") // We don't want to accidentally send an unrelated emoji, so we add a zero width space here
.replace(/(:\d{18}?>)/, ":");
},
{ 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;
}

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> {
// We don't want users to generate large amounts of database and API queries
let failsafe = 0;
// @mentions
message = await smartReplace(message, RE_MENTION_USER, async (match) => {
const id = match.replace('<@', '').replace('>', '');
message = await smartReplace(
message,
RE_MENTION_USER,
async (match) => {
const id = match.replace("<@", "").replace(">", "");
const user = await revoltFetchUser(id);
return `@${user?.username || id}`;
}, { cacheMatchResults: true, maxMatches: 10 });
},
{ cacheMatchResults: true, maxMatches: 10 }
);
// #channels
message = await smartReplace(message, RE_MENTION_CHANNEL, async (match) => {
const id = match.replace('<#', '').replace('>', '');
message = await smartReplace(
message,
RE_MENTION_CHANNEL,
async (match) => {
const id = match.replace("<#", "").replace(">", "");
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
? discordClient.channels.cache.get(bridgeCfg.discord)
: undefined;
return discordChannel ? `<#${discordChannel.id}>` : `#${channel?.name || id}`;
}, { cacheMatchResults: true, maxMatches: 10 });
return discordChannel
? `<#${discordChannel.id}>`
: `#${channel?.name || id}`;
},
{ cacheMatchResults: true, maxMatches: 10 }
);
message = await smartReplace(message, RE_EMOJI, async (match) => {
const emojiName = match.replace(/(^:)|(:$)/g, '');
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 `<${dbEmoji.animated ? "a" : ""}:${emojiName}:${
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;
}