give_role_for_react.ts 5.6 KB

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