aboutsummaryrefslogtreecommitdiff
path: root/src/modules/snippets.js
blob: e9c1271ad3b172510447974fe49e565cb54184e2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
const threads = require('../data/threads');
const snippets = require('../data/snippets');
const config = require('../config');
const utils = require('../utils');
const { parseArguments } = require('knub-command-manager');

const whitespaceRegex = /\s/;
const quoteChars = ["'", '"'];

module.exports = ({ bot, knex, config, commands }) => {
  /**
   * "Renders" a snippet by replacing all argument placeholders e.g. {1} {2} with their corresponding arguments.
   * The number in the placeholder is the argument's order in the argument list, i.e. {1} is the first argument (= index 0)
   * @param {String} body
   * @param {String[]} args
   * @returns {String}
   */
  function renderSnippet(body, args) {
    return body
      .replace(/(?<!\\){\d+}/g, match => {
        const index = parseInt(match.slice(1, -1), 10) - 1;
        return (args[index] != null ? args[index] : match);
      })
      .replace(/\\{/g, '{');
  }

  /**
   * When a staff member uses a snippet (snippet prefix + trigger word), find the snippet and post it as a reply in the thread
   */
  bot.on('messageCreate', async msg => {
    if (! utils.messageIsOnInboxServer(msg)) return;
    if (! utils.isStaff(msg.member)) return;

    if (msg.author.bot) return;
    if (! msg.content) return;
    if (! msg.content.startsWith(config.snippetPrefix) && ! msg.content.startsWith(config.snippetPrefixAnon)) return;

    let snippetPrefix, isAnonymous;

    if (config.snippetPrefixAnon.length > config.snippetPrefix.length) {
      // Anonymous prefix is longer -> check it first
      if (msg.content.startsWith(config.snippetPrefixAnon)) {
        snippetPrefix = config.snippetPrefixAnon;
        isAnonymous = true;
      } else {
        snippetPrefix = config.snippetPrefix;
        isAnonymous = false;
      }
    } else {
      // Regular prefix is longer -> check it first
      if (msg.content.startsWith(config.snippetPrefix)) {
        snippetPrefix = config.snippetPrefix;
        isAnonymous = false;
      } else {
        snippetPrefix = config.snippetPrefixAnon;
        isAnonymous = true;
      }
    }

    const thread = await threads.findByChannelId(msg.channel.id);
    if (! thread) return;

    let [, trigger, rawArgs] = msg.content.slice(snippetPrefix.length).match(/(\S+)(?:\s+(.*))?/s);
    trigger = trigger.toLowerCase();

    const snippet = await snippets.get(trigger);
    if (! snippet) return;

    let args = rawArgs ? parseArguments(rawArgs) : [];
    args = args.map(arg => arg.value);
    const rendered = renderSnippet(snippet.body, args);

    const replied = await thread.replyToUser(msg.member, rendered, [], isAnonymous);
    if (replied) msg.delete();
  });

  // Show or add a snippet
  commands.addInboxServerCommand('snippet', '<trigger> [text$]', async (msg, args, thread) => {
    const snippet = await snippets.get(args.trigger);

    if (snippet) {
      if (args.text) {
        // If the snippet exists and we're trying to create a new one, inform the user the snippet already exists
        utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" already exists! You can edit or delete it with ${config.prefix}edit_snippet and ${config.prefix}delete_snippet respectively.`);
      } else {
        // If the snippet exists and we're NOT trying to create a new one, show info about the existing snippet
        utils.postSystemMessageWithFallback(msg.channel, thread, `\`${config.snippetPrefix}${args.trigger}\` replies with: \`\`\`${utils.disableCodeBlocks(snippet.body)}\`\`\``);
      }
    } else {
      if (args.text) {
        // If the snippet doesn't exist and the user wants to create it, create it
        await snippets.add(args.trigger, args.text, msg.author.id);
        utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" created!`);
      } else {
        // If the snippet doesn't exist and the user isn't trying to create it, inform them how to create it
        utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" doesn't exist! You can create it with \`${config.prefix}snippet ${args.trigger} text\``);
      }
    }
  }, {
    aliases: ['s']
  });

  commands.addInboxServerCommand('delete_snippet', '<trigger>', async (msg, args, thread) => {
    const snippet = await snippets.get(args.trigger);
    if (! snippet) {
      utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" doesn't exist!`);
      return;
    }

    await snippets.del(args.trigger);
    utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" deleted!`);
  }, {
    aliases: ['ds']
  });

  commands.addInboxServerCommand('edit_snippet', '<trigger> [text$]', async (msg, args, thread) => {
    const snippet = await snippets.get(args.trigger);
    if (! snippet) {
      utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" doesn't exist!`);
      return;
    }

    await snippets.del(args.trigger);
    await snippets.add(args.trigger, args.text, msg.author.id);

    utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" edited!`);
  }, {
    aliases: ['es']
  });

  commands.addInboxServerCommand('snippets', [], async (msg, args, thread) => {
    const allSnippets = await snippets.all();
    const triggers = allSnippets.map(s => s.trigger);
    triggers.sort();

    utils.postSystemMessageWithFallback(msg.channel, thread, `Available snippets (prefix ${config.snippetPrefix}):\n${triggers.join(', ')}`);
  });
};