Skip to content

Commit 4b4f109

Browse files
committed
Discord v14
1 parent 7d64792 commit 4b4f109

27 files changed

+2255
-2767
lines changed

.vscode/settings.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
{
2-
"typescript.tsdk": "node_modules/typescript/lib"
2+
"typescript.tsdk": "node_modules/typescript/lib",
3+
"cSpell.words": [
4+
"algoliasearch",
5+
"autorole",
6+
"Cooldown",
7+
"leaderboard",
8+
"twoslash",
9+
"twoslasher"
10+
]
311
}

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# 2022-11-19
2+
3+
- Updated to Discord.js 14, removed Cookiecord to prevent future delays in updating versions.
4+
- The bot will now react on the configured autorole messages to indicate available roles.
5+
- Unhandled rejections will now only be ignored if `NODE_ENV` is set to `production`.
6+
- Removed admin `checkThreads` command as using it would result in the bot checking for closed threads twice as often until restarted.

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:16.14.0-alpine
1+
FROM node:16.18.1-alpine
22
WORKDIR /usr/src/app
33

44
COPY yarn.lock ./

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ See the [documentation](https://cookiecord.js.org/) for the framework we use.
1212

1313
We also have a docker-compose.yml for development, along with a .env.example.
1414

15-
**A quick note about the help channel system:** Please don't use it if you don't have a large server (10k+ members) as it will likely inconvenience your members rather than benefit them. We used a static channel system (#help-1 and #help-2) up until around 9,000 members, when we started to see issues arising (many questions being asked on top of eachother without answers).
15+
**A quick note about the help channel system:** Please only use it if you have a large server (10k+ members) as it will likely inconvenience your members rather than benefit them. We used a static channel system (#help-1 and #help-2) up until around 9,000 members, when we started to see issues arising (many questions being asked on top of each other without answers).
1616

1717
## Thanks!
1818

19-
- [ckie](https://github.com/ckiee) for writing the base for the bot and the amazing [framework](https://github.com/cookiecord/cookiecord) used!
19+
- [ckie](https://github.com/ckiee) for writing the base for the bot.
2020
- [Python Discord](https://github.com/python-discord) for heavily influencing our help channel system.

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ services:
2727
volumes:
2828
- 'postgres_data:/postgres/data'
2929
ports:
30-
- 5432
30+
- 5432:5432
3131

3232
volumes:
3333
postgres_data:

package.json

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,33 @@
44
"description": "Typescript Community Bot",
55
"main": "dist/index.js",
66
"dependencies": {
7-
"@typescript/twoslash": "^2.1.0",
8-
"algoliasearch": "^4.8.6",
9-
"cookiecord": "^0.8.18",
7+
"@typescript/twoslash": "^3.2.1",
8+
"algoliasearch": "^4.14.2",
9+
"discord.js": "^14.6.0",
1010
"dotenv-safe": "^8.2.0",
11-
"html-entities": "^2.3.2",
11+
"html-entities": "^2.3.3",
1212
"lz-string": "^1.4.4",
13-
"node-fetch": "^2.6.7",
14-
"npm-registry-fetch": "^9.0.0",
15-
"parse-duration": "^0.4.4",
16-
"pg": "^8.3.0",
17-
"prettier": "^2.2.1",
18-
"pretty-ms": "^7.0.0",
19-
"tar": "^6.1.0",
20-
"typeorm": "^0.2.25"
13+
"npm-registry-fetch": "^14.0.2",
14+
"parse-duration": "^1.0.2",
15+
"pg": "^8.8.0",
16+
"prettier": "^2.7.1",
17+
"pretty-ms": "^8.0.0",
18+
"tar": "^6.1.12",
19+
"typeorm": "^0.3.10",
20+
"undici": "^5.12.0"
2121
},
2222
"devDependencies": {
23-
"@types/dotenv-safe": "^8.1.0",
23+
"@types/dotenv-safe": "^8.1.2",
2424
"@types/lz-string": "^1.3.34",
25-
"@types/node": "^13.7.0",
26-
"@types/node-fetch": "^2.5.8",
27-
"@types/npm-registry-fetch": "^8.0.0",
28-
"@types/prettier": "^2.2.3",
29-
"@types/tar": "^4.0.4",
30-
"@types/ws": "^7.2.1",
31-
"husky": "^4.2.5",
32-
"pretty-quick": "^2.0.1",
33-
"ts-node-dev": "^1.0.0-pre.60",
34-
"typescript": "^4.1.3"
25+
"@types/node": "^16",
26+
"@types/npm-registry-fetch": "^8.0.4",
27+
"@types/prettier": "^2.7.1",
28+
"@types/tar": "^6.1.3",
29+
"@types/ws": "^8.5.3",
30+
"husky": "^8.0.2",
31+
"pretty-quick": "^3.1.3",
32+
"ts-node-dev": "^2.0.0",
33+
"typescript": "^4.9.3"
3534
},
3635
"husky": {
3736
"hooks": {

src/bot.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { Message, Client, User, GuildMember } from 'discord.js';
2+
import { prefixes, trustedRoleId } from './env';
3+
4+
export interface CommandRegistration {
5+
aliases: string[];
6+
description?: string;
7+
listener: (msg: Message, content: string) => Promise<void>;
8+
}
9+
10+
export class Bot {
11+
commands: CommandRegistration[] = [];
12+
adminCommands: CommandRegistration[] = [];
13+
14+
constructor(public client: Client<true>) {
15+
client.on('messageCreate', msg => {
16+
const triggerWithPrefix = msg.content.split(/\s/)[0];
17+
const matchingPrefix = prefixes.find(p =>
18+
triggerWithPrefix.startsWith(p),
19+
);
20+
if (matchingPrefix) {
21+
const content = msg.content
22+
.substring(triggerWithPrefix.length + 1)
23+
.trim();
24+
this.getByTrigger(
25+
triggerWithPrefix.substring(matchingPrefix.length),
26+
)
27+
?.listener(msg, content)
28+
.catch(err => {
29+
this.client.emit('error', err);
30+
});
31+
}
32+
});
33+
}
34+
35+
registerCommand(command: CommandRegistration) {
36+
this.commands.push(command);
37+
}
38+
39+
registerAdminCommand(command: CommandRegistration) {
40+
this.adminCommands.push(command);
41+
}
42+
43+
getByTrigger(trigger: string): CommandRegistration | undefined {
44+
const match = (c: CommandRegistration) => c.aliases.includes(trigger);
45+
return this.commands.find(match) || this.adminCommands.find(match);
46+
}
47+
48+
isMod(member: GuildMember | null) {
49+
return member?.permissions.has('ManageMessages') ?? false;
50+
}
51+
52+
getTrustedMemberError(msg: Message) {
53+
if (!msg.guild || !msg.member || !msg.channel.isTextBased()) {
54+
return ":warning: you can't use that command here.";
55+
}
56+
57+
if (
58+
!msg.member.roles.cache.has(trustedRoleId) &&
59+
!msg.member.permissions.has('ManageMessages')
60+
) {
61+
return ":warning: you don't have permission to use that command.";
62+
}
63+
}
64+
65+
async getTargetUser(msg: Message): Promise<User | undefined> {
66+
const query = msg.content.split(/\s/)[1];
67+
68+
const mentioned = msg.mentions.members?.first()?.user;
69+
if (mentioned) return mentioned;
70+
71+
if (query) {
72+
// Search by ID
73+
const queriedUser = await this.client.users
74+
.fetch(query)
75+
.catch(() => undefined);
76+
if (queriedUser) return queriedUser;
77+
78+
// Search by name, likely a better way to do this...
79+
for (const user of this.client.users.cache.values()) {
80+
if (user.tag === query || user.username === query) {
81+
return user;
82+
}
83+
}
84+
}
85+
}
86+
}

src/db.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { Connection, createConnection } from 'typeorm';
1+
import { DataSource } from 'typeorm';
22
import { dbUrl } from './env';
33
import { Rep } from './entities/Rep';
44
import { HelpThread } from './entities/HelpThread';
55
import { Snippet } from './entities/Snippet';
66

7-
let db: Connection | undefined;
7+
let db: DataSource | undefined;
88
export async function getDB() {
99
if (db) return db;
1010

@@ -21,14 +21,15 @@ export async function getDB() {
2121
}
2222
: {};
2323

24-
db = await createConnection({
24+
db = new DataSource({
2525
type: 'postgres',
2626
url: dbUrl,
2727
synchronize: true,
2828
logging: false,
2929
entities: [Rep, HelpThread, Snippet],
3030
...extraOpts,
3131
});
32+
await db.initialize()
3233
console.log('Connected to DB');
3334
return db;
3435
}

src/index.ts

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,69 @@
1-
import { token, botAdmins, prefixes } from './env';
2-
import CookiecordClient from 'cookiecord';
3-
import { Intents } from 'discord.js';
1+
import { Client, GatewayIntentBits, Partials } from 'discord.js';
2+
import { Bot } from './bot';
43
import { getDB } from './db';
4+
import { token } from './env';
55
import { hookLog } from './log';
66

7-
import { AutoroleModule } from './modules/autorole';
8-
import { EtcModule } from './modules/etc';
9-
import { HelpThreadModule } from './modules/helpthread';
10-
import { PlaygroundModule } from './modules/playground';
11-
import { RepModule } from './modules/rep';
12-
import { TwoslashModule } from './modules/twoslash';
13-
import { HelpModule } from './modules/help';
14-
import { SnippetModule } from './modules/snippet';
15-
import { HandbookModule } from './modules/handbook';
16-
import { ModModule } from './modules/mod';
7+
import { autoroleModule } from './modules/autorole';
8+
import { etcModule } from './modules/etc';
9+
import { handbookModule } from './modules/handbook';
10+
import { helpModule } from './modules/help';
11+
import { modModule } from './modules/mod';
12+
import { playgroundModule } from './modules/playground';
13+
import { repModule } from './modules/rep';
14+
import { twoslashModule } from './modules/twoslash';
15+
import { snippetModule } from './modules/snippet';
16+
import { helpThreadModule } from './modules/helpthread';
1717

18-
const client = new CookiecordClient(
19-
{
20-
botAdmins,
21-
prefix: prefixes,
18+
const client = new Client({
19+
partials: [
20+
Partials.Reaction,
21+
Partials.Message,
22+
Partials.User,
23+
Partials.Channel,
24+
],
25+
allowedMentions: {
26+
parse: ['users', 'roles'],
2227
},
23-
{
24-
partials: ['REACTION', 'MESSAGE', 'USER', 'CHANNEL'],
25-
allowedMentions: {
26-
parse: ['users', 'roles'],
27-
},
28-
intents: new Intents([
29-
'GUILDS',
30-
'GUILD_MESSAGES',
31-
'GUILD_MEMBERS',
32-
'GUILD_MESSAGE_REACTIONS',
33-
'DIRECT_MESSAGES',
34-
]),
35-
},
36-
).setMaxListeners(Infinity);
37-
38-
for (const mod of [
39-
AutoroleModule,
40-
EtcModule,
41-
HelpThreadModule,
42-
PlaygroundModule,
43-
RepModule,
44-
TwoslashModule,
45-
HelpModule,
46-
SnippetModule,
47-
HandbookModule,
48-
ModModule,
49-
]) {
50-
client.registerModule(mod);
51-
}
28+
intents: [
29+
GatewayIntentBits.Guilds,
30+
GatewayIntentBits.GuildMessages,
31+
GatewayIntentBits.GuildMembers,
32+
GatewayIntentBits.GuildMessageReactions,
33+
GatewayIntentBits.DirectMessages,
34+
GatewayIntentBits.MessageContent,
35+
],
36+
}).setMaxListeners(Infinity);
5237

53-
getDB(); // prepare the db for later
38+
getDB().then(() => client.login(token));
5439

55-
client.login(token);
56-
client.on('ready', () => {
40+
client.on('ready', async () => {
41+
const bot = new Bot(client);
5742
console.log(`Logged in as ${client.user?.tag}`);
58-
hookLog(client);
43+
await hookLog(client);
44+
45+
for (const mod of [
46+
autoroleModule,
47+
etcModule,
48+
helpThreadModule,
49+
playgroundModule,
50+
repModule,
51+
twoslashModule,
52+
helpModule,
53+
snippetModule,
54+
handbookModule,
55+
modModule,
56+
]) {
57+
await mod(bot);
58+
}
5959
});
6060

61-
process.on('unhandledRejection', e => {
62-
console.error('Unhandled rejection', e);
61+
client.on('error', error => {
62+
console.error(error);
6363
});
64+
65+
if (process.env.NODE_ENV === 'production') {
66+
process.on('unhandledRejection', e => {
67+
console.error('Unhandled rejection', e);
68+
});
69+
}

0 commit comments

Comments
 (0)