diff --git a/README.md b/README.md index fae04f0..32b94a1 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ * %first - read first message * %rand - read a random message * %last - read last message +* %find - find specific words or phrases * %repeat - repeat last analysis (adding supplied arguments) * %mobile - fix @invalid-user for last command but mentions users * %gdpr - displays GDPR information @@ -115,6 +116,7 @@ python3 src/main.py * **v1.14** * `mobile/mention` arg to fix mobile bug * `%repeat`, `%mobile` to repeat commands + * more scan: `%find` * bug fix * **v1.13** * improved scan `%words` diff --git a/src/data_types/counter.py b/src/data_types/counter.py index 5f6d569..212cf89 100644 --- a/src/data_types/counter.py +++ b/src/data_types/counter.py @@ -14,7 +14,7 @@ class Counter: def update_use(self, count: int, date: datetime, item: int = 0): self.usages[item] += count - if self.last_used is None or date > self.last_used: + if count > 0 and (self.last_used is None or date > self.last_used): self.last_used = date def score(self) -> float: @@ -22,6 +22,8 @@ class Counter: # When 2 emojis have the same score, # the days since last use is stored in the digits # (more recent first) + if self.last_used is None: + return 0 return self.all_usages() + 1 / ( 100000 * ((datetime.today() - self.last_used).days + 1) ) @@ -59,7 +61,7 @@ class Counter: else: output += f"{name} - unused" top_item = top_key(self.usages) - if top and top_item != 0 and transform is not None: + if sum > 0 and top and top_item != 0 and transform is not None: if self.usages[top_item] == sum: output += f" (all{transform(top_item)})" else: diff --git a/src/main.py b/src/main.py index 1cee0f6..97dda5b 100644 --- a/src/main.py +++ b/src/main.py @@ -68,6 +68,12 @@ bot.register_command( "mobile: fix @invalid-user for last command but mentions users", "```\n%mobile: fix @invalid-user for last command but mentions users\n```", ) +bot.register_command( + "find", + lambda *args: scanners.FindScanner().compute(*args), + "find: find specific words or phrases", + scanners.FindScanner.help(), +) bot.register_command( "last", lambda *args: scanners.LastScanner().compute(*args), diff --git a/src/scanners/__init__.py b/src/scanners/__init__.py index aca349b..c174ed7 100644 --- a/src/scanners/__init__.py +++ b/src/scanners/__init__.py @@ -3,6 +3,7 @@ from .scanner import Scanner from .channels_scanner import ChannelsScanner from .composition_scanner import CompositionScanner from .emojis_scanner import EmojisScanner +from .find_scanner import FindScanner from .first_scanner import FirstScanner from .frequency_scanner import FrequencyScanner from .full_scanner import FullScanner diff --git a/src/scanners/find_scanner.py b/src/scanners/find_scanner.py new file mode 100644 index 0000000..334beb0 --- /dev/null +++ b/src/scanners/find_scanner.py @@ -0,0 +1,99 @@ +from typing import Dict, List +from collections import defaultdict +import discord +import re + +# Custom libs + +from logs import ChannelLogs, MessageLog +from .scanner import Scanner +from data_types import Counter +from utils import ( + generate_help, + plural, + precise, + mention, +) + + +class FindScanner(Scanner): + @staticmethod + def help() -> str: + return generate_help( + "find", + "Find specific words or phrases", + args=[ + "all/everyone - include bots", + ], + example='#mychannel1 #mychannel2 @user "I love you" "you too"', + ) + + def __init__(self): + super().__init__( + all_args=True, + valid_args=["all", "everyone"], + help=FindScanner.help(), + intro_context="Matches", + ) + + async def init(self, message: discord.Message, *args: str) -> bool: + self.matches = defaultdict(Counter) + self.all_messages = "all" in args or "everyone" in args + return True + + def compute_message(self, channel: ChannelLogs, message: MessageLog): + return FindScanner.analyse_message( + message, + self.matches, + self.other_args, + self.raw_members, + all_messages=self.all_messages, + ) + + def get_results(self, intro: str) -> List[str]: + matches = [match for match in self.matches] + matches.sort(key=lambda match: self.matches[match].score(), reverse=True) + usage_count = Counter.total(self.matches) + res = [intro] + res += [ + self.matches[match].to_string( + matches.index(match), + f"`{match}`", + total_usage=self.msg_count, + ranking=False, + transform=lambda id: f" by {mention(id)}", + top=len(self.members) != 1, + ) + for match in matches + ] + if len(matches) > 1: + res += [ + f"Total: {plural(usage_count,'time')} ({precise(usage_count/self.msg_count)}/msg)" + ] + return res + + special_cases = ["'s", "s"] + + @staticmethod + def analyse_message( + message: MessageLog, + matches: Dict[str, Counter], + queries: List[str], + raw_members: List[int], + *, + all_messages: bool, + ) -> bool: + impacted = False + # If author is included in the selection (empty list is all) + if ( + (not message.bot or all_messages) + and len(raw_members) == 0 + or message.author in raw_members + ): + impacted = True + content = message.content.lower() + for query in queries: + matches[query].update_use( + content.count(query.lower()), message.created_at, message.author + ) + return impacted