react.ts 6.9 KB

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