123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- import { client } from "../client";
- import { getRepository } from "typeorm";
- import { KnownUser } from "@shared/db/entity/KnownUser";
- import { ReactionType, ReactionEmote } from "@shared/db/entity/ReactionEmote";
- import { Message, User, PartialUser, MessageReaction as DiscordMessageReaction } from "discord.js";
- import { logger } from "src/logging";
- import { Command, ICommandData, Event, BotEventData, Plugin } from "src/model/plugin";
- import { MessageReaction } from "@shared/db/entity/MessageReaction";
- import { tryDo } from "@shared/common/async_utils";
- const pattern = /^react to\s+"([^"]+)"\s+with\s+<:[^:]+:([^>]+)>$/i;
- async function getRandomEmotes(allowedTypes: ReactionType[], limit: number) {
- const reactionEmotesRepo = getRepository(ReactionEmote);
- const a = await reactionEmotesRepo.query(`
- select distinct on (type) type, "reactionId"
- from
- ( select *
- from reaction_emote
- where type in (${allowedTypes.map((s, i) => `$${i + 1}`).join(",")})
- order by random(), random()
- limit ${limit}
- ) as sub`, allowedTypes) as ReactionEmote[];
- return a;
- }
- @Plugin
- export class ReactCommands {
- private REACT_PROBABILITY = 0.3;
- @Event("messageReactionAdd")
- async randomReact(data: BotEventData, reaction: DiscordMessageReaction, user: User | PartialUser): Promise<void> {
- if (Math.random() <= this.REACT_PROBABILITY && !user.bot) {
- const msg = reaction.message;
- logger.verbose(`Reacting to message ${msg.id} because user ${user.tag} reacted to it`);
- const result = await tryDo(msg.react(reaction.emoji));
- if (!result.ok) {
- logger.error("Failed to react to user %s#%s (%s): %s", msg.author?.username, msg.author?.discriminator, msg.author?.id, result.error);
- }
- }
- }
- @Command({
- type: "mention",
- pattern: "react to",
- auth: true,
- documentation: {
- description: "React to <message> with <emote>.",
- example: "react to \"<message>\" with <emote>"
- }
- })
- async addReaction({ message, contents }: ICommandData): Promise<void> {
- const reactContents = pattern.exec(contents as string);
- if (reactContents != null) {
- const reactable = reactContents[1].trim().toLowerCase();
- const reactionEmoji = reactContents[2];
- if (!client.bot.emojis.cache.has(reactionEmoji)) {
- message.reply({ content: "I cannot react with this emoji :(", failIfNotExists: false });
- return;
- }
- const repo = getRepository(MessageReaction);
- const msgReaction = repo.create({
- message: reactable,
- reactionEmoteId: reactionEmoji
- });
- await repo.save(msgReaction);
- message.reply({ content: "added reaction!", failIfNotExists: false });
- }
- }
- @Command({
- type: "mention",
- pattern: "remove reaction to",
- auth: true,
- documentation: {
- description: "Stops reacting to <message>.",
- example: "remove reaction to <message>"
- }
- })
- async removeReaction({message, contents}: ICommandData): Promise<void> {
- const content = (contents as string).substring("remove reaction to ".length).trim().toLowerCase();
- const repo = getRepository(MessageReaction);
- const result = await repo.delete({ message: content });
- if (result.affected == 0) {
- message.reply({ content: "no such reaction available!", failIfNotExists: false });
- return;
- }
- message.reply({ content: "removed reaction!", failIfNotExists: false });
- }
- @Command({
- type: "mention",
- pattern: "reactions",
- documentation: {
- description: "Lists all known messages this bot can react to.",
- example: "reactions"
- }
- })
- async listReactions({ message }: ICommandData): Promise<void> {
- const reactionsRepo = getRepository(MessageReaction);
- const messages = await reactionsRepo.find({
- select: ["message"]
- });
- const reactions = messages.reduce((p, c) => `${p}\n${c.message}`, "");
- message.reply({ content: `I'll react to the following messages:\n\`\`\`${reactions}\n\`\`\``, failIfNotExists: false });
- }
- @Event("message")
- async reactToMentions(data: BotEventData, msg: Message): Promise<void> {
- if (data.actionsDone)
- return;
-
- const content = msg.cleanContent.trim();
- const lowerContent = content.toLowerCase();
- const reactionRepo = getRepository(MessageReaction);
- const usersRepo = getRepository(KnownUser);
- const message = await reactionRepo.findOne({ message: lowerContent });
- if (message) {
- const emoji = client.bot.emojis.resolve(message.reactionEmoteId);
- if (emoji) {
- const result = await tryDo(msg.react(emoji));
- if (!result.ok) {
- logger.error("Failed to react to user %s#%s (%s): %s", msg.author.username, msg.author.discriminator, msg.author.id, result.error);
- }
- }
- data.actionsDone = true;
- return;
- }
- if (msg.mentions.users.size == 0)
- return;
- const knownUsers = await usersRepo.find({
- select: ["mentionReactionType"],
- where: [...msg.mentions.users.map(u => ({ userID: u.id }))]
- });
- if (knownUsers.length == 0)
- return;
- const reactionEmoteTypes = new Set<ReactionType>();
- for (const user of knownUsers) {
- if (user.mentionReactionType == ReactionType.NONE)
- continue;
- reactionEmoteTypes.add(user.mentionReactionType);
- }
- if(reactionEmoteTypes.size == 0)
- return;
- const randomEmotes = await getRandomEmotes([...reactionEmoteTypes], 5);
- if (randomEmotes.length == 0)
- return;
- for (const emote of randomEmotes) {
- const emoji = client.bot.emojis.resolve(emote.reactionId);
- if(emoji) {
- const result = await tryDo(msg.react(emoji));
- if (!result.ok) {
- logger.error("Failed to react to user %s#%s (%s): %s", msg.author.username, msg.author.discriminator, msg.author.id, result.error);
- }
- }
- }
- data.actionsDone = true;
- }
- @Event("indirectMention")
- async reactToPing(data: BotEventData, msg: Message): Promise<void> {
- if (data.actionsDone)
- return;
- let emoteType = ReactionType.ANGERY;
- const repo = getRepository(KnownUser);
- const knownUser = await repo.findOne({
- select: ["replyReactionType"],
- where: [{
- userID: msg.author.id
- }]
- });
- if (knownUser) {
- if (knownUser.replyReactionType == ReactionType.NONE)
- return;
- emoteType = knownUser.replyReactionType;
- }
- const emotes = await getRandomEmotes([emoteType], 1);
- if (emotes.length != 1)
- return;
- const emote = client.bot.emojis.resolve(emotes[0].reactionId);
- if (!emote) {
- logger.warn(`Emote ${emotes[0]} no longer is valid. Deleting invalid emojis from the list...`);
- const emotesRepo = getRepository(ReactionEmote);
- await emotesRepo.delete({ reactionId: emotes[0].reactionId });
- return;
- }
- msg.channel.send(emote.toString());
- data.actionsDone = true;
- }
- }
|