From 8b0fe859a7b9eb9a8d87a9c50a83253433d73c18 Mon Sep 17 00:00:00 2001 From: Klemek Date: Tue, 13 Jul 2021 18:04:46 +0200 Subject: [PATCH] feat: (BETA) %freq graph --- requirements.txt | 2 ++ src/data_types/frequency.py | 44 +++++++++++++++++++++++++++++++ src/scanners/frequency_scanner.py | 18 ++++++++----- src/scanners/scanner.py | 12 +++++++-- 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1ac453a..f45785f 100755 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,5 @@ discord.py==1.7.0 python-dotenv==0.15.0 python-dateutil==2.8.1 git+git://github.com/Klemek/miniscord.git +numpy +matplotlib \ No newline at end of file diff --git a/src/data_types/frequency.py b/src/data_types/frequency.py index a651cfe..886e454 100644 --- a/src/data_types/frequency.py +++ b/src/data_types/frequency.py @@ -1,6 +1,11 @@ from typing import List from datetime import timedelta import calendar +import matplotlib.pyplot as plt +import numpy as np +from io import BytesIO +import discord +import time from utils import ( from_now, @@ -30,6 +35,45 @@ class Frequency: self.longest_streak_start = None self.longest_streak_author = None + def to_graph(self) -> List[str]: + self.dates.sort() + delta = self.dates[-1] - self.dates[0] + if delta.days == 0: + delta = timedelta(days=1) + day = {j: sum(self.hours[i][j] for i in range(7)) for j in range(24)} + busiest_hour = top_key(day) + n_hours = delta.days + if self.dates[0].hour <= busiest_hour and self.dates[-1].hour >= busiest_hour: + n_hours += 1 + + plt.style.use('dark_background') + + fig, ax = plt.subplots() + + fig.patch.set_facecolor('#36393F') + ax.patch.set_alpha(0) + + times = range(25) + + for i in range(7): + hours = [self.hours[i][hour]*7/n_hours for hour in range(24)] + [self.hours[i][0]*7/n_hours] + ax.plot(times, hours, label=calendar.day_name[i], linestyle='--', linewidth=0.8) + + hours = [day[hour]/n_hours for hour in range(24)] + [day[0]/n_hours] + ax.plot(times, hours, c='r', label='average', linewidth=1.5) + + ax.set_xlabel('hour of day') + ax.set_xlim([0, 24]) + ax.set_ylabel('average messages') + ax.legend(framealpha=0) + + with BytesIO() as f: + plt.savefig(f, format='png', facecolor=fig.get_facecolor(), edgecolor='none') + f.seek(0) + return [ + discord.File(f, f"{time.time()}-plot.png") + ] + def to_string( self, *, diff --git a/src/scanners/frequency_scanner.py b/src/scanners/frequency_scanner.py index 1775032..e850a06 100644 --- a/src/scanners/frequency_scanner.py +++ b/src/scanners/frequency_scanner.py @@ -14,11 +14,13 @@ from utils import generate_help class FrequencyScanner(Scanner): @staticmethod def help() -> str: - return generate_help("freq", "Show frequency-related statistics") + return generate_help("freq", "(BETA) Show frequency-related statistics", args=[ + "graph - plot hours of week", + ],) def __init__(self): super().__init__( - valid_args=["all", "everyone"], + valid_args=["all", "everyone", "graph"], help=FrequencyScanner.help(), intro_context="Frequency", ) @@ -27,6 +29,7 @@ class FrequencyScanner(Scanner): self.freq = Frequency() self.all_messages = "all" in args or "everyone" in args self.member_specific = len(self.members) > 0 + self.to_graph = "graph" in args return True def compute_message(self, channel: ChannelLogs, message: MessageLog): @@ -36,10 +39,13 @@ class FrequencyScanner(Scanner): def get_results(self, intro: str) -> List[str]: FrequencyScanner.compute_results(self.freq) - res = [intro] - res += self.freq.to_string( - member_specific=self.member_specific, - ) + if self.to_graph: + res = self.freq.to_graph() + else: + res = [intro] + res += self.freq.to_string( + member_specific=self.member_specific, + ) return res @staticmethod diff --git a/src/scanners/scanner.py b/src/scanners/scanner.py index c73d632..552b1c5 100644 --- a/src/scanners/scanner.py +++ b/src/scanners/scanner.py @@ -288,15 +288,20 @@ class Scanner(ABC): if self.mention_users else discord.AllowedMentions.none() ) + file = None for r in results: if r: - if isinstance(r, int) and r == SPLIT_TOKEN: + if isinstance(r, discord.File): + file = r + elif isinstance(r, int) and r == SPLIT_TOKEN: await message.channel.send( response, reference=message if first else None, allowed_mentions=allowed_mentions, + file=file, ) first = False + file = None response = "" elif isinstance(r, str): if len(response + "\n" + r) > 2000: @@ -304,15 +309,18 @@ class Scanner(ABC): response, reference=message if first else None, allowed_mentions=allowed_mentions, + file=file, ) first = False + file = None response = "" response += "\n" + r - if len(response) > 0: + if len(response) > 0 or file is not None: await message.channel.send( response, reference=message if first else None, allowed_mentions=allowed_mentions, + file=file, ) command_cache.cache(self, message, args) # Delete custom progress message