create-chapter 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/index.js +4 -98
- package/package.json +18 -5
- package/dist/choose.js +0 -27
- package/dist/node-version.js +0 -13
- package/dist/package-manager.js +0 -59
- package/dist/project.js +0 -82
- package/dist/templates.js +0 -31
- package/templates/community/.env.example +0 -4
- package/templates/community/_gitignore +0 -5
- package/templates/community/package.json +0 -23
- package/templates/community/src/messages.ts +0 -49
- package/templates/community/src/presence.ts +0 -6
- package/templates/community/src/roles/roles.ts +0 -60
- package/templates/community/src/stats/weekly.ts +0 -50
- package/templates/community/src/welcome/channel.ts +0 -19
- package/templates/community/src/welcome/welcome.ts +0 -37
- package/templates/community/tsconfig.json +0 -20
- package/templates/default/.env.example +0 -4
- package/templates/default/_gitignore +0 -5
- package/templates/default/package.json +0 -23
- package/templates/default/src/events/ready.ts +0 -28
- package/templates/default/tsconfig.json +0 -20
- package/templates/moderation/.env.example +0 -4
- package/templates/moderation/_gitignore +0 -5
- package/templates/moderation/package.json +0 -23
- package/templates/moderation/src/moderation/channel.ts +0 -67
- package/templates/moderation/src/moderation/log.ts +0 -37
- package/templates/moderation/src/moderation/messages.ts +0 -158
- package/templates/moderation/src/moderation/purge.ts +0 -30
- package/templates/moderation/src/moderation/timeout.ts +0 -82
- package/templates/moderation/src/moderation/warn.ts +0 -61
- package/templates/moderation/src/presence.ts +0 -6
- package/templates/moderation/tsconfig.json +0 -20
- package/templates/music/.env.example +0 -4
- package/templates/music/_gitignore +0 -5
- package/templates/music/package.json +0 -23
- package/templates/music/src/music/leave.ts +0 -18
- package/templates/music/src/music/messages.ts +0 -83
- package/templates/music/src/music/play.ts +0 -120
- package/templates/music/src/music/queue-command.ts +0 -28
- package/templates/music/src/music/queue.ts +0 -61
- package/templates/music/src/presence.ts +0 -6
- package/templates/music/tsconfig.json +0 -20
- package/templates/tickets/.env.example +0 -4
- package/templates/tickets/_gitignore +0 -5
- package/templates/tickets/package.json +0 -23
- package/templates/tickets/src/presence.ts +0 -6
- package/templates/tickets/src/tickets/close.ts +0 -39
- package/templates/tickets/src/tickets/messages.ts +0 -68
- package/templates/tickets/src/tickets/open.ts +0 -81
- package/templates/tickets/src/tickets/panel.ts +0 -35
- package/templates/tickets/tsconfig.json +0 -20
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "chapter-bot",
|
|
3
|
-
"description": "A bot that starts: one event, ready to grow",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "chapterjs dev",
|
|
8
|
-
"start": "chapterjs start",
|
|
9
|
-
"build": "chapterjs build",
|
|
10
|
-
"typecheck": "tsc -b",
|
|
11
|
-
"postinstall": "chapterjs sync"
|
|
12
|
-
},
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"chapterjs": "latest"
|
|
15
|
-
},
|
|
16
|
-
"devDependencies": {
|
|
17
|
-
"@types/node": "^24.0.0",
|
|
18
|
-
"typescript": "^7.0.2"
|
|
19
|
-
},
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=24"
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { event } from 'chapterjs';
|
|
2
|
-
|
|
3
|
-
// Your bot is made of the files of src/: each one exports what it declares
|
|
4
|
-
// with a function of 'chapterjs' (event, command, button, task...), and
|
|
5
|
-
// chapterjs dev runs it. This one is an event: `name` says which, and types
|
|
6
|
-
// what `run` receives. `ready` runs once, when the bot is connected.
|
|
7
|
-
export default event({
|
|
8
|
-
name: 'ready',
|
|
9
|
-
run({ user, guilds }) {
|
|
10
|
-
console.log(`${user.username} is online in ${guilds.size} server(s)`);
|
|
11
|
-
},
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// A command is a file like this one, anywhere in src/, with command():
|
|
15
|
-
//
|
|
16
|
-
// import { command } from 'chapterjs';
|
|
17
|
-
//
|
|
18
|
-
// export default command({
|
|
19
|
-
// name: 'ping',
|
|
20
|
-
// description: 'Replies with Pong!',
|
|
21
|
-
// async run({ interaction }) {
|
|
22
|
-
// await interaction.reply('Pong!');
|
|
23
|
-
// },
|
|
24
|
-
// });
|
|
25
|
-
//
|
|
26
|
-
// Save it, and /ping shows up in your test server. The other templates of
|
|
27
|
-
// `pnpm create chapter` show tickets, music, moderation and a community
|
|
28
|
-
// bot; the docs at https://www.chapterjs.org walk through everything.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "Preserve",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"allowImportingTsExtensions": true,
|
|
7
|
-
"erasableSyntaxOnly": true,
|
|
8
|
-
"verbatimModuleSyntax": true,
|
|
9
|
-
"noEmit": true,
|
|
10
|
-
"strict": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"types": ["node"]
|
|
13
|
-
},
|
|
14
|
-
"files": [],
|
|
15
|
-
"references": [
|
|
16
|
-
{
|
|
17
|
-
"path": "./.chapterjs"
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "moderation-bot",
|
|
3
|
-
"description": "Timeouts, purge, slowmode, lock, and a log of every moderation action",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "chapterjs dev",
|
|
8
|
-
"start": "chapterjs start",
|
|
9
|
-
"build": "chapterjs build",
|
|
10
|
-
"typecheck": "tsc -b",
|
|
11
|
-
"postinstall": "chapterjs sync"
|
|
12
|
-
},
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"chapterjs": "latest"
|
|
15
|
-
},
|
|
16
|
-
"devDependencies": {
|
|
17
|
-
"@types/node": "^24.0.0",
|
|
18
|
-
"typescript": "^7.0.2"
|
|
19
|
-
},
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=24"
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { command } from 'chapterjs';
|
|
2
|
-
|
|
3
|
-
// /slowmode sets how long people wait between two messages in the channel.
|
|
4
|
-
// 0 removes it.
|
|
5
|
-
export const slowmode = command({
|
|
6
|
-
name: 'slowmode',
|
|
7
|
-
permissions: ['ManageChannels'],
|
|
8
|
-
options: {
|
|
9
|
-
seconds: { type: 'integer', min: 0, max: 21600, required: true },
|
|
10
|
-
},
|
|
11
|
-
async run({ interaction, options, channel, member, t }) {
|
|
12
|
-
if (!channel.isText()) {
|
|
13
|
-
await interaction.reply({ content: t('notHere'), ephemeral: true });
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
await channel.edit(
|
|
17
|
-
{ rateLimitPerUser: options.seconds },
|
|
18
|
-
`By ${member.user.username}`
|
|
19
|
-
);
|
|
20
|
-
await interaction.reply(
|
|
21
|
-
options.seconds === 0
|
|
22
|
-
? t('slowmodeOff')
|
|
23
|
-
: t('slowmodeOn', { count: options.seconds })
|
|
24
|
-
);
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// /lock keeps everyone from writing in the channel, /unlock gives it back.
|
|
29
|
-
// Both change the permissions of @everyone on the channel only.
|
|
30
|
-
export const lock = command({
|
|
31
|
-
name: 'lock',
|
|
32
|
-
permissions: ['ManageChannels'],
|
|
33
|
-
options: {
|
|
34
|
-
reason: { type: 'string', maxLength: 200 },
|
|
35
|
-
},
|
|
36
|
-
async run({ interaction, options, channel, guild, member, t }) {
|
|
37
|
-
if (!channel.isText()) {
|
|
38
|
-
await interaction.reply({ content: t('notHere'), ephemeral: true });
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
await channel.setPermissions(
|
|
42
|
-
guild.everyoneRole,
|
|
43
|
-
{ deny: ['SendMessages', 'CreatePublicThreads', 'CreatePrivateThreads'] },
|
|
44
|
-
options.reason ?? `Locked by ${member.user.username}`
|
|
45
|
-
);
|
|
46
|
-
await interaction.reply(
|
|
47
|
-
t('locked', { reason: options.reason ?? t('noReason') })
|
|
48
|
-
);
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
export const unlock = command({
|
|
53
|
-
name: 'unlock',
|
|
54
|
-
permissions: ['ManageChannels'],
|
|
55
|
-
async run({ interaction, channel, guild, member, t }) {
|
|
56
|
-
if (!channel.isText()) {
|
|
57
|
-
await interaction.reply({ content: t('notHere'), ephemeral: true });
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
// Deleting the overwrite gives @everyone back what the server says.
|
|
61
|
-
await channel.deletePermissions(
|
|
62
|
-
guild.everyoneRole,
|
|
63
|
-
`Unlocked by ${member.user.username}`
|
|
64
|
-
);
|
|
65
|
-
await interaction.reply(t('unlocked'));
|
|
66
|
-
},
|
|
67
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { AuditLogEvent, event } from 'chapterjs';
|
|
2
|
-
|
|
3
|
-
// Every moderation action, whoever did it and however (a command of this
|
|
4
|
-
// bot, another bot, the Discord app), reaches the audit log of the server.
|
|
5
|
-
// This posts the ones below in the channel named mod-log. Discord only
|
|
6
|
-
// sends the audit log to a bot with the View Audit Log permission.
|
|
7
|
-
const ACTIONS: Partial<Record<number, string>> = {
|
|
8
|
-
[AuditLogEvent.MemberKick]: 'kicked',
|
|
9
|
-
[AuditLogEvent.MemberBanAdd]: 'banned',
|
|
10
|
-
[AuditLogEvent.MemberBanRemove]: 'unbanned',
|
|
11
|
-
[AuditLogEvent.MemberUpdate]: 'updated (timeout, nickname...)',
|
|
12
|
-
[AuditLogEvent.MessageBulkDelete]: 'purged messages in',
|
|
13
|
-
[AuditLogEvent.ChannelOverwriteUpdate]: 'changed the permissions of',
|
|
14
|
-
[AuditLogEvent.ChannelOverwriteCreate]: 'changed the permissions of',
|
|
15
|
-
[AuditLogEvent.ChannelOverwriteDelete]: 'changed the permissions of',
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export default event({
|
|
19
|
-
name: 'auditLogEntryCreate',
|
|
20
|
-
async run({ entry, guild }) {
|
|
21
|
-
const action = ACTIONS[entry.actionType];
|
|
22
|
-
if (!action || !entry.userId) return;
|
|
23
|
-
const log = [...guild.channels.values()].find(
|
|
24
|
-
channel => channel.isText() && channel.name === 'mod-log'
|
|
25
|
-
);
|
|
26
|
-
if (!log?.isText()) return;
|
|
27
|
-
const who = `<@${entry.userId}>`;
|
|
28
|
-
const target = entry.targetId
|
|
29
|
-
? guild.channels.has(entry.targetId)
|
|
30
|
-
? `<#${entry.targetId}>`
|
|
31
|
-
: `<@${entry.targetId}>`
|
|
32
|
-
: 'something';
|
|
33
|
-
await log.send(
|
|
34
|
-
`🛡️ ${who} ${action} ${target}${entry.reason ? `: ${entry.reason}` : ''}`
|
|
35
|
-
);
|
|
36
|
-
},
|
|
37
|
-
});
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { language } from 'chapterjs';
|
|
2
|
-
|
|
3
|
-
// The texts of the bot, one language() per language. `t` picks the one of
|
|
4
|
-
// who will read the message. A text with {count} can have one form per
|
|
5
|
-
// quantity: Discord's language rules pick the form.
|
|
6
|
-
export const en = language({
|
|
7
|
-
locale: 'en-US',
|
|
8
|
-
default: true,
|
|
9
|
-
texts: {
|
|
10
|
-
cantModerate:
|
|
11
|
-
'You can not moderate {user}: their highest role is above yours.',
|
|
12
|
-
timedOut: '{user} is timed out until {until}. Reason: {reason}',
|
|
13
|
-
noReason: 'none given',
|
|
14
|
-
notTimedOut: '{user} is not timed out.',
|
|
15
|
-
timeoutRemoved: '{user} can speak again.',
|
|
16
|
-
purged: {
|
|
17
|
-
one: '{count} message deleted.',
|
|
18
|
-
other: '{count} messages deleted.',
|
|
19
|
-
},
|
|
20
|
-
notHere: 'This only works in a text channel of the server.',
|
|
21
|
-
slowmodeOff: 'Slowmode is off.',
|
|
22
|
-
slowmodeOn: {
|
|
23
|
-
one: 'Slowmode: one message every {count} second.',
|
|
24
|
-
other: 'Slowmode: one message every {count} seconds.',
|
|
25
|
-
},
|
|
26
|
-
locked: '🔒 This channel is locked. Reason: {reason}',
|
|
27
|
-
unlocked: '🔓 This channel is open again.',
|
|
28
|
-
warned: {
|
|
29
|
-
one: '{user} was warned. They have {count} warning.',
|
|
30
|
-
other: '{user} was warned. They have {count} warnings.',
|
|
31
|
-
},
|
|
32
|
-
noWarnings: '{user} has no warning.',
|
|
33
|
-
warningsOf: {
|
|
34
|
-
one: '{user} has {count} warning:',
|
|
35
|
-
other: '{user} has {count} warnings:',
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
commands: {
|
|
39
|
-
timeout: {
|
|
40
|
-
description: 'Keeps someone from writing and speaking for a while',
|
|
41
|
-
options: {
|
|
42
|
-
user: { description: 'Who' },
|
|
43
|
-
duration: { description: 'For how long' },
|
|
44
|
-
reason: { description: 'Why' },
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
untimeout: {
|
|
48
|
-
description: 'Ends the timeout of someone',
|
|
49
|
-
options: { user: { description: 'Who' } },
|
|
50
|
-
},
|
|
51
|
-
purge: {
|
|
52
|
-
description: 'Deletes the last messages of this channel',
|
|
53
|
-
options: {
|
|
54
|
-
count: { description: 'How many, 100 at most' },
|
|
55
|
-
user: { description: 'Only the messages of this person' },
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
slowmode: {
|
|
59
|
-
description: 'Sets how long people wait between two messages here',
|
|
60
|
-
options: {
|
|
61
|
-
seconds: { description: 'Seconds between two messages, 0 for none' },
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
lock: {
|
|
65
|
-
description: 'Keeps everyone from writing in this channel',
|
|
66
|
-
options: { reason: { description: 'Why' } },
|
|
67
|
-
},
|
|
68
|
-
unlock: { description: 'Lets everyone write in this channel again' },
|
|
69
|
-
'warn add': {
|
|
70
|
-
description: 'Warns someone; warnings are forgotten after 30 days',
|
|
71
|
-
options: { user: { description: 'Who' }, reason: { description: 'Why' } },
|
|
72
|
-
},
|
|
73
|
-
'warn list': {
|
|
74
|
-
description: 'Lists the warnings of someone',
|
|
75
|
-
options: { user: { description: 'Who' } },
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
export const fr = language({
|
|
81
|
-
locale: 'fr',
|
|
82
|
-
texts: {
|
|
83
|
-
cantModerate:
|
|
84
|
-
'Vous ne pouvez pas modérer {user} : son rôle le plus haut est au-dessus du vôtre.',
|
|
85
|
-
timedOut:
|
|
86
|
-
'{user} est exclu temporairement jusqu’à {until}. Raison : {reason}',
|
|
87
|
-
noReason: 'aucune',
|
|
88
|
-
notTimedOut: '{user} n’est pas exclu.',
|
|
89
|
-
timeoutRemoved: '{user} peut de nouveau parler.',
|
|
90
|
-
purged: {
|
|
91
|
-
one: '{count} message supprimé.',
|
|
92
|
-
other: '{count} messages supprimés.',
|
|
93
|
-
},
|
|
94
|
-
notHere: 'Ceci ne fonctionne que dans un salon textuel du serveur.',
|
|
95
|
-
slowmodeOff: 'Le mode lent est désactivé.',
|
|
96
|
-
slowmodeOn: {
|
|
97
|
-
one: 'Mode lent : un message toutes les {count} seconde.',
|
|
98
|
-
other: 'Mode lent : un message toutes les {count} secondes.',
|
|
99
|
-
},
|
|
100
|
-
locked: '🔒 Ce salon est verrouillé. Raison : {reason}',
|
|
101
|
-
unlocked: '🔓 Ce salon est de nouveau ouvert.',
|
|
102
|
-
warned: {
|
|
103
|
-
one: '{user} a été averti. Il a {count} avertissement.',
|
|
104
|
-
other: '{user} a été averti. Il a {count} avertissements.',
|
|
105
|
-
},
|
|
106
|
-
noWarnings: '{user} n’a aucun avertissement.',
|
|
107
|
-
warningsOf: {
|
|
108
|
-
one: '{user} a {count} avertissement :',
|
|
109
|
-
other: '{user} a {count} avertissements :',
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
commands: {
|
|
113
|
-
timeout: {
|
|
114
|
-
description: 'Empêche quelqu’un d’écrire et de parler pendant un moment',
|
|
115
|
-
options: {
|
|
116
|
-
user: { description: 'Qui' },
|
|
117
|
-
duration: { description: 'Pendant combien de temps' },
|
|
118
|
-
reason: { description: 'Pourquoi' },
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
untimeout: {
|
|
122
|
-
description: 'Met fin à l’exclusion temporaire de quelqu’un',
|
|
123
|
-
options: { user: { description: 'Qui' } },
|
|
124
|
-
},
|
|
125
|
-
purge: {
|
|
126
|
-
description: 'Supprime les derniers messages de ce salon',
|
|
127
|
-
options: {
|
|
128
|
-
count: { description: 'Combien, 100 au plus' },
|
|
129
|
-
user: { description: 'Seulement les messages de cette personne' },
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
slowmode: {
|
|
133
|
-
description: 'Règle l’attente entre deux messages ici',
|
|
134
|
-
options: {
|
|
135
|
-
seconds: { description: 'Secondes entre deux messages, 0 pour aucune' },
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
lock: {
|
|
139
|
-
description: 'Empêche tout le monde d’écrire dans ce salon',
|
|
140
|
-
options: { reason: { description: 'Pourquoi' } },
|
|
141
|
-
},
|
|
142
|
-
unlock: {
|
|
143
|
-
description: 'Laisse de nouveau tout le monde écrire dans ce salon',
|
|
144
|
-
},
|
|
145
|
-
'warn add': {
|
|
146
|
-
description:
|
|
147
|
-
'Avertit quelqu’un ; un avertissement est oublié après 30 jours',
|
|
148
|
-
options: {
|
|
149
|
-
user: { description: 'Qui' },
|
|
150
|
-
reason: { description: 'Pourquoi' },
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
'warn list': {
|
|
154
|
-
description: 'Liste les avertissements de quelqu’un',
|
|
155
|
-
options: { user: { description: 'Qui' } },
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { command } from 'chapterjs';
|
|
2
|
-
|
|
3
|
-
// /purge deletes the last messages of the channel, all of them or only
|
|
4
|
-
// the ones of a person. Discord only deletes in bulk what is less than two
|
|
5
|
-
// weeks old: older messages are left out, and the answer says how many
|
|
6
|
-
// really went.
|
|
7
|
-
export default command({
|
|
8
|
-
name: 'purge',
|
|
9
|
-
permissions: ['ManageMessages'],
|
|
10
|
-
// The answer only the person sees, so the channel is not filled again.
|
|
11
|
-
ephemeral: true,
|
|
12
|
-
cooldown: { channel: '5s' },
|
|
13
|
-
options: {
|
|
14
|
-
count: { type: 'integer', min: 1, max: 100, required: true },
|
|
15
|
-
user: { type: 'user' },
|
|
16
|
-
},
|
|
17
|
-
async run({ interaction, options, channel, member, t }) {
|
|
18
|
-
// Deleting takes a moment: tell Discord the answer is coming.
|
|
19
|
-
await interaction.defer();
|
|
20
|
-
const messages = await channel.fetchMessages({ limit: options.count });
|
|
21
|
-
const chosen = options.user
|
|
22
|
-
? messages.filter(message => message.author.id === options.user!.id)
|
|
23
|
-
: messages;
|
|
24
|
-
const deleted = await channel.bulkDelete(
|
|
25
|
-
chosen,
|
|
26
|
-
`Purge by ${member.user.username}`
|
|
27
|
-
);
|
|
28
|
-
await interaction.reply(t('purged', { count: deleted }));
|
|
29
|
-
},
|
|
30
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { command, timestamp, type GuildMember } from 'chapterjs';
|
|
2
|
-
|
|
3
|
-
// Someone can only act on people below them, and never on the owner.
|
|
4
|
-
function canModerate(target: GuildMember, by: GuildMember): boolean {
|
|
5
|
-
return (
|
|
6
|
-
target.id !== target.guild.ownerId &&
|
|
7
|
-
target.highestRole.position < by.highestRole.position
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// /timeout keeps someone from writing, reacting and speaking for a while.
|
|
12
|
-
// `permissions` is what a member needs to see and use the command; the bot
|
|
13
|
-
// itself needs Moderate Members. The choices of `duration` are what the
|
|
14
|
-
// person picks from, in minutes.
|
|
15
|
-
export default command({
|
|
16
|
-
name: 'timeout',
|
|
17
|
-
permissions: ['ModerateMembers'],
|
|
18
|
-
options: {
|
|
19
|
-
user: { type: 'user', required: true },
|
|
20
|
-
duration: {
|
|
21
|
-
type: 'integer',
|
|
22
|
-
required: true,
|
|
23
|
-
choices: {
|
|
24
|
-
'5 minutes': 5,
|
|
25
|
-
'1 hour': 60,
|
|
26
|
-
'1 day': 60 * 24,
|
|
27
|
-
'1 week': 60 * 24 * 7,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
reason: { type: 'string', maxLength: 200 },
|
|
31
|
-
},
|
|
32
|
-
async run({ interaction, options, guild, member, t }) {
|
|
33
|
-
const target = await guild.fetchMember(options.user);
|
|
34
|
-
if (!canModerate(target, member)) {
|
|
35
|
-
await interaction.reply({
|
|
36
|
-
content: t('cantModerate', { user: target.toString() }),
|
|
37
|
-
ephemeral: true,
|
|
38
|
-
});
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const until = Date.now() + options.duration * 60 * 1000;
|
|
42
|
-
await target.timeout(
|
|
43
|
-
options.duration * 60 * 1000,
|
|
44
|
-
options.reason ?? `By ${member.user.username}`
|
|
45
|
-
);
|
|
46
|
-
await interaction.reply(
|
|
47
|
-
t('timedOut', {
|
|
48
|
-
user: target.toString(),
|
|
49
|
-
until: timestamp(until, 'R'),
|
|
50
|
-
reason: options.reason ?? t('noReason'),
|
|
51
|
-
})
|
|
52
|
-
);
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
export const untimeout = command({
|
|
57
|
-
name: 'untimeout',
|
|
58
|
-
permissions: ['ModerateMembers'],
|
|
59
|
-
options: {
|
|
60
|
-
user: { type: 'user', required: true },
|
|
61
|
-
},
|
|
62
|
-
async run({ interaction, options, guild, member, t }) {
|
|
63
|
-
const target = await guild.fetchMember(options.user);
|
|
64
|
-
// The same rule as /timeout: nobody lifts what someone above them set.
|
|
65
|
-
if (!canModerate(target, member)) {
|
|
66
|
-
await interaction.reply({
|
|
67
|
-
content: t('cantModerate', { user: target.toString() }),
|
|
68
|
-
ephemeral: true,
|
|
69
|
-
});
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (!target.isTimedOut) {
|
|
73
|
-
await interaction.reply({
|
|
74
|
-
content: t('notTimedOut', { user: target.toString() }),
|
|
75
|
-
ephemeral: true,
|
|
76
|
-
});
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
await target.removeTimeout(`By ${member.user.username}`);
|
|
80
|
-
await interaction.reply(t('timeoutRemoved', { user: target.toString() }));
|
|
81
|
-
},
|
|
82
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { command, store, timestamp } from 'chapterjs';
|
|
2
|
-
|
|
3
|
-
interface Warning {
|
|
4
|
-
reason: string;
|
|
5
|
-
/** Who gave it: a user id. */
|
|
6
|
-
by: string;
|
|
7
|
-
/** When, in ms since the epoch: a Date would not survive the file. */
|
|
8
|
-
at: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// The warnings of each member, kept from one restart to the next, per
|
|
12
|
-
// server: `warnings.get(member.id)` in a command is the warnings of that
|
|
13
|
-
// member in the server the command was used in. A warning is forgotten
|
|
14
|
-
// thirty days after the last one was added.
|
|
15
|
-
export const warnings = store<Warning[]>({ expires: '30d' });
|
|
16
|
-
|
|
17
|
-
export default command({
|
|
18
|
-
name: 'warn add',
|
|
19
|
-
permissions: ['ModerateMembers'],
|
|
20
|
-
options: {
|
|
21
|
-
user: { type: 'user', required: true },
|
|
22
|
-
reason: { type: 'string', required: true, maxLength: 200 },
|
|
23
|
-
},
|
|
24
|
-
async run({ interaction, options, member, t }) {
|
|
25
|
-
// Reads, adds and keeps in one go; `list` is undefined the first time.
|
|
26
|
-
const list = await warnings.update(options.user.id, (list = []) => [
|
|
27
|
-
...list,
|
|
28
|
-
{ reason: options.reason, by: member.id, at: Date.now() },
|
|
29
|
-
]);
|
|
30
|
-
await interaction.reply(
|
|
31
|
-
t('warned', { user: options.user.toString(), count: list.length })
|
|
32
|
-
);
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
export const list = command({
|
|
37
|
-
name: 'warn list',
|
|
38
|
-
permissions: ['ModerateMembers'],
|
|
39
|
-
ephemeral: true,
|
|
40
|
-
options: {
|
|
41
|
-
user: { type: 'user', required: true },
|
|
42
|
-
},
|
|
43
|
-
async run({ interaction, options, t }) {
|
|
44
|
-
const list = (await warnings.get(options.user.id)) ?? [];
|
|
45
|
-
if (list.length === 0) {
|
|
46
|
-
await interaction.reply(
|
|
47
|
-
t('noWarnings', { user: options.user.toString() })
|
|
48
|
-
);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
await interaction.reply(
|
|
52
|
-
[
|
|
53
|
-
t('warningsOf', { user: options.user.toString(), count: list.length }),
|
|
54
|
-
...list.map(
|
|
55
|
-
warning =>
|
|
56
|
-
`• ${timestamp(warning.at, 'R')} <@${warning.by}>: ${warning.reason}`
|
|
57
|
-
),
|
|
58
|
-
].join('\n')
|
|
59
|
-
);
|
|
60
|
-
},
|
|
61
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "Preserve",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"allowImportingTsExtensions": true,
|
|
7
|
-
"erasableSyntaxOnly": true,
|
|
8
|
-
"verbatimModuleSyntax": true,
|
|
9
|
-
"noEmit": true,
|
|
10
|
-
"strict": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"types": ["node"]
|
|
13
|
-
},
|
|
14
|
-
"files": [],
|
|
15
|
-
"references": [
|
|
16
|
-
{
|
|
17
|
-
"path": "./.chapterjs"
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "music-bot",
|
|
3
|
-
"description": "A music player in voice: a queue, buttons, pause, skip and stop",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "chapterjs dev",
|
|
8
|
-
"start": "chapterjs start",
|
|
9
|
-
"build": "chapterjs build",
|
|
10
|
-
"typecheck": "tsc -b",
|
|
11
|
-
"postinstall": "chapterjs sync"
|
|
12
|
-
},
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"chapterjs": "latest"
|
|
15
|
-
},
|
|
16
|
-
"devDependencies": {
|
|
17
|
-
"@types/node": "^24.0.0",
|
|
18
|
-
"typescript": "^7.0.2"
|
|
19
|
-
},
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=24"
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { command } from 'chapterjs';
|
|
2
|
-
import { clearQueue } from './queue';
|
|
3
|
-
|
|
4
|
-
// /leave makes the bot leave the voice channel and forget the queue.
|
|
5
|
-
export default command({
|
|
6
|
-
name: 'leave',
|
|
7
|
-
async run({ interaction, guild, t }) {
|
|
8
|
-
const voice = guild.voice;
|
|
9
|
-
if (!voice) {
|
|
10
|
-
await interaction.reply({ content: t('notInVoice'), ephemeral: true });
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
clearQueue(guild);
|
|
14
|
-
// leave() ends what plays: the loop of play.ts stops too.
|
|
15
|
-
await voice.leave();
|
|
16
|
-
await interaction.reply(t('left'));
|
|
17
|
-
},
|
|
18
|
-
});
|