give_role_for_react.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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. embed: {
  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, check, {
  74. dispose: true
  75. });
  76. const guild = msg.guild;
  77. if (!guild) {
  78. throw new Error("Tried to initialize role collector for non-guild channel.");
  79. }
  80. collector.on("collect", async (_, user) => {
  81. if (user.bot) {
  82. return;
  83. }
  84. const gu = guild.member(user);
  85. if (!gu) {
  86. return;
  87. }
  88. const result = await tryDo(gu.roles.add(role));
  89. if (!result.ok) {
  90. logger.error("GiveRoleForReact: Can't add role %s to user %s: %s", role, gu.id, result.error);
  91. }
  92. });
  93. collector.on("remove", async (_, user) => {
  94. if (user.bot) {
  95. return;
  96. }
  97. const gu = guild.member(user);
  98. if (!gu) {
  99. return;
  100. }
  101. const result = await tryDo(gu.roles.remove(role));
  102. if (!result.ok) {
  103. logger.error("GiveRoleForReact: Can't remove role %s to user %s: %s", role, gu.id, result.error);
  104. }
  105. });
  106. }
  107. private async sendDM(usr: User, messageText: string) {
  108. const dmResult = await tryDo(usr.createDM());
  109. if (dmResult.ok) {
  110. tryDo(dmResult.result.send(messageText));
  111. }
  112. }
  113. async start(): Promise<void> {
  114. logger.info("Initializing role give messages");
  115. const roleGiveMessageRepo = getRepository(GiveRoleMessage);
  116. const reactMessages = await roleGiveMessageRepo.find();
  117. const staleEntities = [];
  118. for (const reactMessage of reactMessages) {
  119. const guildResult = await tryDo(client.bot.guilds.fetch(reactMessage.guildId));
  120. if (!guildResult.ok) {
  121. staleEntities.push(reactMessage);
  122. continue;
  123. }
  124. const guild = guildResult.result;
  125. const channel = guild.channels.resolve(reactMessage.channelId);
  126. if (!channel || !(channel instanceof TextChannel)) {
  127. staleEntities.push(reactMessage);
  128. continue;
  129. }
  130. const msgResult = await tryDo(channel.messages.fetch(reactMessage.messageId));
  131. if (!msgResult.ok) {
  132. staleEntities.push(reactMessage);
  133. continue;
  134. }
  135. this.initReactCollector(msgResult.result, reactMessage.roleToGive);
  136. }
  137. await roleGiveMessageRepo.remove(staleEntities);
  138. }
  139. }