neet/minazuki

View on GitHub
src/consumers/highlight.ts

Summary

Maintainability
A
25 mins
Test Coverage
import Discord from 'discord.js';
import { filter } from 'rxjs/operators';
import { Consumer } from '.';
import { Raw } from '../context';
import { getNickname } from '../utils/get-nickname';
import { toEmbed } from '../utils/to-embed';

// Magic-topic
const HIGHLIGHTS = '__HIGHLIGHTS__';

interface ReactionPayload {
  user_id: string;
  message_id: string;
  member: {
    user: {
      username: string;
      id: string;
      discriminator: string;
      avatar: string;
    };
    roles: string[];
    premium_since: string;
    nick: string;
    mute: boolean;
    joined_at: string;
    hoisted_role: string;
    deaf: boolean;
  };
  emoji: {
    name: string;
    id: string;
  };
  channel_id: string;
  guild_id: string;
}

const isMessageReactionAdd = (data: [Raw]): data is [Raw<ReactionPayload>] =>
  data[0].t === 'MESSAGE_REACTION_ADD';

const highlightChannelCache = new Map<string, string>();

const findHighlightChannel = (guild: Discord.Guild) => {
  const cachedId = highlightChannelCache.get(guild.id);

  const channel = cachedId
    ? guild.channels.cache.get(cachedId)
    : guild.channels.cache.find(
        c =>
          c instanceof Discord.TextChannel &&
          !!c.topic &&
          c.topic.includes(HIGHLIGHTS),
      );

  if (!cachedId && channel) {
    highlightChannelCache.set(guild.id, channel?.id);
  }

  return channel instanceof Discord.TextChannel ? channel : undefined;
};

const countMeaningfulReactions = (reactions: Discord.ReactionManager) =>
  reactions.cache.array().reduce((count, reaction) => {
    const bots = reaction.users.cache.array().filter(user => user.bot);
    if (reaction.count == null) return count;
    return count + (reaction.count - bots.length);
  }, 0);

const shouldNoticeReaction = (n: number) => {
  // [5, 10, 20..90, 100, 200...900, 1000, 2000...9000]
  if (n === 5) return true;
  if (n >= 10 && n % 10 ** Math.floor(Math.log10(n)) === 0) return true;
  return false;
};

export const highlight: Consumer = context =>
  context.raw$
    .pipe(filter(isMessageReactionAdd))
    .subscribe(async ([packet]) => {
      const { channel_id, message_id } = packet.d;

      const channel = await context.client.channels.fetch(channel_id);
      if (!(channel instanceof Discord.TextChannel)) return;

      const message = await channel.messages.fetch(message_id);
      const count = countMeaningfulReactions(message.reactions);
      if (!shouldNoticeReaction(count)) return;

      const highlightChannel = findHighlightChannel(channel.guild);
      if (!highlightChannel) return;

      await highlightChannel.send(
        `🎉 ${getNickname(
          message,
        )}さんの投稿が**${count}リアクション**を獲得しました!`,
        {
          embed: toEmbed(message),
        },
      );
    });