mirror of
https://github.com/janderedev/automod.git
synced 2025-01-10 18:35:28 +00:00
339 lines
17 KiB
TypeScript
339 lines
17 KiB
TypeScript
// fuck slash commands
|
|
|
|
import { client } from "./client";
|
|
import { REST } from '@discordjs/rest';
|
|
import { Routes } from 'discord-api-types/v9';
|
|
import { BRIDGED_MESSAGES, BRIDGE_CONFIG, BRIDGE_REQUESTS, BRIDGE_USER_CONFIG, logger } from "..";
|
|
import { MessageEmbed, TextChannel } from "discord.js";
|
|
import { revoltFetchMessage, revoltFetchUser } from "../util";
|
|
import { client as revoltClient } from "../revolt/client";
|
|
|
|
const PRIVACY_POLICY_URL = 'https://github.com/janderedev/automod/wiki/Privacy-Policy';
|
|
|
|
const COMMANDS: any[] = [
|
|
{
|
|
name: 'bridge',
|
|
description: 'Confirm or delete Revolt bridges',
|
|
type: 1, // Slash command
|
|
options: [
|
|
{
|
|
name: 'confirm',
|
|
description: 'Confirm a bridge initiated from Revolt',
|
|
type: 1, // Subcommand
|
|
options: [
|
|
{
|
|
name: "id",
|
|
description: "The bridge request ID",
|
|
required: true,
|
|
type: 3,
|
|
}
|
|
],
|
|
},
|
|
{
|
|
name: 'unlink',
|
|
description: 'Unbridge the current channel',
|
|
type: 1,
|
|
},
|
|
{
|
|
name: 'help',
|
|
description: 'Usage instructions',
|
|
type: 1,
|
|
},
|
|
{
|
|
name: 'opt_out',
|
|
description: 'Opt out of having your messages bridged',
|
|
type: 1,
|
|
options: [
|
|
{
|
|
name: 'opt_out',
|
|
description: 'Whether you wish to opt out of having your messages bridged',
|
|
optional: true,
|
|
type: 5 // Boolean
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: 'status',
|
|
description: 'Find out whether this channel is bridged to Revolt',
|
|
type: 1
|
|
}
|
|
],
|
|
},
|
|
{
|
|
name: 'Message Info',
|
|
description: '',
|
|
type: 3, // Message context menu
|
|
}
|
|
];
|
|
|
|
const rest = new REST({ version: '9' }).setToken(process.env['DISCORD_TOKEN']!);
|
|
|
|
client.once('ready', async () => {
|
|
try {
|
|
logger.info(`Refreshing application commands.`);
|
|
|
|
if (process.env.NODE_ENV != 'production' && process.env.DEV_GUILD) {
|
|
await rest.put(
|
|
Routes.applicationGuildCommands(client.user!.id, process.env.DEV_GUILD),
|
|
{ body: COMMANDS },
|
|
);
|
|
logger.done(`Application commands for ${process.env.DEV_GUILD} have been updated.`);
|
|
} else {
|
|
await rest.put(
|
|
Routes.applicationCommands(client.user!.id),
|
|
{ body: COMMANDS },
|
|
);
|
|
logger.done(`Global application commands have been updated.`);
|
|
}
|
|
} catch(e) {
|
|
console.error(e);
|
|
}
|
|
});
|
|
|
|
client.on('interactionCreate', async interaction => {
|
|
try {
|
|
if (interaction.isCommand()) {
|
|
logger.debug(`Command received: /${interaction.commandName}`);
|
|
|
|
// The revolutionary Jan command handler
|
|
switch(interaction.commandName) {
|
|
case 'bridge':
|
|
if (!interaction.memberPermissions?.has('MANAGE_GUILD') &&
|
|
['confirm', 'unlink'].includes(interaction.options.getSubcommand(true))
|
|
) {
|
|
return await interaction.reply({ content: `\`MANAGE_GUILD\` permission is required for this.`, ephemeral: true });
|
|
}
|
|
|
|
const ownPerms = (interaction.channel as TextChannel).permissionsFor(client.user!)!;
|
|
switch(interaction.options.getSubcommand(true)) {
|
|
case 'confirm':
|
|
if (!ownPerms.has('MANAGE_WEBHOOKS'))
|
|
return interaction.reply('Sorry, I lack permission to manage webhooks in this channel.');
|
|
|
|
const id = interaction.options.getString('id', true);
|
|
const request = await BRIDGE_REQUESTS.findOne({ id: id });
|
|
if (!request || request.expires < Date.now()) return await interaction.reply('Unknown ID.');
|
|
|
|
const bridgedCount = await BRIDGE_CONFIG.count({ discord: interaction.channelId });
|
|
if (bridgedCount > 0) return await interaction.reply('This channel is already bridged.');
|
|
|
|
const webhook = await (interaction.channel as TextChannel)
|
|
.createWebhook('AutoMod Bridge', { avatar: client.user?.avatarURL() });
|
|
|
|
await BRIDGE_REQUESTS.remove({ id: id });
|
|
await BRIDGE_CONFIG.insert({
|
|
discord: interaction.channelId,
|
|
revolt: request.revolt,
|
|
discordWebhook: {
|
|
id: webhook.id,
|
|
token: webhook.token || '',
|
|
}
|
|
});
|
|
|
|
return await interaction.reply(`✅ Channel bridged!`);
|
|
case 'unlink':
|
|
const res = await BRIDGE_CONFIG.findOneAndDelete({ discord: interaction.channelId });
|
|
if (res?._id) {
|
|
await interaction.reply('Channel unbridged.');
|
|
if (ownPerms.has('MANAGE_WEBHOOKS') && res.discordWebhook) {
|
|
try {
|
|
const hooks = await (interaction.channel as TextChannel).fetchWebhooks();
|
|
if (hooks.get(res?.discordWebhook?.id)) await hooks.get(res?.discordWebhook?.id)
|
|
?.delete('Channel has been unbridged');
|
|
} catch(_) {}
|
|
}
|
|
}
|
|
else await interaction.reply('This channel is not bridged.');
|
|
|
|
break;
|
|
|
|
case 'help':
|
|
const isPrivileged = !!interaction.memberPermissions?.has('MANAGE_GUILD');
|
|
const INVITE_URL = `https://discord.com/api/oauth2/authorize?client_id=${client.user?.id}&permissions=536996864&scope=bot%20applications.commands`;
|
|
const embed = new MessageEmbed()
|
|
.setColor('#ff6e6d')
|
|
.setAuthor({ name: 'AutoMod Revolt Bridge', iconURL: client.user?.displayAvatarURL() });
|
|
|
|
embed.setDescription(
|
|
'[AutoMod](https://automod.me) is a utility and moderation bot for [Revolt](https://revolt.chat). ' +
|
|
'This Discord bot allows you to link your Discord servers to your Revolt servers ' +
|
|
'by mirroring messages between text channels.'
|
|
);
|
|
|
|
embed.addField(
|
|
'Setting up a bridge',
|
|
isPrivileged
|
|
? 'The bridge process is initialized by running the `/bridge link` command in the Revolt ' +
|
|
'channel you wish to bridge.\n' +
|
|
'Afterwards you can run the `/bridge confirm` command in the correct Discord channel to finish the link.'
|
|
: 'You don\'t have `Manage Messages` permission - Please ask a moderator to configure the bridge.'
|
|
);
|
|
|
|
embed.addField(
|
|
'Adding AutoMod to your server',
|
|
`You can add the Revolt bot to your server ` +
|
|
`[here](https://app.revolt.chat/bot/${revoltClient.user?._id} "Open Revolt"). To add the Discord counterpart, ` +
|
|
`click [here](${INVITE_URL} "Add Discord bot").`
|
|
);
|
|
|
|
embed.addField(
|
|
'Contact',
|
|
`If you have any questions regarding this bot or the Revolt counterpart, feel free to join ` +
|
|
`[this](https://discord.gg/4pZgvqgYJ8) Discord server or [this](https://rvlt.gg/jan) Revolt server.\n` +
|
|
`If you want to report a bug, suggest a feature or browse the source code, ` +
|
|
`feel free to do so [on GitHub](https://github.com/janderedev/automod).\n` +
|
|
`For other inquiries, please contact \`contact@automod.me\`.\n\n` +
|
|
`Before using this bot, please read the [Privacy Policy](${PRIVACY_POLICY_URL})!`
|
|
);
|
|
|
|
await interaction.reply({ embeds: [ embed ], ephemeral: true });
|
|
break;
|
|
|
|
case 'opt_out':
|
|
const optOut = interaction.options.getBoolean('opt_out', false);
|
|
if (optOut == null) {
|
|
const userConfig = await BRIDGE_USER_CONFIG.findOne({ id: interaction.user.id });
|
|
if (userConfig?.optOut) {
|
|
return await interaction.reply({
|
|
ephemeral: true,
|
|
content: 'You are currently **opted out** of message bridging. ' +
|
|
'Users on Revolt **will not** see your username, avatar or message content.'
|
|
});
|
|
} else {
|
|
return await interaction.reply({
|
|
ephemeral: true,
|
|
content: 'You are currently **not** opted out of message bridging. ' +
|
|
'All your messages in a bridged channel will be sent to the associated Revolt channel.'
|
|
});
|
|
}
|
|
} else {
|
|
await BRIDGE_USER_CONFIG.update(
|
|
{ id: interaction.user.id },
|
|
{
|
|
$setOnInsert: { id: interaction.user.id },
|
|
$set: { optOut },
|
|
},
|
|
{ upsert: true }
|
|
);
|
|
|
|
return await interaction.reply({
|
|
ephemeral: true,
|
|
content: `You have **opted ${optOut ? 'out of' : 'into'}** message bridging. `
|
|
+ (
|
|
optOut
|
|
? 'Your username, avatar and message content will no longer be visible on Revolt.\n' +
|
|
'Please note that some servers may be configured to automatically delete your messages.'
|
|
: 'All your messages in a bridged channel will be sent to the associated Revolt channel.'
|
|
),
|
|
});
|
|
}
|
|
break;
|
|
|
|
case 'status':
|
|
const bridgeConfig = await BRIDGE_CONFIG.findOne({ discord: interaction.channelId });
|
|
|
|
if (!bridgeConfig?.revolt) {
|
|
return await interaction.reply({
|
|
ephemeral: true,
|
|
content: 'This channel is **not** bridged. No message content data will be processed.',
|
|
});
|
|
}
|
|
else {
|
|
return await interaction.reply({
|
|
ephemeral: true,
|
|
content: 'This channel is **bridged to Revolt**. Your messages will ' +
|
|
'be processed and sent to [Revolt](<https://revolt.chat>) according to AutoMod\'s ' +
|
|
`[Privacy Policy](<${PRIVACY_POLICY_URL}>).`,
|
|
});
|
|
}
|
|
|
|
break;
|
|
|
|
default: await interaction.reply('Unknown subcommand');
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
else if (interaction.isMessageContextMenu()) {
|
|
logger.debug(`Received context menu: ${interaction.targetMessage.id}`);
|
|
|
|
switch(interaction.commandName) {
|
|
case 'Message Info':
|
|
const message = interaction.targetMessage;
|
|
const bridgeInfo = await BRIDGED_MESSAGES.findOne({ "discord.messageId": message.id });
|
|
const messageUrl = `https://discord.com/channels/${interaction.guildId}/${interaction.channelId}/${message.id}`;
|
|
|
|
if (!bridgeInfo) return await interaction.reply({
|
|
ephemeral: true,
|
|
embeds: [
|
|
new MessageEmbed()
|
|
.setAuthor({ name: 'Message info', url: messageUrl })
|
|
.setDescription('This message has not been bridged.')
|
|
.setColor('#7e96ff'),
|
|
],
|
|
});
|
|
else {
|
|
const embed = new MessageEmbed();
|
|
|
|
embed.setColor('#7e96ff');
|
|
embed.setAuthor({ name: 'Message info', url: messageUrl });
|
|
|
|
embed.addField('Origin', bridgeInfo.origin == 'discord' ? 'Discord' : 'Revolt', true);
|
|
|
|
if (bridgeInfo.origin == 'discord') {
|
|
embed.addField(
|
|
'Bridge Status',
|
|
bridgeInfo.revolt.messageId
|
|
? 'Bridged'
|
|
: bridgeInfo.revolt.nonce
|
|
? 'ID unknown'
|
|
: 'Unbridged',
|
|
true
|
|
);
|
|
} else {
|
|
embed.addField(
|
|
'Bridge Status',
|
|
bridgeInfo.discord.messageId
|
|
? 'Bridged'
|
|
: 'Unbridged',
|
|
true
|
|
);
|
|
|
|
if (bridgeInfo.channels?.revolt) {
|
|
const channel = await revoltClient.channels.get(bridgeInfo.channels.revolt);
|
|
const revoltMsg = await revoltFetchMessage(bridgeInfo.revolt.messageId, channel);
|
|
|
|
if (revoltMsg) {
|
|
const author = await revoltFetchUser(revoltMsg.author_id);
|
|
embed.addField(
|
|
'Message Author',
|
|
`**@${author?.username}** (${revoltMsg.author_id})`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
embed.addField(
|
|
'Bridge Data',
|
|
`Origin: \`${bridgeInfo.origin}\`\n` +
|
|
`Discord ID: \`${bridgeInfo.discord.messageId}\`\n` +
|
|
`Revolt ID: \`${bridgeInfo.revolt.messageId}\`\n` +
|
|
`Revolt Nonce: \`${bridgeInfo.revolt.nonce}\`\n` +
|
|
`Discord Channel: \`${bridgeInfo.channels?.discord}\`\n` +
|
|
`Revolt Channel: \`${bridgeInfo.channels?.revolt}\``
|
|
);
|
|
|
|
return await interaction.reply({
|
|
ephemeral: true,
|
|
embeds: [ embed ],
|
|
});
|
|
}
|
|
}
|
|
}
|
|
} catch(e) {
|
|
console.error(e);
|
|
if (interaction.isCommand()) interaction.reply('An error has occurred: ' + e).catch(() => {});
|
|
}
|
|
});
|