create-chapterjs 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.
Files changed (57) hide show
  1. package/README.md +37 -0
  2. package/dist/choose.js +27 -0
  3. package/dist/index.js +98 -4
  4. package/dist/node-version.js +13 -0
  5. package/dist/package-manager.js +59 -0
  6. package/dist/project.js +82 -0
  7. package/dist/templates.js +31 -0
  8. package/package.json +19 -5
  9. package/templates/community/.env.example +4 -0
  10. package/templates/community/_gitignore +5 -0
  11. package/templates/community/package.json +24 -0
  12. package/templates/community/src/community.test.ts +48 -0
  13. package/templates/community/src/messages.ts +49 -0
  14. package/templates/community/src/presence.ts +6 -0
  15. package/templates/community/src/roles/roles.ts +60 -0
  16. package/templates/community/src/stats/weekly.ts +50 -0
  17. package/templates/community/src/welcome/channel.ts +19 -0
  18. package/templates/community/src/welcome/welcome.ts +37 -0
  19. package/templates/community/tsconfig.json +20 -0
  20. package/templates/default/.env.example +4 -0
  21. package/templates/default/_gitignore +5 -0
  22. package/templates/default/package.json +24 -0
  23. package/templates/default/src/events/ready.ts +28 -0
  24. package/templates/default/tsconfig.json +20 -0
  25. package/templates/moderation/.env.example +4 -0
  26. package/templates/moderation/_gitignore +5 -0
  27. package/templates/moderation/package.json +24 -0
  28. package/templates/moderation/src/moderation/channel.ts +67 -0
  29. package/templates/moderation/src/moderation/log.ts +37 -0
  30. package/templates/moderation/src/moderation/messages.ts +158 -0
  31. package/templates/moderation/src/moderation/moderation.test.ts +76 -0
  32. package/templates/moderation/src/moderation/purge.ts +30 -0
  33. package/templates/moderation/src/moderation/timeout.ts +82 -0
  34. package/templates/moderation/src/moderation/warn.ts +61 -0
  35. package/templates/moderation/src/presence.ts +6 -0
  36. package/templates/moderation/tsconfig.json +20 -0
  37. package/templates/music/.env.example +4 -0
  38. package/templates/music/_gitignore +5 -0
  39. package/templates/music/package.json +24 -0
  40. package/templates/music/src/music/leave.ts +18 -0
  41. package/templates/music/src/music/messages.ts +83 -0
  42. package/templates/music/src/music/music.test.ts +29 -0
  43. package/templates/music/src/music/play.ts +120 -0
  44. package/templates/music/src/music/queue-command.ts +28 -0
  45. package/templates/music/src/music/queue.ts +61 -0
  46. package/templates/music/src/presence.ts +6 -0
  47. package/templates/music/tsconfig.json +20 -0
  48. package/templates/tickets/.env.example +4 -0
  49. package/templates/tickets/_gitignore +5 -0
  50. package/templates/tickets/package.json +24 -0
  51. package/templates/tickets/src/presence.ts +6 -0
  52. package/templates/tickets/src/tickets/close.ts +39 -0
  53. package/templates/tickets/src/tickets/messages.ts +68 -0
  54. package/templates/tickets/src/tickets/open.ts +81 -0
  55. package/templates/tickets/src/tickets/panel.ts +35 -0
  56. package/templates/tickets/src/tickets/tickets.test.ts +72 -0
  57. package/templates/tickets/tsconfig.json +20 -0
@@ -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
+ }