Merge pull request #10 from Klemek/f-history-commands

history commands
This commit is contained in:
Klemek
2021-04-06 23:02:26 +02:00
committed by GitHub
14 changed files with 215 additions and 11 deletions
+4
View File
@@ -18,6 +18,9 @@
* %freq - frequency analysis * %freq - frequency analysis
* %compo - composition analysis * %compo - composition analysis
* %pres - presence analysis * %pres - presence analysis
* %first - read first message
* %rand - read a random message
* %last - read last message
* %emojis - rank emotes by their usage * %emojis - rank emotes by their usage
* arguments: * arguments:
* <n> - top <n> emojis, default is 20 * <n> - top <n> emojis, default is 20
@@ -98,6 +101,7 @@ python3 src/main.py
## Changelog ## Changelog
* **v1.11** * **v1.11**
* more scans `%first`, `%rand`, `%last`
* streak computing in `%pres` * streak computing in `%pres`
* **v1.10** * **v1.10**
* multithreading for queries * multithreading for queries
+1
View File
@@ -3,3 +3,4 @@ from .frequency import Frequency
from .composition import Composition from .composition import Composition
from .presence import Presence from .presence import Presence
from .counter import Counter from .counter import Counter
from .history import History
+39
View File
@@ -0,0 +1,39 @@
from typing import List
import random
# Custom libs
from utils import mention, from_now, str_datetime, message_link
class History:
def __init__(self):
self.messages = []
def to_string(self, *, type: str) -> List[str]:
if len(self.messages) == 0:
return ["There was no messages matching your filters"]
message = None
intro = None
if type == "first":
self.messages.sort(key=lambda m: m.created_at)
message = self.messages[0]
intro = f"First message out of {len(self.messages):,}"
elif type == "last":
self.messages.sort(key=lambda m: m.created_at, reverse=True)
message = self.messages[0]
intro = f"Last message out of {len(self.messages):,}"
elif type == "random":
message = random.choice(self.messages)
intro = f"Random message out of {len(self.messages):,}"
text = ["> " + line for line in message.content.splitlines()]
if message.attachment:
text += ["> <image>" if message.image else "> <attachment>"]
return [
intro,
f"{str_datetime(message.created_at)} ({from_now(message.created_at)}) {mention(message.author)} said:",
*text,
f"<{message_link(message)}>",
]
+6 -5
View File
@@ -1,4 +1,4 @@
from typing import Union, Tuple from typing import Union, Tuple, Any
import discord import discord
from . import MessageLog from . import MessageLog
@@ -9,7 +9,8 @@ FORMAT = 3
class ChannelLogs: class ChannelLogs:
def __init__(self, channel: Union[discord.TextChannel, dict]): def __init__(self, channel: Union[discord.TextChannel, dict], guild: Any):
self.guild = guild
if isinstance(channel, discord.TextChannel): if isinstance(channel, discord.TextChannel):
self.id = channel.id self.id = channel.id
self.name = channel.name self.name = channel.name
@@ -27,7 +28,7 @@ class ChannelLogs:
if channel["last_message_id"] is not None if channel["last_message_id"] is not None
else None else None
) )
self.messages = [MessageLog(message) for message in channel["messages"]] self.messages = [MessageLog(message, self) for message in channel["messages"]]
def is_format(self): def is_format(self):
return self.format == FORMAT return self.format == FORMAT
@@ -44,7 +45,7 @@ class ChannelLogs:
oldest_first=True, oldest_first=True,
): ):
self.last_message_id = message.id self.last_message_id = message.id
m = MessageLog(message) m = MessageLog(message, self)
await m.load(message) await m.load(message)
self.messages.insert(0, m) self.messages.insert(0, m)
yield len(self.messages), False yield len(self.messages), False
@@ -64,7 +65,7 @@ class ChannelLogs:
): ):
done += 1 done += 1
last_message_id = message.id last_message_id = message.id
m = MessageLog(message) m = MessageLog(message, self)
await m.load(message) await m.load(message)
self.messages += [m] self.messages += [m]
yield len(self.messages), False yield len(self.messages), False
+5 -2
View File
@@ -49,6 +49,7 @@ class Worker:
class GuildLogs: class GuildLogs:
def __init__(self, guild: discord.Guild): def __init__(self, guild: discord.Guild):
self.id = guild.id
self.guild = guild self.guild = guild
self.log_file = os.path.join(LOG_DIR, f"{guild.id}.logz") self.log_file = os.path.join(LOG_DIR, f"{guild.id}.logz")
self.channels = {} self.channels = {}
@@ -104,7 +105,9 @@ class GuildLogs:
return CANCELLED, 0 return CANCELLED, 0
await code_message(progress, "Reading saved history (4/4)...") await code_message(progress, "Reading saved history (4/4)...")
t0 = datetime.now() t0 = datetime.now()
self.channels = {int(id): ChannelLogs(channels[id]) for id in channels} self.channels = {
int(id): ChannelLogs(channels[id], self) for id in channels
}
# remove invalid format # remove invalid format
self.channels = { self.channels = {
id: self.channels[id] id: self.channels[id]
@@ -154,7 +157,7 @@ class GuildLogs:
for channel in target_channels: for channel in target_channels:
if channel.id not in self.channels or fresh: if channel.id not in self.channels or fresh:
loading_new += 1 loading_new += 1
self.channels[channel.id] = ChannelLogs(channel) self.channels[channel.id] = ChannelLogs(channel, self)
workers += [Worker(self.channels[channel.id], channel)] workers += [Worker(self.channels[channel.id], channel)]
warning_msg = "(this might take a while)" warning_msg = "(this might take a while)"
if len(target_channels) > 5 and loading_new > 5: if len(target_channels) > 5 and loading_new > 5:
+3 -2
View File
@@ -1,4 +1,4 @@
from typing import Union from typing import Union, Any
import discord import discord
from datetime import datetime from datetime import datetime
@@ -9,7 +9,8 @@ EMBED_IMAGES = ["image", "gifv"]
class MessageLog: class MessageLog:
def __init__(self, message: Union[discord.Message, dict]): def __init__(self, message: Union[discord.Message, dict], channel: Any):
self.channel = channel
if isinstance(message, discord.Message): if isinstance(message, discord.Message):
self.id = message.id self.id = message.id
self.created_at = message.created_at self.created_at = message.created_at
+21
View File
@@ -18,6 +18,9 @@ from scanners import (
MessagesScanner, MessagesScanner,
ChannelsScanner, ChannelsScanner,
ReactionsScanner, ReactionsScanner,
FirstScanner,
RandomScanner,
LastScanner,
) )
from logs import GuildLogs from logs import GuildLogs
@@ -41,6 +44,24 @@ bot.register_command(
"cancel: stop current analysis", "cancel: stop current analysis",
"```\n" + "%cancel: Stop current analysis\n" + "```", "```\n" + "%cancel: Stop current analysis\n" + "```",
) )
bot.register_command(
"last",
lambda *args: LastScanner().compute(*args),
"last: read last message",
LastScanner.help(),
)
bot.register_command(
"rand(om)?",
lambda *args: RandomScanner().compute(*args),
"rand: read a random message",
RandomScanner.help(),
)
bot.register_command(
"first",
lambda *args: FirstScanner().compute(*args),
"first: read first message",
FirstScanner.help(),
)
bot.register_command( bot.register_command(
"mentioned", "mentioned",
lambda *args: MentionedScanner().compute(*args), lambda *args: MentionedScanner().compute(*args),
+3
View File
@@ -8,3 +8,6 @@ from .mentioned_scanner import MentionedScanner
from .messages_scanner import MessagesScanner from .messages_scanner import MessagesScanner
from .channels_scanner import ChannelsScanner from .channels_scanner import ChannelsScanner
from .reactions_scanner import ReactionsScanner from .reactions_scanner import ReactionsScanner
from .first_scanner import FirstScanner
from .last_scanner import LastScanner
from .random_scanner import RandomScanner
+19
View File
@@ -0,0 +1,19 @@
from typing import List
# Custom libs
from .history_scanner import HistoryScanner
class FirstScanner(HistoryScanner):
@staticmethod
def help() -> str:
return super(FirstScanner, FirstScanner).help(
cmd="first", text="Read first message"
)
def __init__(self):
super().__init__(help=FirstScanner.help())
def get_results(self, intro: str) -> List[str]:
return self.history.to_string(type="first")
+70
View File
@@ -0,0 +1,70 @@
from abc import ABC, abstractmethod
from typing import List
import discord
# Custom libs
from .scanner import Scanner
from data_types import History
from logs import ChannelLogs, MessageLog
from utils import COMMON_HELP_ARGS
class HistoryScanner(Scanner, ABC):
@staticmethod
def help(*, cmd: str, text: str) -> str:
return (
"```\n"
+ f"%{cmd}: {text}\n"
+ "arguments:\n"
+ COMMON_HELP_ARGS
+ "* all/everyone - include bots\n"
+ "Example: %{cmd} #mychannel1 @user\n"
+ "```"
)
def __init__(self, *, help: str):
super().__init__(
has_digit_args=True,
valid_args=["all", "everyone"],
help=help,
intro_context="",
)
async def init(self, message: discord.Message, *args: str) -> bool:
self.history = History()
self.all_messages = "all" in args or "everyone" in args
return True
def compute_message(self, channel: ChannelLogs, message: MessageLog):
return HistoryScanner.analyse_message(
channel,
message,
self.history,
self.raw_members,
all_messages=self.all_messages,
)
@abstractmethod
def get_results(self, intro: str):
pass
@staticmethod
def analyse_message(
channel: ChannelLogs,
message: MessageLog,
history: History,
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
) and (message.content or message.attachment):
impacted = True
history.messages += [message]
return impacted
+19
View File
@@ -0,0 +1,19 @@
from typing import List
# Custom libs
from .history_scanner import HistoryScanner
class LastScanner(HistoryScanner):
@staticmethod
def help() -> str:
return super(LastScanner, LastScanner).help(
cmd="last", text="Read last message"
)
def __init__(self):
super().__init__(help=LastScanner.help())
def get_results(self, intro: str) -> List[str]:
return self.history.to_string(type="last")
+19
View File
@@ -0,0 +1,19 @@
from typing import List
# Custom libs
from .history_scanner import HistoryScanner
class RandomScanner(HistoryScanner):
@staticmethod
def help() -> str:
return super(RandomScanner, RandomScanner).help(
cmd="rand", text="Read a random message"
)
def __init__(self):
super().__init__(help=RandomScanner.help())
def get_results(self, intro: str) -> List[str]:
return self.history.to_string(type="random")
+2 -2
View File
@@ -5,7 +5,7 @@ import logging
import re import re
import discord import discord
from utils import no_duplicate, get_intro, delta, deltas, mention, channel_mention from utils import no_duplicate, get_intro, delta
from logs import GuildLogs, ChannelLogs, MessageLog, ALREADY_RUNNING, CANCELLED from logs import GuildLogs, ChannelLogs, MessageLog, ALREADY_RUNNING, CANCELLED
@@ -173,5 +173,5 @@ class Scanner(ABC):
pass pass
@abstractmethod @abstractmethod
def get_results(self, intro: str): def get_results(self, intro: str) -> List[str]:
pass pass
+4
View File
@@ -51,6 +51,10 @@ def channel_mention(channel_id: int) -> str:
return f"<#{channel_id}>" return f"<#{channel_id}>"
def message_link(message: discord.Message) -> str:
return f"https://discord.com/channels/{message.channel.guild.id}/{message.channel.id}/{message.id}"
class FakeMessage: class FakeMessage:
def __init__(self, id: int): def __init__(self, id: int):
self.id = id self.id = id