create-chapterjs 0.2.2 → 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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/choose.js +27 -0
  4. package/dist/index.js +98 -4
  5. package/dist/node-version.js +13 -0
  6. package/dist/package-manager.js +59 -0
  7. package/dist/project.js +82 -0
  8. package/dist/templates.js +31 -0
  9. package/package.json +19 -5
  10. package/templates/community/.env.example +4 -0
  11. package/templates/community/_gitignore +5 -0
  12. package/templates/community/package.json +24 -0
  13. package/templates/community/src/community.test.ts +48 -0
  14. package/templates/community/src/messages.ts +49 -0
  15. package/templates/community/src/presence.ts +6 -0
  16. package/templates/community/src/roles/roles.ts +60 -0
  17. package/templates/community/src/stats/weekly.ts +50 -0
  18. package/templates/community/src/welcome/channel.ts +19 -0
  19. package/templates/community/src/welcome/welcome.ts +37 -0
  20. package/templates/community/tsconfig.json +20 -0
  21. package/templates/default/.env.example +4 -0
  22. package/templates/default/_gitignore +5 -0
  23. package/templates/default/package.json +24 -0
  24. package/templates/default/src/events/ready.ts +28 -0
  25. package/templates/default/tsconfig.json +20 -0
  26. package/templates/moderation/.env.example +4 -0
  27. package/templates/moderation/_gitignore +5 -0
  28. package/templates/moderation/package.json +24 -0
  29. package/templates/moderation/src/moderation/channel.ts +67 -0
  30. package/templates/moderation/src/moderation/log.ts +37 -0
  31. package/templates/moderation/src/moderation/messages.ts +158 -0
  32. package/templates/moderation/src/moderation/moderation.test.ts +76 -0
  33. package/templates/moderation/src/moderation/purge.ts +30 -0
  34. package/templates/moderation/src/moderation/timeout.ts +82 -0
  35. package/templates/moderation/src/moderation/warn.ts +61 -0
  36. package/templates/moderation/src/presence.ts +6 -0
  37. package/templates/moderation/tsconfig.json +20 -0
  38. package/templates/music/.env.example +4 -0
  39. package/templates/music/_gitignore +5 -0
  40. package/templates/music/package.json +24 -0
  41. package/templates/music/src/music/leave.ts +18 -0
  42. package/templates/music/src/music/messages.ts +83 -0
  43. package/templates/music/src/music/music.test.ts +29 -0
  44. package/templates/music/src/music/play.ts +120 -0
  45. package/templates/music/src/music/queue-command.ts +28 -0
  46. package/templates/music/src/music/queue.ts +61 -0
  47. package/templates/music/src/presence.ts +6 -0
  48. package/templates/music/tsconfig.json +20 -0
  49. package/templates/tickets/.env.example +4 -0
  50. package/templates/tickets/_gitignore +5 -0
  51. package/templates/tickets/package.json +24 -0
  52. package/templates/tickets/src/presence.ts +6 -0
  53. package/templates/tickets/src/tickets/close.ts +39 -0
  54. package/templates/tickets/src/tickets/messages.ts +68 -0
  55. package/templates/tickets/src/tickets/open.ts +81 -0
  56. package/templates/tickets/src/tickets/panel.ts +35 -0
  57. package/templates/tickets/src/tickets/tickets.test.ts +72 -0
  58. package/templates/tickets/tsconfig.json +20 -0
@@ -0,0 +1,68 @@
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: the person for a private answer, the server
5
+ // otherwise. `commands` is what Discord shows of each command.
6
+ export const en = language({
7
+ locale: 'en-US',
8
+ default: true,
9
+ texts: {
10
+ panelTitle: 'Need help?',
11
+ panelText:
12
+ 'Open a ticket and the staff will answer you in a private thread.',
13
+ panelPosted: 'The panel is posted.',
14
+ openButton: 'Open a ticket',
15
+ formTitle: 'New ticket',
16
+ formSubject: 'Subject',
17
+ formCategory: 'What is it about?',
18
+ formDetails: 'Tell us more',
19
+ notHere: 'Tickets can only be opened from a text channel.',
20
+ opened: 'Thanks {user}, the staff will be with you shortly.',
21
+ created: 'Your ticket is open: {thread}',
22
+ alreadyOpen: 'You already have a ticket open: {thread}. Close it first.',
23
+ category: 'Category',
24
+ openedBy: 'Opened by',
25
+ closeButton: 'Close the ticket',
26
+ notATicket: 'This button only works inside a ticket.',
27
+ notYours: 'Only the staff, or who opened this ticket, can close it.',
28
+ closing: 'Ticket closed by {user}.',
29
+ closedDm: 'Your ticket "{ticket}" on {server} was closed.',
30
+ },
31
+ commands: {
32
+ 'ticket panel': {
33
+ description: 'Posts the message people open tickets from',
34
+ },
35
+ },
36
+ });
37
+
38
+ export const fr = language({
39
+ locale: 'fr',
40
+ texts: {
41
+ panelTitle: 'Besoin d’aide ?',
42
+ panelText: 'Ouvrez un ticket et le staff vous répondra dans un fil privé.',
43
+ panelPosted: 'Le panneau est posté.',
44
+ openButton: 'Ouvrir un ticket',
45
+ formTitle: 'Nouveau ticket',
46
+ formSubject: 'Sujet',
47
+ formCategory: 'De quoi s’agit-il ?',
48
+ formDetails: 'Dites-nous en plus',
49
+ notHere: 'Un ticket ne peut être ouvert que depuis un salon textuel.',
50
+ opened: 'Merci {user}, le staff arrive.',
51
+ created: 'Votre ticket est ouvert : {thread}',
52
+ alreadyOpen:
53
+ 'Vous avez déjà un ticket ouvert : {thread}. Fermez-le d’abord.',
54
+ category: 'Catégorie',
55
+ openedBy: 'Ouvert par',
56
+ closeButton: 'Fermer le ticket',
57
+ notATicket: 'Ce bouton ne fonctionne que dans un ticket.',
58
+ notYours:
59
+ 'Seul le staff, ou la personne qui a ouvert ce ticket, peut le fermer.',
60
+ closing: 'Ticket fermé par {user}.',
61
+ closedDm: 'Votre ticket « {ticket} » sur {server} a été fermé.',
62
+ },
63
+ commands: {
64
+ 'ticket panel': {
65
+ description: 'Poste le message depuis lequel on ouvre un ticket',
66
+ },
67
+ },
68
+ });
@@ -0,0 +1,81 @@
1
+ import { ChannelType, modal, store } from 'chapterjs';
2
+ import { close } from './close';
3
+
4
+ // The ticket each person has open: the id of its thread, by person. A
5
+ // store keeps data from one restart to the next, per server: in this
6
+ // server, `tickets.get(member.id)` is the ticket of that member here.
7
+ export const tickets = store<string>();
8
+
9
+ // The form a ticket starts with. `run` receives the fields typed from
10
+ // their kind: `subject` and `details` are strings, `category` is one of
11
+ // the three values below.
12
+ export const openTicket = modal({
13
+ title: ({ t }) => t('formTitle'),
14
+ fields: {
15
+ subject: {
16
+ type: 'text',
17
+ label: ({ t }) => t('formSubject'),
18
+ style: 'short',
19
+ maxLength: 80,
20
+ },
21
+ category: {
22
+ type: 'radio',
23
+ label: ({ t }) => t('formCategory'),
24
+ options: { Bug: 'bug', Question: 'question', Other: 'other' },
25
+ },
26
+ details: {
27
+ type: 'text',
28
+ label: ({ t }) => t('formDetails'),
29
+ style: 'paragraph',
30
+ minLength: 20,
31
+ maxLength: 1000,
32
+ },
33
+ },
34
+ async run({ interaction, fields, channel, member, t }) {
35
+ if (!channel.isText()) {
36
+ await interaction.reply({ content: t('notHere'), ephemeral: true });
37
+ return;
38
+ }
39
+ // One ticket at a time per person.
40
+ const open = await tickets.get(member.id);
41
+ if (open) {
42
+ await interaction.reply({
43
+ content: t('alreadyOpen', { thread: `<#${open}>` }),
44
+ ephemeral: true,
45
+ });
46
+ return;
47
+ }
48
+ // A private thread: only the people added to it, and the staff, see it.
49
+ const thread = await channel.startThread(
50
+ {
51
+ name: `${fields.category}-${member.displayName}`.slice(0, 100),
52
+ type: ChannelType.PrivateThread,
53
+ invitable: false,
54
+ },
55
+ `Ticket opened by ${member.user.username}`
56
+ );
57
+ await thread.addMember(member);
58
+ await tickets.set(member.id, thread.id);
59
+ await thread.send({
60
+ content: t('opened', { user: member.toString() }),
61
+ embeds: [
62
+ {
63
+ title: fields.subject,
64
+ description: fields.details,
65
+ color: 0xc026d3,
66
+ fields: [
67
+ { name: t('category'), value: fields.category, inline: true },
68
+ { name: t('openedBy'), value: member.toString(), inline: true },
69
+ ],
70
+ },
71
+ ],
72
+ // The close button carries who opened the ticket, so it knows who may
73
+ // close it besides the staff, and who to tell when it is closed.
74
+ components: [close({ userId: member.id })],
75
+ });
76
+ await interaction.reply({
77
+ content: t('created', { thread: thread.toString() }),
78
+ ephemeral: true,
79
+ });
80
+ },
81
+ });
@@ -0,0 +1,35 @@
1
+ import { button, command } from 'chapterjs';
2
+ import { openTicket } from './open';
3
+
4
+ // /ticket panel posts the message people open a ticket from: an embed and
5
+ // a button. Only the staff can post it (`permissions`); Discord hides the
6
+ // command from everyone else. `t` speaks the language of the server.
7
+ export default command({
8
+ name: 'ticket panel',
9
+ permissions: ['ManageThreads'],
10
+ async run({ interaction, channel, t }) {
11
+ await channel.send({
12
+ embeds: [
13
+ {
14
+ title: t('panelTitle'),
15
+ description: t('panelText'),
16
+ color: 0xc026d3,
17
+ },
18
+ ],
19
+ components: [open],
20
+ });
21
+ await interaction.reply({ content: t('panelPosted'), ephemeral: true });
22
+ },
23
+ });
24
+
25
+ // A button is named by its export (`open`): nothing to register, no id to
26
+ // write. Its label is a function of `t`, run when the message is sent.
27
+ export const open = button({
28
+ label: ({ t }) => t('openButton'),
29
+ style: 'primary',
30
+ emoji: '🎫',
31
+ async run({ interaction }) {
32
+ // A form must be the first answer to a click.
33
+ await interaction.showModal(openTicket);
34
+ },
35
+ });
@@ -0,0 +1,72 @@
1
+ import { scenario } from 'chapterjs/test';
2
+ import { close } from './close';
3
+ import { openTicket } from './open';
4
+ import panel, { open } from './panel';
5
+
6
+ // `chapterjs test` runs these against a Discord in memory: no token, no
7
+ // server. `bot` is this bot in a test server, with people you name.
8
+ export default [
9
+ scenario('the panel is posted by the staff only', async bot => {
10
+ const refused = await bot.use(panel);
11
+ refused.isEphemeral().says('ManageThreads');
12
+
13
+ const staff = bot.member('staff', { permissions: ['ManageThreads'] });
14
+ const reply = await bot.use(panel, {}, { by: staff });
15
+ reply.isEphemeral().says('The panel is posted.');
16
+ bot
17
+ .channel('general')
18
+ .last!.hasEmbed('Need help?')
19
+ .hasButton('Open a ticket');
20
+ }),
21
+
22
+ scenario('a ticket opens in a private thread, once per person', async bot => {
23
+ const click = await bot.click(open);
24
+ click.opensForm(openTicket);
25
+
26
+ const reply = await bot.submit(openTicket, {
27
+ subject: 'No sound',
28
+ category: 'bug',
29
+ details: 'The music stops after a minute, every single time.',
30
+ });
31
+ reply.isEphemeral().says('Your ticket is open');
32
+ const [thread] = bot.channel('general').threads;
33
+ thread!
34
+ .last!.says('Thanks')
35
+ .hasEmbed('No sound')
36
+ .hasButton('Close the ticket');
37
+
38
+ const again = await bot.submit(openTicket, {
39
+ subject: 'Another one',
40
+ category: 'question',
41
+ details: 'Can I open two tickets at the same time, please?',
42
+ });
43
+ again.isEphemeral().says('You already have a ticket open');
44
+ }),
45
+
46
+ scenario('only the staff or who opened a ticket closes it', async bot => {
47
+ await bot.click(open);
48
+ await bot.submit(openTicket, {
49
+ subject: 'Login',
50
+ category: 'other',
51
+ details: 'I can not log in since this morning, what can I do?',
52
+ });
53
+ const [thread] = bot.channel('general').threads;
54
+ const ticket = thread!.last!;
55
+
56
+ const stranger = await bot.click(ticket.button('Close the ticket'), {
57
+ by: bot.member('stranger'),
58
+ in: thread,
59
+ on: ticket,
60
+ });
61
+ stranger.isEphemeral().says('Only the staff');
62
+
63
+ const closed = await bot.click(ticket.button('Close the ticket'), {
64
+ in: thread,
65
+ on: ticket,
66
+ });
67
+ closed.isPublic().says('Ticket closed by');
68
+ if (!thread!.archived || !thread!.locked) {
69
+ throw new Error('The thread should be archived and locked.');
70
+ }
71
+ }),
72
+ ];
@@ -0,0 +1,20 @@
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
+ }