WIP mostly running again

This commit is contained in:
John Lancaster
2023-05-24 21:24:56 -05:00
parent e46c3bd65b
commit 61e3065a87
7 changed files with 214 additions and 197 deletions

View File

@@ -1,6 +1,6 @@
pandas pandas
# discord.py discord.py
nextcord # nextcord
nltk nltk
python-dotenv python-dotenv
@@ -9,3 +9,4 @@ beautifulsoup4
requests requests
lxml lxml
rich

View File

@@ -2,7 +2,8 @@ import logging
import random import random
import re import re
from nextcord import Message, Client # from nextcord import Message, Client
from discord import Message, Client
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)

View File

@@ -2,8 +2,9 @@ import logging
import re import re
from random import choice from random import choice
from nextcord import Client, Message # from nextcord import Client, Message
from nextcord import utils # from nextcord import utils
from discord import Client, Message, utils
from . import base, helpers from . import base, helpers

View File

@@ -1,165 +1,168 @@
import asyncio import asyncio
import logging import logging
import re import re
import sqlite3 import sqlite3
from datetime import datetime, timedelta from pathlib import Path
from pathlib import Path from typing import List
from typing import List
import pandas as pd
import pandas as pd # from nextcord import Client, Message, TextChannel
from nextcord import Client, Message, TextChannel # from nextcord import RawReactionActionEvent, Emoji
from nextcord import RawReactionActionEvent, Emoji # from nextcord import utils
from nextcord import utils
from discord import Client, Message, TextChannel
from . import jokes from discord import RawReactionActionEvent, Emoji
from .reactions import ReactionData from discord import utils
LIL_STINKY_ID = 704043422276780072 from . import jokes
from .reactions import ReactionData
LOGGER = logging.getLogger(__name__)
LIL_STINKY_ID = 704043422276780072
class Kwaylon(Client): LOGGER = logging.getLogger(__name__)
def __init__(self, limit: int = 5000, days: int = 30, db_path: Path = None, *args, **kwargs):
super().__init__(*args, **kwargs)
if db_path is None: class Kwaylon(Client):
self.db_path = Path(__file__).parents[2] / 'data' / 'messages.db' def __init__(self, limit: int = 5000, days: int = 30, db_path: Path = None, *args, **kwargs):
else: super().__init__(*args, **kwargs)
self.db_path = db_path if db_path is None:
self.db_path = Path(__file__).parents[2] / 'data' / 'messages.db'
self.limit, self.days = limit, days else:
self.jokes = list(jokes.collect_jokes()) self.db_path = db_path
self.lock = asyncio.Lock()
self.limit, self.days = limit, days
self.most_regex = re.compile('^most\s*(?P<emoji>\S+)', re.IGNORECASE) self.jokes = list(jokes.collect_jokes())
self.lock = asyncio.Lock()
def text_channels(self) -> List[TextChannel]:
return [chan for chan in self.get_all_channels() if isinstance(chan, TextChannel)] self.most_regex = re.compile('^most\s*(?P<emoji>\S+)', re.IGNORECASE)
def robotics_facility(self) -> TextChannel: def text_channels(self) -> List[TextChannel]:
for chan in self.text_channels(): return [chan for chan in self.get_all_channels() if isinstance(chan, TextChannel)]
if chan.name == 'robotics-facility' and chan.guild.name == 'Family Dinner':
return chan def robotics_facility(self) -> TextChannel:
for chan in self.text_channels():
def kaylon_emoji(self) -> Emoji: if chan.name == 'robotics-facility' and chan.guild.name == 'Family Dinner':
return utils.get(self.emojis, name='kaylon') return chan
async def handle_ready(self): def kaylon_emoji(self) -> Emoji:
async def alive(): return utils.get(self.emojis, name='kaylon')
channel: TextChannel = self.robotics_facility()
await channel.send('https://tenor.com/view/terminator-im-back-gif-19144173') async def handle_ready(self):
await channel.send(self.kaylon_emoji()) async def alive():
channel: TextChannel = self.robotics_facility()
# await alive() await channel.send('https://tenor.com/view/terminator-im-back-gif-19144173')
await channel.send(self.kaylon_emoji())
try:
self.data = ReactionData(self.db_path) # await alive()
LOGGER.info(f'{self.data.row_count():d} reactions in {self.db_path}')
except sqlite3.Error as e: try:
LOGGER.exception(e) self.data = ReactionData(self.db_path)
LOGGER.error(f'self.db_path: {self.db_path}') LOGGER.info(f'{self.data.row_count():d} reactions in {self.db_path}')
except sqlite3.Error as e:
async def handle_message(self, message: Message): LOGGER.exception(e)
if message.author != self.user: LOGGER.error(f'self.db_path: {self.db_path}')
await self.read_command(message)
await self.respond_to_joke(message) async def handle_message(self, message: Message):
await self.respond_to_emoji(message) if message.author != self.user:
await self.read_command(message)
async def read_command(self, message: Message): await self.respond_to_joke(message)
for mention in message.mentions: await self.respond_to_emoji(message)
if mention.id == self.user.id and 'read' in message.content:
days = get_days(message.content) or self.days async def read_command(self, message: Message):
await self.data.scan_messages(client=self, limit=self.limit, days=days) for mention in message.mentions:
if mention.id == self.user.id and 'read' in message.content:
async def respond_to_emoji(self, message: Message): days = get_days(message.content) or self.days
if (most_match := self.most_regex.match(message.content)): await self.data.scan_messages(client=self, limit=self.limit, days=days)
emoji_ref = most_match.group('emoji')
emoji_name = get_emoji_name(emoji_ref) async def respond_to_emoji(self, message: Message):
LOGGER.info(f'Most {emoji_name}') if (most_match := self.most_regex.match(message.content)):
emoji_ref = most_match.group('emoji')
async with message.channel.typing(): emoji_name = get_emoji_name(emoji_ref)
with self.data.connect() as con: LOGGER.info(f'Most {emoji_name}')
days = get_days(message.content) or 14
async with message.channel.typing():
if days >= 1000: with self.data.connect() as con:
await message.reply( days = get_days(message.content) or 14
f'https://tenor.com/view/i-hate-you-anakin-darth-vader-vader-star-wars-gif-13071041'
) if days >= 1000:
return await message.reply(
f'https://tenor.com/view/i-hate-you-anakin-darth-vader-vader-star-wars-gif-13071041'
df = self.data.read_emoji(emoji_name, con=con, days=days) )
con.close() return
if df.shape[0] > 0: df = self.data.read_emoji(emoji_name, con=con, days=days)
LOGGER.info(f'{df.shape[0]} messages with {emoji_ref} after filtering') con.close()
if 'leaderboard' in message.content: if df.shape[0] > 0:
LOGGER.info(f'Building leaderboard') LOGGER.info(f'{df.shape[0]} messages with {emoji_ref} after filtering')
res = f'{emoji_ref} totals, past {days} days\n'
if (board := await self.leaderboard(df)) is not None: if 'leaderboard' in message.content:
res += board LOGGER.info(f'Building leaderboard')
await message.reply(res) res = f'{emoji_ref} totals, past {days} days\n'
if (board := await self.leaderboard(df)) is not None:
else: res += board
if len(message.mentions) > 0: await message.reply(res)
df = df[df['auth_id'].isin([m.id for m in message.mentions])]
else:
most = df.sort_values('count').iloc[-1] if len(message.mentions) > 0:
msg = await self.fetch_message(most) df = df[df['auth_id'].isin([m.id for m in message.mentions])]
await message.reply(f'Most {emoji_ref} in past {days} days\n{msg.jump_url}')
most = df.sort_values('count').iloc[-1]
# else: msg = await self.fetch_message(most)
# await message.reply(f"NObody (in the past {days} days)...gah, leave me alone!") await message.reply(f'Most {emoji_ref} in past {days} days\n{msg.jump_url}')
LOGGER.info(f'Done') # else:
# await message.reply(f"NObody (in the past {days} days)...gah, leave me alone!")
async def respond_to_joke(self, message: Message):
for joke in self.jokes: LOGGER.info(f'Done')
if (joke_match := joke.scan(message)):
LOGGER.info(f'{joke.__class__.__name__} detected: {message.content}, {joke_match.group()}') async def respond_to_joke(self, message: Message):
await joke.respond(message, self, joke_match) for joke in self.jokes:
if (joke_match := joke.scan(message)):
async def leaderboard(self, df: pd.DataFrame) -> str: LOGGER.info(f'{joke.__class__.__name__} detected: {message.content}, {joke_match.group()}')
df = df.groupby('auth_id').sum() await joke.respond(message, self, joke_match)
counts = df['count'].sort_values(ascending=False)
counts.index = [(await self.fetch_user(idx)).display_name for idx in counts.index] async def leaderboard(self, df: pd.DataFrame) -> str:
df = df.groupby('auth_id').sum()
width = max([len(str(s)) for s in counts.index]) counts = df['count'].sort_values(ascending=False)
counts.index = [(await self.fetch_user(idx)).display_name for idx in counts.index]
res = '\n'.join(
f"`{str(name).ljust(width + 1)}with {cnt:<2.0f} total`" width = max([len(str(s)) for s in counts.index])
for name, cnt in counts.iteritems()
) res = '\n'.join(
return res f"`{str(name).ljust(width + 1)}with {cnt:<2.0f} total`"
for name, cnt in counts.iteritems()
async def handle_raw_reaction(self, payload: RawReactionActionEvent): )
LOGGER.info(payload) return res
guild = await self.fetch_guild(payload.guild_id) async def handle_raw_reaction(self, payload: RawReactionActionEvent):
channel = await guild.fetch_channel(payload.channel_id) LOGGER.info(payload)
message = await channel.fetch_message(payload.message_id)
guild = await self.fetch_guild(payload.guild_id)
async with self.lock: channel = await guild.fetch_channel(payload.channel_id)
with self.data.connect() as con: message = await channel.fetch_message(payload.message_id)
self.data.add_reactions_from_message(message, con)
con.close() async with self.lock:
with self.data.connect() as con:
async def fetch_message(self, row: pd.Series): self.data.add_reactions_from_message(message, con)
guild = await self.fetch_guild(row['guild_id']) con.close()
channel = await guild.fetch_channel(row['channel_id'])
return await channel.fetch_message(row['msg_id']) async def fetch_message(self, row: pd.Series):
guild = await self.fetch_guild(row['guild_id'])
async def scan_messages(self, **kwargs): channel = await guild.fetch_channel(row['channel_id'])
async with self.lock: return await channel.fetch_message(row['msg_id'])
await self.data.scan_messages(client=self, **kwargs)
async def scan_messages(self, **kwargs):
async with self.lock:
def get_emoji_name(string: str) -> str: await self.data.scan_messages(client=self, **kwargs)
if (m := re.search('<:(?P<name>\w+):(?P<id>\d+)>', string)):
string = m.group('name')
return string.lower().strip() def get_emoji_name(string: str) -> str:
if (m := re.search('<:(?P<name>\w+):(?P<id>\d+)>', string)):
string = m.group('name')
def get_days(input_str): return string.lower().strip()
if (m := re.search('(?P<days>\d+) days', input_str)):
return int(m.group('days'))
def get_days(input_str):
if (m := re.search('(?P<days>\d+) days', input_str)):
return int(m.group('days'))

View File

@@ -2,9 +2,12 @@ import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
import pandas as pd import pandas as pd
from nextcord import Client, Message, Reaction from discord import Client, Message, Reaction, TextChannel
from nextcord import TextChannel
from nextcord.utils import AsyncIterator # from nextcord import Client, Message, Reaction
# from nextcord import TextChannel
# from nextcord.utils import AsyncIterator
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@@ -15,7 +18,7 @@ async def message_gen(client: Client,
days: int = None, days: int = None,
after: datetime = None, after: datetime = None,
around: datetime = None, around: datetime = None,
oldest_first: bool = True) -> AsyncIterator[Message]: oldest_first: bool = True):
if days is not None: if days is not None:
after = (datetime.today() - timedelta(days=days)).astimezone() after = (datetime.today() - timedelta(days=days)).astimezone()
@@ -38,7 +41,8 @@ async def message_gen(client: Client,
async for msg in channel.history(**kwargs): async for msg in channel.history(**kwargs):
yield msg yield msg
for thread in channel.threads: for thread in channel.threads:
LOGGER.info(f'Thread: {channel.category}: {channel.name}: {thread.name}') LOGGER.info(
f'Thread: {channel.category}: {channel.name}: {thread.name}')
async for msg in thread.history(**kwargs): async for msg in thread.history(**kwargs):
yield msg yield msg
else: else:
@@ -60,7 +64,7 @@ def reaction_dict(reaction: Reaction):
} }
async def reaction_gen(client: Client, **kwargs) -> AsyncIterator[Reaction]: async def reaction_gen(client: Client, **kwargs):
async for msg in message_gen(client=client, **kwargs): async for msg in message_gen(client=client, **kwargs):
for reaction in msg.reactions: for reaction in msg.reactions:
yield reaction_dict(reaction) yield reaction_dict(reaction)

View File

@@ -5,7 +5,8 @@ from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
import pandas as pd import pandas as pd
from nextcord import Message, Client # from nextcord import Message, Client
from discord import Message, Client
from .msg import reaction_dict, message_gen from .msg import reaction_dict, message_gen

View File

@@ -1,47 +1,53 @@
#!/usr/bin/env python3
import logging
import os import os
import nextcord as discord from discord import Intents, Message, RawReactionActionEvent
from dotenv import load_dotenv from dotenv import load_dotenv
from nextcord import RawReactionActionEvent from rich.highlighter import NullHighlighter
from rich.logging import RichHandler
from kwaylon import Kwaylon from kwaylon import Kwaylon
if __name__ == '__main__': if __name__ == '__main__':
import logging logging.basicConfig(
level=logging.DEBUG,
# https://docs.python.org/3/library/logging.html#logrecord-attributes
format='[magenta]%(name)s[/] [cyan]%(funcName)s[/] %(message)s',
datefmt='%Y-%m-%d %I:%M:%S %p',
handlers=[
RichHandler(
highlighter=NullHighlighter(),
markup=True,
rich_tracebacks=True,
tracebacks_suppress=['pandas'],
)
]
)
logging.basicConfig(level=logging.INFO) for handler in logging.getLogger('discord.client').handlers:
print(handler)
client = Kwaylon()
intents = Intents.default()
intents.message_content = True
client = Kwaylon(intents=intents)
@client.event @client.event
async def on_ready(): async def on_ready():
await client.handle_ready() await client.handle_ready()
# await client.data.scan_messages(
# client=client,
# limit=50,
# # days=7,
# )
# chan = await client.fetch_channel(690588413543579649)
# msg = await chan.fetch_message(936684979654623293)
# logging.info(f'Msg: {msg.clean_content}')
# await msg.reply(f'https://tenor.com/view/i-will-orange-county-jack-black-nodding-nod-gif-4984565')
@client.event @client.event
async def on_message(message: discord.Message): async def on_message(message: Message):
await client.handle_message(message) await client.handle_message(message)
@client.event @client.event
async def on_raw_reaction_add(payload: RawReactionActionEvent): async def on_raw_reaction_add(payload: RawReactionActionEvent):
await client.handle_raw_reaction(payload) await client.handle_raw_reaction(payload)
@client.event @client.event
async def on_raw_reaction_remove(payload: RawReactionActionEvent): async def on_raw_reaction_remove(payload: RawReactionActionEvent):
await client.handle_raw_reaction(payload) await client.handle_raw_reaction(payload)
load_dotenv() load_dotenv()
client.run(os.getenv('DISCORD_TOKEN')) client.run(os.getenv('DISCORD_TOKEN'))