give_role_for_react.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { parseYaml } from "../util";
  2. import { Command, ICommandData, Plugin } from "src/model/plugin";
  3. import { tryDo } from "@shared/common/async_utils";
  4. import * as t from "io-ts";
  5. import { logger } from "src/logging";
  6. import { getRepository } from "typeorm";
  7. import { GiveRoleMessage } from "@shared/db/entity/GiveRoleMessage";
  8. import { Message, MessageReaction, ReactionCollector, ReactionEmoji, TextChannel, User } from "discord.js";
  9. import { client } from "src/client";
  10. const ReactRoleMessageParams = t.type({
  11. message: t.string,
  12. roleId: t.string
  13. });
  14. const REACT_EMOTE = "⭐";
  15. const MSG_COLOR = 9830318;
  16. @Plugin
  17. export class GiveRoleForReact {
  18. @Command({
  19. type: "mention",
  20. pattern: "add role message",
  21. documentation: {
  22. description: "Add role giver message",
  23. example: "add role message { message: \"This is a role!\", roleId: \"0237894783782\" }"
  24. },
  25. allowDM: false,
  26. auth: true
  27. })
  28. async makeRoleMessage({ message, contents }: ICommandData): Promise<void> {
  29. const textContent = (contents as string).substring("add role message".length).trim();
  30. tryDo(message.delete());
  31. const opts = parseYaml(ReactRoleMessageParams, textContent);
  32. if (!opts.ok) {
  33. await this.sendDM(message.author, `Sorry, I don't understand the command! Got the following errors: ${opts.errors.join("\n")}`);
  34. return;
  35. }
  36. const params = opts.result;
  37. if (!message.guild?.roles.cache.has(params.roleId)) {
  38. await this.sendDM(message.author, "Sorry, the role ID is not a valid role on the server!");
  39. return;
  40. }
  41. const msgSendResult = await tryDo(message.channel.send({
  42. embed: {
  43. title: `React with ${REACT_EMOTE} to gain role`,
  44. description: params.message,
  45. color: MSG_COLOR
  46. }
  47. }));
  48. if (!msgSendResult.ok) {
  49. logger.error(`GiveRoleForReact: failed to create message because ${msgSendResult.error}`);
  50. return;
  51. }
  52. const msg = msgSendResult.result;
  53. await msg.react(REACT_EMOTE);
  54. const roleGiveMessageRepo = getRepository(GiveRoleMessage);
  55. if (!msg.guild) {
  56. logger.error("GiveRoleForReact: tried to set up role react for DMs (this should never happen!)");
  57. return;
  58. }
  59. await roleGiveMessageRepo.save({
  60. messageId: msg.id,
  61. guildId: msg.guild.id,
  62. channelId: msg.channel.id,
  63. roleToGive: params.roleId
  64. });
  65. this.initReactCollector(msg, params.roleId);
  66. }
  67. private initReactCollector(msg: Message, role: string) {
  68. const check = (reaction: MessageReaction) => {
  69. return reaction.emoji.name == REACT_EMOTE;
  70. };
  71. const collector = new ReactionCollector(msg, check, {
  72. dispose: true
  73. });
  74. const guild = msg.guild;
  75. if (!guild) {
  76. throw new Error("Tried to initialize role collector for non-guild channel.");
  77. }
  78. collector.on("collect", async (_, user) => {
  79. if (user.bot) {
  80. return;
  81. }
  82. const gu = guild.member(user);
  83. if (!gu) {
  84. return;
  85. }
  86. const result = await tryDo(gu.roles.add(role));
  87. if (!result.ok) {
  88. logger.error("GiveRoleForReact: Can't add role %s to user %s: %s", role, gu.id, result.error);
  89. }
  90. });
  91. collector.on("remove", async (_, user) => {
  92. if (user.bot) {
  93. return;
  94. }
  95. const gu = guild.member(user);
  96. if (!gu) {
  97. return;
  98. }
  99. const result = await tryDo(gu.roles.remove(role));
  100. if (!result.ok) {
  101. logger.error("GiveRoleForReact: Can't remove role %s to user %s: %s", role, gu.id, result.error);
  102. }
  103. });
  104. }
  105. private async sendDM(usr: User, messageText: string) {
  106. const dmResult = await tryDo(usr.createDM());
  107. if (dmResult.ok) {
  108. tryDo(dmResult.result.send(messageText));
  109. }
  110. }
  111. async start(): Promise<void> {
  112. logger.info("Initializing role give messages");
  113. const roleGiveMessageRepo = getRepository(GiveRoleMessage);
  114. const reactMessages = await roleGiveMessageRepo.find();
  115. const staleEntities = [];
  116. for (const reactMessage of reactMessages) {
  117. const guildResult = await tryDo(client.bot.guilds.fetch(reactMessage.guildId));
  118. if (!guildResult.ok) {
  119. staleEntities.push(reactMessage);
  120. continue;
  121. }
  122. const guild = guildResult.result;
  123. const channel = guild.channels.resolve(reactMessage.channelId);
  124. if (!channel || !(channel instanceof TextChannel)) {
  125. staleEntities.push(reactMessage);
  126. continue;
  127. }
  128. const msgResult = await tryDo(channel.messages.fetch(reactMessage.messageId));
  129. if (!msgResult.ok) {
  130. staleEntities.push(reactMessage);
  131. continue;
  132. }
  133. this.initReactCollector(msgResult.result, reactMessage.roleToGive);
  134. }
  135. await roleGiveMessageRepo.remove(staleEntities);
  136. }
  137. }