main.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. // We need some kind of module resolver for @shared/db. We use module-alias.
  2. require("module-alias/register");
  3. import dotenv from "dotenv";
  4. if (process.env.NODE_ENV == "dev") {
  5. dotenv.config({
  6. path: "../.env"
  7. });
  8. dotenv.config({
  9. path: "../db.env"
  10. });
  11. process.env.TYPEORM_HOST = "localhost";
  12. process.env.TYPEORM_USERNAME = process.env.DB_USERNAME;
  13. process.env.TYPEORM_PASSWORD = process.env.DB_PASSWORD;
  14. process.env.TYPEORM_DATABASE = process.env.DB_NAME;
  15. }
  16. import * as fs from "fs";
  17. import * as path from "path";
  18. import { client } from "./client";
  19. import * as mCmd from "./model/command";
  20. import "reflect-metadata";
  21. import { createConnection, getConnectionOptions } from "typeorm";
  22. import { getNumberEnums } from "./util";
  23. import { DB_ENTITIES } from "@shared/db/entities";
  24. import { BOT_COMMAND_DESCRIPTOR } from "./model/command";
  25. const REACT_PROBABILITY = 0.3;
  26. async function trigger(type: mCmd.ActionType, ...params: any[]) {
  27. let actionDone = false;
  28. let actions = botEvents[type];
  29. for (let i = 0; i < actions.length; i++) {
  30. const action = actions[i] as (...args: any[]) => boolean | Promise<boolean>;
  31. let actionResult = action(actionDone, ...params);
  32. if (actionResult instanceof Promise)
  33. actionDone = (await actionResult) || actionDone;
  34. else
  35. actionDone = actionResult || actionDone;
  36. }
  37. return actionDone;
  38. }
  39. let commandSets: mCmd.ICommand[] = [];
  40. let botCommands: mCmd.IBotCommand[] = [];
  41. let botEvents: { [event in mCmd.ActionType]?: mCmd.BotAction[] } = getNumberEnums(mCmd.ActionType).reduce((p, c) => { p[c] = []; return p; }, {} as any);
  42. let startActions: Array<() => void | Promise<void>> = [];
  43. client.on("ready", async () => {
  44. console.log("Starting up NoctBot!");
  45. client.user.setActivity(process.env.NODE_ENV == "dev" ? "Maintenance" : "@NoctBot help", {
  46. type: "PLAYING"
  47. });
  48. for (let i = 0; i < startActions.length; i++) {
  49. const action = startActions[i];
  50. let val = action();
  51. if (val instanceof Promise)
  52. await val;
  53. }
  54. console.log("NoctBot is ready!");
  55. });
  56. client.on("message", async m => {
  57. if (m.author.id == client.user.id)
  58. return;
  59. if(process.env.FOOLS == "TRUE" && (m.channel.id == "297109482905796608" || m.channel.id == "429295461099110402") && Math.random() <= 0.01) {
  60. const neighs = ["*NEIGH*", "neeeeeigh!", "Gimme carrots!", "NEEEEIIIIGH", "**N E I G H**"];
  61. await m.channel.send(neighs[Math.floor(Math.random() * neighs.length)]);
  62. return;
  63. }
  64. let content = m.cleanContent.trim();
  65. if (await trigger(mCmd.ActionType.MESSAGE, m, content))
  66. return;
  67. if (m.mentions.users.size > 0 && m.mentions.users.has(client.user.id)) {
  68. if (m.content.trim().startsWith(`<@${client.user.id}>`) || m.content.trim().startsWith(`<@!${client.user.id}>`)) {
  69. content = content.substring(`@${client.user.username}`.length).trim();
  70. let lowerCaseContent = content.toLowerCase();
  71. for (let c of botCommands) {
  72. if (typeof (c.pattern) == "string" && lowerCaseContent.startsWith(c.pattern)) {
  73. c.action(m, content);
  74. return;
  75. }
  76. else if (c.pattern instanceof RegExp) {
  77. let result = c.pattern.exec(content);
  78. if (result != null) {
  79. c.action(m, content, result);
  80. return;
  81. }
  82. }
  83. }
  84. if (await trigger(mCmd.ActionType.DIRECT_MENTION, m, lowerCaseContent))
  85. return;
  86. }
  87. if (await trigger(mCmd.ActionType.INDIRECT_MENTION, m))
  88. return;
  89. }
  90. await trigger(mCmd.ActionType.POST_MESSAGE);
  91. });
  92. client.on("messageReactionAdd", (r, u) => {
  93. if (Math.random() <= REACT_PROBABILITY && !u.bot) {
  94. console.log(`Reacting to message ${r.message.id} because user ${u.tag} reacted to it`);
  95. r.message.react(r.emoji);
  96. }
  97. });
  98. function loadCommand(mod: any) {
  99. for (let i in mod) {
  100. if (!mod.hasOwnProperty(i))
  101. continue;
  102. let commandClass = mod[i] as any;
  103. // Ensure this is indeed a command class
  104. if (!commandClass.prototype || commandClass.prototype.BOT_COMMAND !== BOT_COMMAND_DESCRIPTOR)
  105. continue;
  106. let cmd = new commandClass() as mCmd.ICommand;
  107. commandSets.push(cmd);
  108. if (cmd._botCommands)
  109. botCommands.push(...cmd._botCommands.map(c => ({ ...c, action: c.action.bind(cmd) })));
  110. if (cmd._botEvents)
  111. for (let [i, event] of Object.entries(cmd._botEvents)) {
  112. botEvents[+i as mCmd.ActionType].push((event as Function).bind(cmd));
  113. }
  114. if(cmd.onStart)
  115. startActions.push(cmd.onStart.bind(cmd));
  116. }
  117. }
  118. export function getDocumentation() {
  119. return botCommands.filter(m => m.documentation !== undefined).map(m => ({
  120. name: m.pattern.toString(),
  121. doc: m.documentation.description,
  122. example: m.documentation.example,
  123. auth: m.auth || false
  124. }));
  125. }
  126. async function main() {
  127. await createConnection({
  128. ...await getConnectionOptions(),
  129. entities: DB_ENTITIES
  130. });
  131. let commandsPath = path.resolve(path.dirname(module.filename), "commands");
  132. let files = fs.readdirSync(commandsPath);
  133. for (const file of files) {
  134. let ext = path.extname(file);
  135. if (ext != ".js")
  136. continue;
  137. loadCommand(require(path.resolve(commandsPath, file)));
  138. }
  139. client.login(process.env.BOT_TOKEN);
  140. }
  141. main();