Refactor bot. (#8)
This commit is contained in:
parent
e0374bea36
commit
259d540e6f
18 changed files with 491 additions and 300 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
/index.js
|
/index.js
|
||||||
dist
|
dist
|
||||||
|
.env
|
||||||
|
|
169
commands.ts
169
commands.ts
|
@ -1,169 +0,0 @@
|
||||||
import type { Client, Message } from 'discord.js';
|
|
||||||
import fetch from 'node-fetch';
|
|
||||||
|
|
||||||
type Commands = {
|
|
||||||
[cmd: string]: (c: Client, e: Message) => void | Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const commands: Commands = {
|
|
||||||
'!ping': async (c, e) => {
|
|
||||||
await e.reply(`${c.ws.ping}ms`);
|
|
||||||
},
|
|
||||||
|
|
||||||
'!why': async (c, e) => {
|
|
||||||
await e.reply({
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: 'Why PolyMC exists',
|
|
||||||
description:
|
|
||||||
'https://polymc.org/wiki/overview/faq/#why-did-our-community-choose-to-fork\nhttps://polymc.org/news/moving-on/',
|
|
||||||
color: 'GREYPLE',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
'!paths': async (c, e) => {
|
|
||||||
await e.reply({
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: 'Data directories',
|
|
||||||
description: 'Where PolyMC stores your data (e.g. instances)',
|
|
||||||
color: 'AQUA',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'Portable (Windows / Linux)',
|
|
||||||
value: 'In the PolyMC folder',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Windows',
|
|
||||||
value: '`%APPDATA%/PolyMC`',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'macOS',
|
|
||||||
value: '`~/Library/Application Support/PolyMC`',
|
|
||||||
},
|
|
||||||
{ name: 'Linux', value: '`~/.local/share/PolyMC`' },
|
|
||||||
{
|
|
||||||
name: 'Flatpak',
|
|
||||||
value: '`~/.var/app/org.polymc.PolyMC/data/PolyMC`',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
'!cursed': async (c, e) => {
|
|
||||||
await e.reply({
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: "What's wrong with CurseForge?",
|
|
||||||
description: `
|
|
||||||
CurseForge added a new option to block third party clients like PolyMC from accessing mod files, and they started to enforce this option lately.
|
|
||||||
|
|
||||||
We can't allow you to download those mods directly from PolyMC, but PolyMC 1.3.1 and higher have a workaround to let modpacks work: letting you to download those opted out mods manually.
|
|
||||||
We highly encourage asking authors that opted out to stop doing so.
|
|
||||||
`.trim(),
|
|
||||||
color: 'ORANGE',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
'!migrate': async (c, e) => {
|
|
||||||
await e.reply('https://polymc.org/wiki/getting-started/migrating-multimc/');
|
|
||||||
},
|
|
||||||
|
|
||||||
'!build': async (c, e) => {
|
|
||||||
await e.reply('https://polymc.org/wiki/development/build-instructions/');
|
|
||||||
},
|
|
||||||
|
|
||||||
'!java': async (c, e) => {
|
|
||||||
await e.reply('https://polymc.org/wiki/getting-started/installing-java/');
|
|
||||||
},
|
|
||||||
|
|
||||||
'!eta': async (c, e) => {
|
|
||||||
await e.reply('Sometime');
|
|
||||||
},
|
|
||||||
|
|
||||||
'!members': async (c, e) => {
|
|
||||||
const mems = await e.guild?.members.fetch().then((r) => r.toJSON());
|
|
||||||
if (!mems) return;
|
|
||||||
|
|
||||||
await e.reply({
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: `${mems.length} total members!`,
|
|
||||||
description: `${
|
|
||||||
mems.filter(
|
|
||||||
(m) =>
|
|
||||||
m.presence?.status === 'online' ||
|
|
||||||
m.presence?.status === 'idle' ||
|
|
||||||
m.presence?.status === 'dnd'
|
|
||||||
).length
|
|
||||||
} online members, and ${
|
|
||||||
mems.filter((m) => m.presence?.status === 'invisible').length
|
|
||||||
} members that are pretending to be offline`,
|
|
||||||
color: 'GOLD',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
'!stars': async (c, e) => {
|
|
||||||
const count = await fetch('https://api.github.com/repos/PolyMC/PolyMC')
|
|
||||||
.then((r) => r.json() as Promise<{ stargazers_count: number }>)
|
|
||||||
.then((j) => j.stargazers_count);
|
|
||||||
|
|
||||||
await e.reply({
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: `⭐ ${count} total stars!`,
|
|
||||||
color: 'GOLD',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// '!polycatgen': async (c, e) => {
|
|
||||||
// if (!e.guild) return;
|
|
||||||
// if (
|
|
||||||
// e.channelId !== POLYCAT_CHANNEL_ID &&
|
|
||||||
// process.env.NODE_ENV !== 'development'
|
|
||||||
// )
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// await e.guild.emojis.fetch();
|
|
||||||
// const polycat = e.guild.emojis.cache.find(
|
|
||||||
// (emoji) => emoji.name?.toLowerCase() === 'polycat'
|
|
||||||
// );
|
|
||||||
|
|
||||||
// await e.reply(
|
|
||||||
// `.\n${polycat}${polycat}${polycat}${polycat}${polycat}\n${polycat}${polycat}${polycat}${polycat}${polycat}\n${polycat}${polycat}${polycat}${polycat}${polycat}\n${polycat}${polycat}${polycat}${polycat}${polycat}\n${polycat}${polycat}${polycat}${polycat}${polycat}`
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
|
|
||||||
'!piracy': async (c, e) => {
|
|
||||||
await e.reply({
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: "We don't tolerate piracy!",
|
|
||||||
description:
|
|
||||||
"PolyMC has always been legal, legitimate & appropriate. We don't and never will have features such as offline login without an official account.",
|
|
||||||
color: 'DARK_RED',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const aliases: { [a: string]: string } = {
|
|
||||||
'!curse': '!cursed',
|
|
||||||
'!curseforge': '!cursed',
|
|
||||||
'!cf': '!cursed',
|
|
||||||
'!diff': '!why',
|
|
||||||
'!migr': '!migrate',
|
|
||||||
'!j': '!java',
|
|
||||||
'!multimc': '!migrate',
|
|
||||||
};
|
|
121
index.ts
121
index.ts
|
@ -1,121 +0,0 @@
|
||||||
import { Client, Intents } from 'discord.js';
|
|
||||||
import { commands, aliases } from './commands';
|
|
||||||
|
|
||||||
import * as BuildConfig from './constants';
|
|
||||||
import { isBad } from './badLinks';
|
|
||||||
|
|
||||||
import { green, bold, blue, underline, yellow } from 'kleur/colors';
|
|
||||||
import urlRegex from 'url-regex';
|
|
||||||
import removeMarkdown from 'remove-markdown';
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: [
|
|
||||||
Intents.FLAGS.GUILDS,
|
|
||||||
Intents.FLAGS.GUILD_MESSAGES,
|
|
||||||
Intents.FLAGS.DIRECT_MESSAGES,
|
|
||||||
Intents.FLAGS.GUILD_MEMBERS,
|
|
||||||
Intents.FLAGS.GUILD_PRESENCES,
|
|
||||||
Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
|
|
||||||
Intents.FLAGS.GUILD_BANS,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(process.env.DISCORD_TOKEN);
|
|
||||||
|
|
||||||
client.once('ready', async () => {
|
|
||||||
console.log(green('Discord bot ready!'));
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'development')
|
|
||||||
console.warn(yellow(bold('Running in production mode!')));
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
'Invite link:',
|
|
||||||
blue(
|
|
||||||
underline(
|
|
||||||
client.generateInvite({
|
|
||||||
scopes: ['bot'],
|
|
||||||
permissions: ['ADMINISTRATOR'],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const POLYMC_GUILD = await client.guilds.fetch(BuildConfig.GUILD_ID);
|
|
||||||
const DEBUG_CHANNEL = POLYMC_GUILD.channels.cache.get(
|
|
||||||
BuildConfig.DEBUG_CHANNEL_ID
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!DEBUG_CHANNEL || !DEBUG_CHANNEL.isText()) throw new Error();
|
|
||||||
DEBUG_CHANNEL.send({
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: 'Started!',
|
|
||||||
description: new Date().toISOString(),
|
|
||||||
color: 'AQUA',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('messageCreate', async (e) => {
|
|
||||||
if (!e.content) return;
|
|
||||||
if (!e.channel.isText()) return;
|
|
||||||
if (e.author === client.user) return;
|
|
||||||
|
|
||||||
if (
|
|
||||||
process.env.NODE_ENV === 'development' &&
|
|
||||||
e.channelId !== BuildConfig.DEBUG_CHANNEL_ID
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
} else if (
|
|
||||||
process.env.NODE_ENV !== 'development' &&
|
|
||||||
e.channelId === BuildConfig.DEBUG_CHANNEL_ID
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// phishing link filter
|
|
||||||
{
|
|
||||||
const urlMatches = [...e.content.matchAll(urlRegex())];
|
|
||||||
|
|
||||||
if (urlMatches.length) {
|
|
||||||
console.log('Found links in message from', e.author.tag);
|
|
||||||
|
|
||||||
for (const match of urlMatches) {
|
|
||||||
console.log('[link]', match[0]);
|
|
||||||
if (await isBad(match[0])) {
|
|
||||||
await e.reply({
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: 'Hold on!',
|
|
||||||
description:
|
|
||||||
'There seems to be a phishing / malware link in your message.',
|
|
||||||
color: 'RED',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// neat
|
|
||||||
{
|
|
||||||
const cleanContent = removeMarkdown(e.content).toLowerCase();
|
|
||||||
if (cleanContent.split(' ').includes('neat')) {
|
|
||||||
console.log('[neat]', cleanContent);
|
|
||||||
await e.reply('Neat is a mod by Vazkii.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cmd = e.content.split(' ')[0];
|
|
||||||
if (!cmd.startsWith('!')) return;
|
|
||||||
let func = commands[cmd];
|
|
||||||
func = func ?? commands[aliases[cmd]];
|
|
||||||
|
|
||||||
if (func !== undefined) {
|
|
||||||
await func(client, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
10
package.json
10
package.json
|
@ -1,15 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "polly",
|
"name": "polly",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "NODE_ENV=development nodemon --watch index.ts --exec esno index.ts",
|
"dev": "NODE_ENV=development nodemon --ext ts,json --watch src --exec esno src/index.ts",
|
||||||
"build": "esbuild index.ts --format=cjs --platform=node --target=node17 --minify --bundle --sourcemap --external:discord.js --outdir=dist",
|
"build": "tsc",
|
||||||
"lint": "eslint **/*.ts"
|
"lint": "eslint **/*.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cliqz/adblocker": "^1.23.8",
|
"@cliqz/adblocker": "^1.23.8",
|
||||||
|
"discord-command-parser": "^1.5.3",
|
||||||
"discord.js": "^13.7.0",
|
"discord.js": "^13.7.0",
|
||||||
|
"dotenv": "^16.0.1",
|
||||||
"kleur": "^4.1.4",
|
"kleur": "^4.1.4",
|
||||||
"node-fetch": "^3.2.4",
|
"node-fetch": "^3.2.4",
|
||||||
"remove-markdown": "^0.5.0",
|
"remove-markdown": "^0.5.0",
|
||||||
|
@ -17,7 +19,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esbuild-plugins/node-resolve": "^0.1.4",
|
"@esbuild-plugins/node-resolve": "^0.1.4",
|
||||||
"@types/remove-markdown": "^0.3.1",
|
"@types/bad-words": "^3.0.1",
|
||||||
"@types/node": "^17.0.38",
|
"@types/node": "^17.0.38",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.25.0",
|
"@typescript-eslint/eslint-plugin": "^5.25.0",
|
||||||
"@typescript-eslint/parser": "^5.25.0",
|
"@typescript-eslint/parser": "^5.25.0",
|
||||||
|
|
32
src/commands/help.ts
Normal file
32
src/commands/help.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { MessageEmbed } from 'discord.js';
|
||||||
|
import { commands } from '..';
|
||||||
|
import { Command } from '..';
|
||||||
|
|
||||||
|
export const cmd: Command = {
|
||||||
|
name: 'help',
|
||||||
|
desc: 'Shows this menu.',
|
||||||
|
exec: async (e) => {
|
||||||
|
const embed = new MessageEmbed()
|
||||||
|
.setTitle('Help Menu')
|
||||||
|
.setColor('DARK_GREEN');
|
||||||
|
let comman = commands;
|
||||||
|
comman.sort((x, y) => {
|
||||||
|
return x.name == 'help' ? -1 : y.name == 'help' ? 1 : 0;
|
||||||
|
});
|
||||||
|
for (const i in comman) {
|
||||||
|
const cmd = comman[i];
|
||||||
|
const resp = [];
|
||||||
|
if (cmd.desc) {
|
||||||
|
resp.push(cmd.desc);
|
||||||
|
}
|
||||||
|
if (cmd.aliases && cmd.aliases[0]) {
|
||||||
|
resp.push(`**Aliases**: ${cmd.aliases.join(', ')}`);
|
||||||
|
}
|
||||||
|
if (cmd.examples && cmd.examples[0]) {
|
||||||
|
resp.push(`**Examples**: \n${cmd.examples.join('\n> ')}`);
|
||||||
|
}
|
||||||
|
embed.addField('!' + cmd.name, resp.join('\n'));
|
||||||
|
}
|
||||||
|
return e.reply({ embeds: [embed] });
|
||||||
|
},
|
||||||
|
};
|
28
src/commands/members.ts
Normal file
28
src/commands/members.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { Command } from '../index';
|
||||||
|
|
||||||
|
export const cmd: Command = {
|
||||||
|
name: 'members',
|
||||||
|
desc: 'Shows the amount of online users in PolyMC Discord',
|
||||||
|
aliases: ['mems', 'memcount'],
|
||||||
|
exec: async (e) => {
|
||||||
|
const memes = await e.guild?.members.fetch().then((r) => r.toJSON());
|
||||||
|
if (!memes) return;
|
||||||
|
|
||||||
|
return e.reply({
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: `${memes.length} total members!`,
|
||||||
|
description: `${
|
||||||
|
memes.filter(
|
||||||
|
(m) =>
|
||||||
|
m.presence?.status === 'online' ||
|
||||||
|
m.presence?.status === 'idle' ||
|
||||||
|
m.presence?.status === 'dnd'
|
||||||
|
).length
|
||||||
|
} online members`,
|
||||||
|
color: 'GOLD',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
10
src/commands/ping.ts
Normal file
10
src/commands/ping.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { Command } from '../index';
|
||||||
|
|
||||||
|
export const cmd: Command = {
|
||||||
|
name: 'ping',
|
||||||
|
desc: 'Shows the ping of the bot',
|
||||||
|
aliases: ['test'],
|
||||||
|
exec: async (e) => {
|
||||||
|
return await e.reply(`${e.client.ws.ping}ms`);
|
||||||
|
},
|
||||||
|
};
|
20
src/commands/stars.ts
Normal file
20
src/commands/stars.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { Command } from '../index';
|
||||||
|
|
||||||
|
export const cmd: Command = {
|
||||||
|
name: 'stars',
|
||||||
|
desc: 'Shows the number of stars in PolyMC',
|
||||||
|
aliases: ['star', 'stargazers'],
|
||||||
|
exec: async (e) => {
|
||||||
|
const count = await fetch('https://api.github.com/repos/PolyMC/PolyMC')
|
||||||
|
.then((r) => r.json() as Promise<{ stargazers_count: number }>)
|
||||||
|
.then((j) => j.stargazers_count);
|
||||||
|
return e.reply({
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: `⭐ ${count} total stars!`,
|
||||||
|
color: 'GOLD',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
27
src/commands/tags.ts
Normal file
27
src/commands/tags.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { MessageEmbed } from 'discord.js';
|
||||||
|
import { Command } from '../index';
|
||||||
|
import { tags } from '../index';
|
||||||
|
|
||||||
|
export const cmd: Command = {
|
||||||
|
name: 'tags',
|
||||||
|
desc: 'Lists the tags available',
|
||||||
|
exec: async (e) => {
|
||||||
|
const em = new MessageEmbed().setTitle('tags').setColor('DARK_GREEN');
|
||||||
|
|
||||||
|
for (let i in tags) {
|
||||||
|
const tag = tags[i];
|
||||||
|
let text = '';
|
||||||
|
if (tag.aliases && tag.aliases[0]) {
|
||||||
|
text += '**Aliases**: ' + tag.aliases.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag.text) {
|
||||||
|
text += tag.text;
|
||||||
|
} else if (tag.embed) {
|
||||||
|
text += '\n[embedded message]';
|
||||||
|
}
|
||||||
|
em.addField(tag.name, text);
|
||||||
|
}
|
||||||
|
return e.reply({ embeds: [em] });
|
||||||
|
},
|
||||||
|
};
|
34
src/filters.ts
Normal file
34
src/filters.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import * as BuildConfig from './constants';
|
||||||
|
import { Message } from 'discord.js';
|
||||||
|
import { isBad } from './badLinks';
|
||||||
|
import urlRegex from 'url-regex';
|
||||||
|
|
||||||
|
// true if message is ok, false if filtered
|
||||||
|
export async function filterMessage(e: Message): Promise<boolean> {
|
||||||
|
// url matcher
|
||||||
|
const urlMatches = [...e.content.matchAll(urlRegex())];
|
||||||
|
|
||||||
|
if (urlMatches.length) {
|
||||||
|
console.log('Found links in message from', e.author.tag);
|
||||||
|
|
||||||
|
for (const match of urlMatches) {
|
||||||
|
console.log('[link]', match[0]);
|
||||||
|
if (await isBad(match[0])) {
|
||||||
|
await e.reply({
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: 'Hold on!',
|
||||||
|
description:
|
||||||
|
'There seems to be a phishing / malware link in your message.',
|
||||||
|
color: 'RED',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
157
src/index.ts
Normal file
157
src/index.ts
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import {
|
||||||
|
Client,
|
||||||
|
Intents,
|
||||||
|
Message,
|
||||||
|
MessageEmbed,
|
||||||
|
MessageEmbedOptions,
|
||||||
|
} from 'discord.js';
|
||||||
|
|
||||||
|
import * as BuildConfig from './constants';
|
||||||
|
import { filterMessage } from './filters';
|
||||||
|
import { green, bold, blue, underline, yellow } from 'kleur/colors';
|
||||||
|
import * as parser from 'discord-command-parser';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path, { dirname } from 'path';
|
||||||
|
import { SuccessfulParsedMessage } from 'discord-command-parser';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import { parseLog } from './mclogs';
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export interface Command {
|
||||||
|
name: string;
|
||||||
|
aliases?: Array<string>;
|
||||||
|
desc?: string;
|
||||||
|
examples?: Array<string>;
|
||||||
|
exec(
|
||||||
|
m: Message,
|
||||||
|
p: SuccessfulParsedMessage<Message<boolean>>
|
||||||
|
): Promise<Message> | Promise<any> | any;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Commands = Array<Command>;
|
||||||
|
export let commands: Commands = [];
|
||||||
|
|
||||||
|
interface Tag {
|
||||||
|
name: string;
|
||||||
|
aliases?: Array<string>;
|
||||||
|
text?: string;
|
||||||
|
embed?: MessageEmbedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tags = Array<Tag>;
|
||||||
|
export const tags: Tags = JSON.parse(
|
||||||
|
fs.readFileSync(path.join(__dirname, 'tags.json'), 'utf8')
|
||||||
|
);
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
intents: [
|
||||||
|
Intents.FLAGS.GUILDS,
|
||||||
|
Intents.FLAGS.GUILD_MESSAGES,
|
||||||
|
Intents.FLAGS.DIRECT_MESSAGES,
|
||||||
|
Intents.FLAGS.GUILD_MEMBERS,
|
||||||
|
Intents.FLAGS.GUILD_PRESENCES,
|
||||||
|
Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
|
||||||
|
Intents.FLAGS.GUILD_BANS,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dir = fs.readdirSync(path.join(__dirname, '/commands'));
|
||||||
|
for (const i in dir) {
|
||||||
|
const cmdName = dir[i];
|
||||||
|
const cmd: Command = require(path.join(__dirname, '/commands/', cmdName)).cmd;
|
||||||
|
commands.push(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.once('ready', async () => {
|
||||||
|
console.log(green('Discord bot ready!'));
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'development')
|
||||||
|
console.warn(yellow(bold('Running in production mode!')));
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'Invite link:',
|
||||||
|
blue(
|
||||||
|
underline(
|
||||||
|
client.generateInvite({
|
||||||
|
scopes: ['bot'],
|
||||||
|
permissions: ['ADMINISTRATOR'],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const POLYMC_GUILD = await client.guilds.fetch(BuildConfig.GUILD_ID);
|
||||||
|
|
||||||
|
client.on('messageCreate', async (e) => {
|
||||||
|
if (!e.content) return;
|
||||||
|
if (!e.channel.isText()) return;
|
||||||
|
if (e.author === client.user) return;
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV === 'development' &&
|
||||||
|
e.channelId !== BuildConfig.DEBUG_CHANNEL_ID
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
} else if (
|
||||||
|
process.env.NODE_ENV !== 'development' &&
|
||||||
|
e.channelId === BuildConfig.DEBUG_CHANNEL_ID
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commanded = await parseMsg(e);
|
||||||
|
if (commanded) return;
|
||||||
|
|
||||||
|
const log = await parseLog(e.content);
|
||||||
|
if (log != null) {
|
||||||
|
e.reply({ embeds: [log] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filtered = await filterMessage(e);
|
||||||
|
if (!filtered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function parseMsg(e: Message) {
|
||||||
|
const parsed = parser.parse(e, '!', {
|
||||||
|
allowBots: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!parsed.success) return false;
|
||||||
|
const cmd = commands.find(
|
||||||
|
(c) => c.name == parsed.command || c.aliases?.includes(parsed.command)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!cmd) {
|
||||||
|
const tag = tags.find(
|
||||||
|
(t) => t.name == parsed.command || t.aliases?.includes(parsed.command)
|
||||||
|
);
|
||||||
|
if (tag) {
|
||||||
|
if (tag.text) {
|
||||||
|
e.reply(tag.text);
|
||||||
|
return true;
|
||||||
|
} else if (tag.embed) {
|
||||||
|
const em = new MessageEmbed(tag.embed);
|
||||||
|
e.reply({ embeds: [em] });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await cmd.exec(e, parsed);
|
||||||
|
} catch (err: any) {
|
||||||
|
// ts moment
|
||||||
|
const em = new MessageEmbed()
|
||||||
|
.setTitle('Error')
|
||||||
|
.setColor('RED')
|
||||||
|
.setDescription(err);
|
||||||
|
e.reply({ embeds: [em] });
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.login(process.env.DISCORD_TOKEN);
|
75
src/mclogs.ts
Normal file
75
src/mclogs.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import { getLatest } from './version';
|
||||||
|
import { MessageEmbed } from 'discord.js';
|
||||||
|
const reg = /https\:\/\/mclo.gs\/[^ ]*/g;
|
||||||
|
|
||||||
|
type analyzer = (text: string) => Promise<Array<string> | null>;
|
||||||
|
const javaAnalyzer: analyzer = async (text) => {
|
||||||
|
if (text.includes('This instance is not compatible with Java version')) {
|
||||||
|
const xp =
|
||||||
|
/Please switch to one of the following Java versions for this instance:[\r\n]+([^\r\n]+)/g;
|
||||||
|
|
||||||
|
let ver: string;
|
||||||
|
const m = text.match(xp);
|
||||||
|
if (!m || !m[0]) {
|
||||||
|
ver = '';
|
||||||
|
} else {
|
||||||
|
ver = m[0].split('\n')[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'WrongJavaVersion',
|
||||||
|
`Please switch to the following: \`${ver}\`\nFor more information, type \`!java\``,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const versionAnalyzer: analyzer = async (text) => {
|
||||||
|
const vers = text.match(/PolyMC version: [0-9].[0-9].[0-9]/g);
|
||||||
|
if (vers && vers[0]) {
|
||||||
|
const latest = await getLatest();
|
||||||
|
const current = vers[0].replace('PolyMC version: ', '');
|
||||||
|
if (latest != current) {
|
||||||
|
return [
|
||||||
|
'OutdatedPolyMC',
|
||||||
|
`Your installed version is ${current}, while the newest version is ${latest}.\nPlease update, for more info see https://polymc.org/download/`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const analyzers: analyzer[] = [javaAnalyzer, versionAnalyzer];
|
||||||
|
|
||||||
|
export async function parseLog(s: string): Promise<MessageEmbed | null> {
|
||||||
|
const r = s.match(reg);
|
||||||
|
if (r == null || !r[0]) return null;
|
||||||
|
const link = r[0]; // for now only first url
|
||||||
|
const id = link.replace('https://mclo.gs/', '');
|
||||||
|
if (!id) return null;
|
||||||
|
const apiUrl = 'https://api.mclo.gs/1/raw/' + id;
|
||||||
|
let log: string;
|
||||||
|
try {
|
||||||
|
const f = await fetch(apiUrl);
|
||||||
|
if (f.status != 200) {
|
||||||
|
throw 'nope';
|
||||||
|
}
|
||||||
|
log = await f.text();
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const embed = new MessageEmbed()
|
||||||
|
.setTitle('Log analyzer')
|
||||||
|
.setColor('DARK_GREEN')
|
||||||
|
.setDescription(`Analysis of ${link} [${apiUrl}] [ID: ${id}]`);
|
||||||
|
for (let i in analyzers) {
|
||||||
|
const analyzer = analyzers[i];
|
||||||
|
const out = await analyzer(log);
|
||||||
|
if (out) embed.addField(out[0], out[1]);
|
||||||
|
}
|
||||||
|
if (embed.fields[0]) return embed;
|
||||||
|
else {
|
||||||
|
embed.addField('Analyze failed', 'No issues found automatically');
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
}
|
76
src/tags.json
Normal file
76
src/tags.json
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "migrate",
|
||||||
|
"text": "https://polymc.org/wiki/getting-started/migrating-multimc/",
|
||||||
|
"aliases": ["migr", "mmc"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "matrix",
|
||||||
|
"text": "https://matrix.to/#/#polymc:matrix.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "log",
|
||||||
|
"text": "https://i.imgur.com/gsrgYzg.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "java",
|
||||||
|
"text": "https://polymc.org/wiki/getting-started/installing-java/",
|
||||||
|
"aliases": ["j"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "paths",
|
||||||
|
"aliases": ["dirs", "locate"],
|
||||||
|
"embed": {
|
||||||
|
"title": "Data directories",
|
||||||
|
"description": "Where PolyMC stores your data (e.g. instances)",
|
||||||
|
"color": "AQUA",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Portable (Windows / Linux)",
|
||||||
|
"value": "In the PolyMC folder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Windows",
|
||||||
|
"value": "`%APPDATA%/PolyMC`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macOS",
|
||||||
|
"value": "`~/Library/Application Support/PolyMC`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"value": "`~/.local/share/PolyMC`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Flatpak",
|
||||||
|
"value": "`~/.var/app/org.polymc.PolyMC/data/PolyMC`"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "build",
|
||||||
|
"text": "https://polymc.org/wiki/development/build-instructions/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eta",
|
||||||
|
"text": "Sometime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "curseforge",
|
||||||
|
"embed": {
|
||||||
|
"title": "What's wrong with CurseForge?",
|
||||||
|
"description": "CurseForge added a new option to block third party clients like PolyMC from accessing mod files, and they started to enforce this option lately. We can't allow you to download those mods directly from PolyMC, but PolyMC 1.3.1 and higher have a workaround to let modpacks work: letting you to download those opted out mods manually. We highly encourage asking authors that opted out to stop doing so.",
|
||||||
|
"color": "ORANGE"
|
||||||
|
},
|
||||||
|
"aliases": ["cf", "curse", "cursed"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "piracy",
|
||||||
|
"embed": {
|
||||||
|
"title": "We don't tolerate piracy!",
|
||||||
|
"description": "PolyMC has always been legal, legitimate & appropriate. We don't and never will have features such as offline login without an official account.",
|
||||||
|
"color": "DARK_RED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
11
src/version.ts
Normal file
11
src/version.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
let cachedVer: string;
|
||||||
|
let cachedTimestamp: number;
|
||||||
|
|
||||||
|
export async function getLatest(): Promise<string> {
|
||||||
|
if (cachedVer && Date.now() - cachedTimestamp < 600000) return cachedVer; // 10min
|
||||||
|
const f = await fetch('https://api.github.com/repos/PolyMC/PolyMC/releases');
|
||||||
|
const versions = await f.json();
|
||||||
|
cachedVer = versions[0].tag_name;
|
||||||
|
cachedTimestamp = Date.now();
|
||||||
|
return versions[0].tag_name;
|
||||||
|
}
|
|
@ -3,6 +3,9 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"target": "ES5"
|
"target": "ES2018",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
yarn.lock
15
yarn.lock
|
@ -239,11 +239,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef"
|
||||||
integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==
|
integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==
|
||||||
|
|
||||||
"@types/remove-markdown@^0.3.1":
|
|
||||||
version "0.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/remove-markdown/-/remove-markdown-0.3.1.tgz#82bc3664c313f50f7c77f1bb59935f567689dc63"
|
|
||||||
integrity sha512-JpJNEJEsmmltyL2LdE8KRjJ0L2ad5vgLibqNj85clohT9AyTrfN6jvHxStPshDkmtcL/ShFu0p2tbY7DBS1mqQ==
|
|
||||||
|
|
||||||
"@types/node@^17.0.38":
|
"@types/node@^17.0.38":
|
||||||
version "17.0.38"
|
version "17.0.38"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947"
|
||||||
|
@ -623,6 +618,11 @@ discord-api-types@^0.31.1:
|
||||||
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.31.2.tgz#8d131e25340bd695815af3bb77128a6993c1b516"
|
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.31.2.tgz#8d131e25340bd695815af3bb77128a6993c1b516"
|
||||||
integrity sha512-gpzXTvFVg7AjKVVJFH0oJGC0q0tO34iJGSHZNz9u3aqLxlD6LfxEs9wWVVikJqn9gra940oUTaPFizCkRDcEiA==
|
integrity sha512-gpzXTvFVg7AjKVVJFH0oJGC0q0tO34iJGSHZNz9u3aqLxlD6LfxEs9wWVVikJqn9gra940oUTaPFizCkRDcEiA==
|
||||||
|
|
||||||
|
discord-command-parser@^1.5.3:
|
||||||
|
version "1.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/discord-command-parser/-/discord-command-parser-1.5.3.tgz#ba27097aa0976fa9287ea81f8d8cdd82f2887317"
|
||||||
|
integrity sha512-YWgalkrbly0dJCyLw7p9SX3RC7HIxOrSTz/8vKjlmYPyyZmMCGmKwpXu6HkPXRZ20L6QqftVWigSw6fDK2zemg==
|
||||||
|
|
||||||
discord.js@^13.7.0:
|
discord.js@^13.7.0:
|
||||||
version "13.7.0"
|
version "13.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.7.0.tgz#5172f7f5d816e2c7296015d335b54e46968d9c67"
|
resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.7.0.tgz#5172f7f5d816e2c7296015d335b54e46968d9c67"
|
||||||
|
@ -652,6 +652,11 @@ dot-prop@^5.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-obj "^2.0.0"
|
is-obj "^2.0.0"
|
||||||
|
|
||||||
|
dotenv@^16.0.1:
|
||||||
|
version "16.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d"
|
||||||
|
integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==
|
||||||
|
|
||||||
duplexer3@^0.1.4:
|
duplexer3@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue