From 86dc115697a4a87b5bf163a1e365555db161e280 Mon Sep 17 00:00:00 2001 From: klemek Date: Wed, 13 Jan 2021 16:32:48 +0100 Subject: [PATCH] %mentions --- README.md | 7 ++ src/data_types/__init__.py | 1 + src/data_types/mention.py | 45 ++++++++++++ src/main.py | 11 ++- src/scanners/__init__.py | 1 + src/scanners/mentions_scanner.py | 119 +++++++++++++++++++++++++++++++ src/scanners/scanner.py | 1 - src/utils/utils.py | 8 +++ 8 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 src/data_types/mention.py create mode 100644 src/scanners/mentions_scanner.py diff --git a/README.md b/README.md index 346966d..15d547c 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,12 @@ When you need statistics about your discord server * : top emojis, default is 20 * all : list all common emojis in addition to this guild's * members : show top member for each emote + * sort:usage/reaction : other sorting methods +* `%mentions`: rank mentions by their usage + * arguments: + * : top mentions, default is 10 + * all : show role/channel/everyone/here mentions +* `%cancel` : cancel current analysis * Common arguments: * @member/me : filter for one or more member @@ -64,6 +70,7 @@ python3 src/main.py * emojis percents * emojis other sorting * mentions ranking + * cancel * **v1.6**: * more scans : `%scan`, `%freq`, `%compo`, `%pres` * huge bug fix diff --git a/src/data_types/__init__.py b/src/data_types/__init__.py index aa734be..0aa2d3a 100644 --- a/src/data_types/__init__.py +++ b/src/data_types/__init__.py @@ -2,3 +2,4 @@ from .emote import Emote, get_emote_dict from .frequency import Frequency from .composition import Composition from .presence import Presence +from .mention import Mention diff --git a/src/data_types/mention.py b/src/data_types/mention.py new file mode 100644 index 0000000..7554715 --- /dev/null +++ b/src/data_types/mention.py @@ -0,0 +1,45 @@ +from datetime import datetime + +# Custom libs + +from utils import plural, from_now, percent + + +class Mention: + def __init__(self): + self.usages = 0 + self.last_used = None + + def update_use(self, count: int, date: datetime): + self.usages += count + if self.last_used is None or date > self.last_used: + self.last_used = date + + def score(self) -> float: + # Score is compose of usages + reactions + # When 2 emotes have the same score, + # the days since last use is stored in the digits + # (more recent first) + return self.usages + 1 / ( + 100000 * ((datetime.today() - self.last_used).days + 1) + ) + + def to_string( + self, + i: int, + name: str, + *, + total_usage: int, + ) -> str: + # place + output = "" + if i == 0: + output += ":first_place:" + elif i == 1: + output += ":second_place:" + elif i == 2: + output += ":third_place:" + else: + output += f"**#{i + 1}**" + output += f" {name} - {plural(self.usages, 'time')} ({percent(self.usages/total_usage)}) (last {from_now(self.last_used)})" + return output diff --git a/src/main.py b/src/main.py index 743c3b7..d41df9c 100644 --- a/src/main.py +++ b/src/main.py @@ -8,6 +8,7 @@ from scanners import ( FrequencyScanner, CompositionScanner, PresenceScanner, + MentionsScanner, ) from logs import GuildLogs @@ -29,12 +30,18 @@ bot.register_command( "(cancel|stop)", GuildLogs.cancel, "cancel: stop current analysis", - "", + "```\n" + "%cancel : Stop current analysis\n" + "```", +) +bot.register_command( + "(mentions?)", + lambda *args: MentionsScanner().compute(*args), + "mentions: rank mentions by their usage", + MentionsScanner.help(), ) bot.register_command( "(emojis?|emotes?)", lambda *args: EmotesScanner().compute(*args), - "emojis: emojis analysis", + "emojis: rank emojis by their usage", EmotesScanner.help(), ) bot.register_command( diff --git a/src/scanners/__init__.py b/src/scanners/__init__.py index 51b5d69..1d8bcf6 100644 --- a/src/scanners/__init__.py +++ b/src/scanners/__init__.py @@ -3,3 +3,4 @@ from .frequency_scanner import FrequencyScanner from .composition_scanner import CompositionScanner from .presence_scanner import PresenceScanner from .full_scanner import FullScanner +from .mentions_scanner import MentionsScanner diff --git a/src/scanners/mentions_scanner.py b/src/scanners/mentions_scanner.py new file mode 100644 index 0000000..8bb013d --- /dev/null +++ b/src/scanners/mentions_scanner.py @@ -0,0 +1,119 @@ +from typing import Dict, List +from collections import defaultdict +import discord + + +# Custom libs + +from logs import ChannelLogs, MessageLog +from .scanner import Scanner +from data_types import Mention +from utils import ( + COMMON_HELP_ARGS, + plural, + precise, + mention, + alt_mention, + role_mention, + channel_mention, +) + + +class MentionsScanner(Scanner): + @staticmethod + def help() -> str: + return ( + "```\n" + + "%mentions : Rank mentions by their usage\n" + + "arguments:\n" + + COMMON_HELP_ARGS + + "* : top mentions, default is 10\n" + + "* all : show role/channel/everyone/here mentions\n" + + "Example: %mentions 10 #mychannel1 #mychannel2 @user\n" + + "```" + ) + + def __init__(self): + super().__init__( + has_digit_args=True, + valid_args=["all"], + help=MentionsScanner.help(), + intro_context="Mention usage", + ) + self.top = 10 + + async def init(self, message: discord.Message, *args: str) -> bool: + # get max emotes to view + self.top = 10 + for arg in args: + if arg.isdigit(): + self.top = int(arg) + # check other args + self.all_mentions = "all" in args + # Create mentions dict + self.mentions = defaultdict(Mention) + return True + + def compute_message(self, channel: ChannelLogs, message: MessageLog): + return MentionsScanner.analyse_message( + message, self.mentions, self.raw_members, all_mentions=self.all_mentions + ) + + def get_results(self, intro: str) -> List[str]: + names = [name for name in self.mentions] + names.sort(key=lambda name: self.mentions[name].score(), reverse=True) + names = names[: self.top] + # Get the total of all emotes used + usage_count = sum([mention.usages for mention in self.mentions.values()]) + res = [intro] + res += [ + self.mentions[name].to_string( + names.index(name), + name, + total_usage=usage_count, + ) + for name in names + ] + res += [ + f"Total: {plural(usage_count,'time')} ({precise(usage_count/self.msg_count)}/msg)" + ] + return res + + @staticmethod + def analyse_message( + message: MessageLog, + mentions: Dict[str, Mention], + raw_members: List[int], + *, + all_mentions: bool, + ) -> bool: + impacted = False + # If author is included in the selection (empty list is all) + if not message.bot and len(raw_members) == 0 or message.author in raw_members: + impacted = True + for member_id in message.mentions: + name = mention(member_id) + count = message.content.count(name) + message.content.count( + alt_mention(member_id) + ) + mentions[name].update_use(count, message.created_at) + if all_mentions: + for role_id in message.role_mentions: + name = role_mention(role_id) + mentions[name].update_use( + message.content.count(name), message.created_at + ) + for channel_id in message.channel_mentions: + name = channel_mention(channel_id) + mentions[name].update_use( + message.content.count(name), message.created_at + ) + if "@everyone" in message.content: + mentions["@\u200beveryone"].update_use( + message.content.count("@everyone"), message.created_at + ) + if "@here" in message.content: + mentions["@\u200bhere"].update_use( + message.content.count("@here"), message.created_at + ) + return impacted diff --git a/src/scanners/scanner.py b/src/scanners/scanner.py index 35c3a9f..9bf7b7f 100644 --- a/src/scanners/scanner.py +++ b/src/scanners/scanner.py @@ -46,7 +46,6 @@ class Scanner(ABC): # check args validity str_channel_mentions = [str(channel.id) for channel in message.channel_mentions] str_mentions = [str(member.id) for member in message.mentions] - print(str_mentions) for i, arg in enumerate(args[1:]): if re.match(r"^<@!?\d+>$", arg): arg = arg[3:-1] if "!" in arg else arg[2:-1] diff --git a/src/utils/utils.py b/src/utils/utils.py index fa6d3a8..a8f36a5 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -38,6 +38,14 @@ def mention(member_id: int) -> str: return f"<@{member_id}>" +def alt_mention(member_id: int) -> str: + return f"<@!{member_id}>" + + +def role_mention(role_id: int) -> str: + return f"<@&{role_id}>" + + def channel_mention(channel_id: int) -> str: return f"<#{channel_id}>"