straight to prod you go
This commit is contained in:
parent
9a69d15a1e
commit
8785588f44
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@ dist/
|
||||||
node_modules
|
node_modules
|
||||||
.env
|
.env
|
||||||
db.json
|
db.json
|
||||||
|
reactionroles.json
|
||||||
|
|
217
src/index.ts
217
src/index.ts
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue