react.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import { client } from "../client";
  2. import { getRepository } from "typeorm";
  3. import { MessageReaction } from "@shared/db/entity/MessageReaction";
  4. import { KnownUser } from "@shared/db/entity/KnownUser";
  5. import { ReactionType, ReactionEmote } from "@shared/db/entity/ReactionEmote";
  6. import { isAuthorisedAsync } from "../util";
  7. import { CommandSet, Command, Action, ActionType } from "src/model/command";
  8. import { Message } from "discord.js";
  9. import { logger } from "src/logging";
  10. const pattern = /^react to\s+"([^"]+)"\s+with\s+<:[^:]+:([^>]+)>$/i;
  11. async function getRandomEmotes(allowedTypes: ReactionType[], limit: number) {
  12. const reactionEmotesRepo = getRepository(ReactionEmote);
  13. const a = await reactionEmotesRepo.query(`
  14. select distinct on (type) type, "reactionId"
  15. from
  16. ( select *
  17. from reaction_emote
  18. where type in (${allowedTypes.map((s, i) => `$${i + 1}`).join(",")})
  19. order by random(), random()
  20. limit ${limit}
  21. ) as sub`, allowedTypes) as ReactionEmote[];
  22. return a;
  23. }
  24. @CommandSet
  25. export class ReactCommands {
  26. @Command({ pattern: "react to", auth: true, documentation: {description: "React to <message> with <emote>.", example: "react to \"<message>\" with <emote>"} })
  27. async addReaction(msg: Message, s: string): Promise<void> {
  28. if (!await isAuthorisedAsync(msg.member))
  29. return;
  30. const contents = pattern.exec(s);
  31. if (contents != null) {
  32. const reactable = contents[1].trim().toLowerCase();
  33. const reactionEmoji = contents[2];
  34. if (!client.bot.emojis.cache.has(reactionEmoji)) {
  35. msg.channel.send(`${msg.author.toString()} I cannot react with this emoji :(`);
  36. return;
  37. }
  38. const repo = getRepository(MessageReaction);
  39. const message = repo.create({
  40. message: reactable,
  41. reactionEmoteId: reactionEmoji
  42. });
  43. await repo.save(message);
  44. msg.channel.send(`${msg.author.toString()} Added reaction!`);
  45. }
  46. }
  47. @Command({ pattern: "remove reaction to", auth: true, documentation: {description: "Stops reacting to <message>.", example: "remove reaction to <message>"} })
  48. async removeReaction(msg: Message, s: string): Promise<void> {
  49. if (!await isAuthorisedAsync(msg.member))
  50. return;
  51. const content = s.substring("remove reaction to ".length).trim().toLowerCase();
  52. const repo = getRepository(MessageReaction);
  53. const result = await repo.delete({ message: content });
  54. if (result.affected == 0) {
  55. msg.channel.send(`${msg.author.toString()} No such reaction available!`);
  56. return;
  57. }
  58. msg.channel.send(`${msg.author.toString()} Removed reaction!`);
  59. }
  60. @Command({ pattern: "reactions", documentation: {description: "Lists all known messages this bot can react to.", example: "reactions"} })
  61. async listReactions(msg: Message): Promise<void> {
  62. const reactionsRepo = getRepository(MessageReaction);
  63. const messages = await reactionsRepo.find({
  64. select: ["message"]
  65. });
  66. const reactions = messages.reduce((p, c) => `${p}\n${c.message}`, "");
  67. msg.channel.send(`I'll react to the following messages:\n\`\`\`${reactions}\n\`\`\``);
  68. }
  69. @Action(ActionType.MESSAGE)
  70. async reactToMentions(actionsDone: boolean, msg: Message, content: string): Promise<boolean> {
  71. if (actionsDone)
  72. return false;
  73. const lowerContent = content.toLowerCase();
  74. const reactionRepo = getRepository(MessageReaction);
  75. const usersRepo = getRepository(KnownUser);
  76. const message = await reactionRepo.findOne({ message: lowerContent });
  77. if (message) {
  78. const emoji = client.bot.emojis.resolve(message.reactionEmoteId);
  79. if (emoji)
  80. msg.react(emoji);
  81. return true;
  82. }
  83. if (msg.mentions.users.size == 0)
  84. return false;
  85. const knownUsers = await usersRepo.find({
  86. select: ["mentionReactionType"],
  87. where: [...msg.mentions.users.map(u => ({ userID: u.id }))]
  88. });
  89. if (knownUsers.length == 0)
  90. return false;
  91. const reactionEmoteTypes = new Set<ReactionType>();
  92. for (const user of knownUsers) {
  93. if (user.mentionReactionType == ReactionType.NONE)
  94. continue;
  95. reactionEmoteTypes.add(user.mentionReactionType);
  96. }
  97. if(reactionEmoteTypes.size == 0)
  98. return false;
  99. const randomEmotes = await getRandomEmotes([...reactionEmoteTypes], 5);
  100. if (randomEmotes.length == 0)
  101. return false;
  102. for (const emote of randomEmotes) {
  103. const emoji = client.bot.emojis.resolve(emote.reactionId);
  104. if(emoji)
  105. await msg.react(emoji);
  106. }
  107. return true;
  108. }
  109. @Action(ActionType.INDIRECT_MENTION)
  110. async reactToPing(actionsDone: boolean, msg: Message): Promise<boolean> {
  111. if (actionsDone)
  112. return false;
  113. let emoteType = ReactionType.ANGERY;
  114. const repo = getRepository(KnownUser);
  115. const knownUser = await repo.findOne({
  116. select: ["replyReactionType"],
  117. where: [{
  118. userID: msg.author.id
  119. }]
  120. });
  121. if (knownUser) {
  122. if (knownUser.replyReactionType == ReactionType.NONE)
  123. return false;
  124. emoteType = knownUser.replyReactionType;
  125. }
  126. const emotes = await getRandomEmotes([emoteType], 1);
  127. if (emotes.length != 1)
  128. return false;
  129. const emote = client.bot.emojis.resolve(emotes[0].reactionId);
  130. if (!emote) {
  131. logger.warn(`Emote ${emotes[0]} no longer is valid. Deleting invalid emojis from the list...`);
  132. const emotesRepo = getRepository(ReactionEmote);
  133. await emotesRepo.delete({ reactionId: emotes[0].reactionId });
  134. return false;
  135. }
  136. msg.channel.send(emote.toString());
  137. return true;
  138. }
  139. }