straight to prod you go

This commit is contained in:
Lea 2024-05-08 17:48:51 +02:00
parent 9a69d15a1e
commit 8785588f44
Signed by: Lea
GPG key ID: 1BAFFE8347019C42
2 changed files with 176 additions and 42 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@ dist/
node_modules node_modules
.env .env
db.json db.json
reactionroles.json

View file

@ -1,17 +1,30 @@
import { Channel, Client, Member, Message, User } from "revolt.js"; import { Channel, Client, ClientboundNotification, Member, Message, User } from "revolt.js";
import { config } from 'dotenv'; import { config } from 'dotenv';
import { SendableEmbed } from 'revolt-api'; import { SendableEmbed } from 'revolt-api';
import { Low, JSONFile } from 'lowdb'; import { Low, JSONFile } from 'lowdb';
import { decodeTime } from 'ulid'; import { decodeTime } from 'ulid';
import sherlock from "./sherlock.js"; import sherlock from "./sherlock.js";
import axios from "axios"; import axios from "axios";
import { readFileSync } from "fs";
import path from "path";
import { AnyMxRecord } from "dns";
import { fileURLToPath } from "url";
config(); config();
const __dirname = path.dirname(fileURLToPath(import.meta.url));
type ReactionId = { message: string, channel: string, emoji: string };
type Db = { type Db = {
probation: string[]; probation: string[];
blocked: { [key: string]: { blocked: boolean, kick: boolean } }; blocked: { [key: string]: { blocked: boolean, kick: boolean } };
children: string[]; children: string[];
reaction_ids: {
nsfw_role: ReactionId,
cosmetic_roles: {
[key: string]: ReactionId,
}
}
} }
type CommandFlag = { type CommandFlag = {
@ -58,6 +71,7 @@ const COMMANDS = {
'unblock': 'Untroll a user', 'unblock': 'Untroll a user',
'child': 'Mark a user as child', 'child': 'Mark a user as child',
'notchild': 'Remove user\'s child status', 'notchild': 'Remove user\'s child status',
'setup_reactions': 'Set up reaction role messages',
'sherlock': 'Run a sherlock scan on a username', 'sherlock': 'Run a sherlock scan on a username',
} }
@ -71,10 +85,11 @@ const client = new Client({ });
client.loginBot(process.env.TOKEN); client.loginBot(process.env.TOKEN);
db.read().then(() => { db.read().then(() => {
db.data ||= { probation: [], blocked: {}, children: [] }; db.data ||= { probation: [], blocked: {}, children: [], reaction_ids: { cosmetic_roles: {}, nsfw_role: { emoji: 'a', channel: '', message: '1' } } };
db.data.probation ||= []; db.data.probation ||= [];
db.data.blocked ||= {}; db.data.blocked ||= {};
db.data.children ||= []; db.data.children ||= [];
db.data.reaction_ids ||= { cosmetic_roles: {}, nsfw_role: { emoji: 'a', channel: '', message: '1' } };
if (Array.isArray(db.data.blocked)) { if (Array.isArray(db.data.blocked)) {
console.log('Running migration: db.blocked'); console.log('Running migration: db.blocked');
@ -96,7 +111,7 @@ client.once('ready', async () => {
console.log('Got server members'); console.log('Got server members');
}); });
function embed(content: string, title?: string, type?: 'INFO'|'SUCCESS'|'WARN'|'ERROR'): SendableEmbed { function embed(content: string, title?: string, type?: 'INFO'|'SUCCESS'|'WARN'|'ERROR'|`#${string}`): SendableEmbed {
const colors = { const colors = {
'SUCCESS': 'var(--success)', 'SUCCESS': 'var(--success)',
'INFO': 'var(--status-streaming)', 'INFO': 'var(--status-streaming)',
@ -105,7 +120,7 @@ function embed(content: string, title?: string, type?: 'INFO'|'SUCCESS'|'WARN'|'
} }
return { return {
colour: colors[type || 'INFO'], colour: type ? (colors as any)[type] || type : undefined,
description: content, description: content,
title: title, title: title,
} }
@ -561,6 +576,61 @@ client.on('message', async (message) => {
break; break;
} }
case 'setup_reactions': {
db.data!.reaction_ids.cosmetic_roles = {};
const file = Object.entries(JSON.parse(readFileSync(path.join(__dirname, "..", "reactionroles.json"), "utf-8")) as { [key: string]: string });
const nsfwReactionId = '01GE2KP575W886T71MZK9Z17D6';
const nsfwMsg = await message.channel!.sendMessage({
embeds: [embed(
"React to this message to get access to the NSFW channel.\n> This channel is marked 18+, and as such we ask minors not to interact with it :01H9HFRZTGPK9PA3WBZV07BQ9Q:\n> Please also read the channel description before participating!",
"Reaction role",
"#aa557f",
)],
interactions: {
reactions: ['01GE2KP575W886T71MZK9Z17D6'],
restrict_reactions: true,
},
masquerade: {
name: message.channel?.server?.name,
avatar: message.channel?.server?.generateIconURL({ size: 256 }),
},
});
db.data!.reaction_ids.nsfw_role.channel = nsfwMsg.channel_id;
db.data!.reaction_ids.nsfw_role.message = nsfwMsg._id;
db.data!.reaction_ids.nsfw_role.emoji = nsfwReactionId;
while (file.length > 0) {
const items = file.splice(0, 15);
const msg = await message.channel!.sendMessage({
embeds: [embed(
`React to receive one of the following roles:\n\n`
+ items.map((item) => `${item[1]} ${message.channel?.server?.roles?.[item[0]].name}`).join("\n"),
"Reaction role",
"#aa557f",
)],
interactions: {
reactions: items.map((item) => encodeURIComponent(item[1].replaceAll(":", ""))),
restrict_reactions: true,
},
masquerade: {
name: message.channel?.server?.name,
avatar: message.channel?.server?.generateIconURL({ size: 256 }),
},
});
for (const item of items) {
db.data!.reaction_ids.cosmetic_roles[item[0]] = {
channel: msg.channel_id,
message: msg._id,
emoji: item[1].replaceAll(":", ""),
};
}
}
await db.write();
break;
}
case 'type': { case 'type': {
if (message.channel?.typing_ids.has(client.user!._id)) { if (message.channel?.typing_ids.has(client.user!._id)) {
message.channel.stopTyping(); message.channel.stopTyping();
@ -657,52 +727,115 @@ client.on('member/join', async (member) => {
} }
}); });
client.on('packet', async (packet) => { async function processActionButtonReactions(packet: ClientboundNotification & { type: "MessageReact" }) {
if (packet.type != 'MessageReact') return; const channel = client.channels.get(packet.channel_id);
if (packet.emoji_id != '↩️') return; const message = client.messages.get(packet.id) || await channel!.fetchMessage(packet.id);
try { if (!message || message.author_id != client.user?._id) return console.log('Ignoring react: Author mismatch');
const channel = client.channels.get(packet.channel_id);
const message = client.messages.get(packet.id) || await channel!.fetchMessage(packet.id);
if (!message || message.author_id != client.user?._id) return console.log('Ignoring react: Author mismatch');
const members = getMembers(channel!.server_id!); const members = getMembers(channel!.server_id!);
const member = members.find(m => m._id.user == packet.user_id); const member = members.find(m => m._id.user == packet.user_id);
if (!member) return console.log('Ignoring react: Could not find reacting user'); if (!member) return console.log('Ignoring react: Could not find reacting user');
const privileged = member.hasPermission(channel!.server!, 'ManageMessages'); const privileged = member.hasPermission(channel!.server!, 'ManageMessages');
if (!privileged) return console.log('Ignoring react: User is unprivileged'); if (!privileged) return console.log('Ignoring react: User is unprivileged');
let info = message.content?.match(/^\$%log action=\S+ user=[A-Z0-9]+%\$$/)?.[0]; let info = message.content?.match(/^\$%log action=\S+ user=[A-Z0-9]+%\$$/)?.[0];
if (!info) return console.log('Ignoring react: Could not extract message metadata'); if (!info) return console.log('Ignoring react: Could not extract message metadata');
let action = info.match(/action=\S+/)?.[0].substring(7), let action = info.match(/action=\S+/)?.[0].substring(7),
user = info.match(/user=[A-Z0-9]+/)?.[0].substring(5); user = info.match(/user=[A-Z0-9]+/)?.[0].substring(5);
const target = members.find(m => m._id.user == user); const target = members.find(m => m._id.user == user);
if (!target) return await channel?.sendMessage({ embeds: [ if (!target) return await channel?.sendMessage({ embeds: [
embed(`Failed to find user \`${user}\`.`, 'Message interaction failed', 'ERROR'), embed(`Failed to find user \`${user}\`.`, 'Message interaction failed', 'ERROR'),
] }); ] });
switch(action) { switch(action) {
case 'probation_add': {// Log message was for added probation, clicking button will remove it case 'probation_add': {// Log message was for added probation, clicking button will remove it
await setProbation(target, false); await setProbation(target, false);
const embed = message.embeds?.[0] as SendableEmbed; const embed = message.embeds?.[0] as SendableEmbed;
embed.description = embed.description?.replace(' Press ↩️ to undo.', ''); embed.description = embed.description?.replace(' Press ↩️ to undo.', '');
embed.colour = 'var(--status-invisible)'; embed.colour = 'var(--status-invisible)';
await message.edit({ content: '#', embeds: [ embed ] }); await message.edit({ content: '#', embeds: [ embed ] });
break; break;
}
case 'probation_remove': {
await setProbation(target, true);
const embed = message.embeds?.[0] as SendableEmbed;
embed.description = embed.description?.replace(' Press ↩️ to undo.', '');
embed.colour = 'var(--status-invisible)';
await message.edit({ content: '#', embeds: [ embed ] });
break;
}
}
if (!target) return;
}
async function processReactionRoles(messageId: string, emoji: string, userId: string, added: boolean) {
async function assignRole(member: Member, role: string, assign: boolean) {
if (assign) {
if (!member.roles?.includes(role)) {
await member.edit({ roles: [...(member.roles ?? []), role] });
} }
case 'probation_remove': { } else {
await setProbation(target, true); if (member.roles?.includes(role)) {
const embed = message.embeds?.[0] as SendableEmbed; await member.edit({ roles: member.roles.filter((r) => r != role) });
embed.description = embed.description?.replace(' Press ↩️ to undo.', '');
embed.colour = 'var(--status-invisible)';
await message.edit({ content: '#', embeds: [ embed ] });
break;
} }
} }
if (!target) return; }
} catch(e) {
console.error(e); // nsfw role needs extra processing
if (messageId == db.data?.reaction_ids.nsfw_role.message && emoji == db.data.reaction_ids.nsfw_role.emoji) {
const channel = await client.channels.fetch(db.data.reaction_ids.nsfw_role.channel);
const members = getMembers(channel.server_id!);
const member = members.find((member) => member._id.user == userId);
if (!member) return;
if (added && db.data.children.includes(userId)) {
try {
await client.api.delete(
`/channels/${channel._id}/messages/${messageId}/reactions/${emoji}` as '-/channels/{target}/messages/{msg}/reactions/{emoji}',
{ user_id: userId },
);
} catch(e) {
console.error(e);
}
await assignRole(member, process.env.NSFW_ROLE!, false).catch(); // Remove the role if it's still there for some reason
return;
}
await assignRole(member, process.env.NSFW_ROLE!, added);
return;
}
const roleConfig = Object.entries(db.data?.reaction_ids.cosmetic_roles ?? []).find(
(r) => r[1].emoji == emoji && r[1].message == messageId
);
if (!roleConfig) return;
const channel = await client.channels.fetch(roleConfig[1].channel);
const members = getMembers(channel.server_id!);
const member = members.find((member) => member._id.user == userId);
if (!member) return;
await assignRole(member, roleConfig[0], added);
}
client.on('packet', async (packet) => {
if (packet.type == 'MessageReact') {
try {
if (packet.emoji_id == '↩️') await processActionButtonReactions(packet);
await processReactionRoles(packet.id, packet.emoji_id, packet.user_id, true);
} catch(e) {
console.error(e);
}
} else if (packet.type == 'MessageUnreact') {
try {
await processReactionRoles(packet.id, packet.emoji_id, packet.user_id, false);
} catch(e) {
console.error(e);
}
} }
}); });