aoc2021/aocrr_bot.py
2021-12-07 08:34:49 +01:00

183 lines
7.3 KiB
Python
Executable File

#!/usr/bin/env python3.9
import json
import requests
import sys
from datetime import datetime, timedelta
from time import sleep
from tools.datafiles import JSONFile
from tools.irc import Client
from tools.schedule import Scheduler
from tools.tools import human_readable_time_from_delta
#IRC_SERVER = "irc.eu.libera.chat"
IRC_SERVER = "irc.tu-ilmenau.de"
IRC_PORT = 6667
#IRC_CHANNEL = "#aocrr"
IRC_CHANNEL = "#hinterzimmer"
IRC_NICK = "aocrr-bot"
IRC_USER = "aocrr-bot"
IRC_REALNAME = "#aocrr Leaderboard Announcer"
def fetch_leaderboard(session_id: str, year: int = datetime.now().year) -> dict:
return json.loads(
requests.get(
"https://adventofcode.com/%d/leaderboard/private/view/711147.json" % year,
cookies={'session': session_id}
).content
)
class IrcBot:
def __init__(self):
self.cache = JSONFile("aocrr_bot.cache", create=True)
self.__session_id = open(".session", "r").readlines()[0].strip()
self.irc_client = Client(IRC_SERVER, IRC_PORT, IRC_NICK, IRC_USER, IRC_REALNAME)
self.irc_client.join(IRC_CHANNEL)
self.irc_client.register('PRIVMSG', self.on_privmsg)
self.update_leaderboard()
self.scheduler = Scheduler()
self.scheduler.schedule('irc-receive', timedelta(seconds=10), self.irc_client.receive)
self.scheduler.schedule('leaderboard', timedelta(minutes=15), self.update_leaderboard)
def on_privmsg(self, msg_from: str, msg_to: str, message: str):
if msg_to != IRC_CHANNEL:
return
if message.startswith("!today"):
today = str(datetime.now().day)
day_start = datetime.today().replace(hour=6, minute=0, second=0)
today_list = []
for member, member_data in self.cache.items():
if not member.startswith("__") and 'days' in member_data and today in member_data['days']:
today_list.append(member)
self.irc_client.privmsg(
IRC_CHANNEL,
"Todays leaderboard (last updated: %s):" % self.cache['__last_update__']
)
for i, member in enumerate(sorted(today_list, key=lambda x: self.cache[x]['days'][today]['score'], reverse=True)):
if i > 3:
sleep(1) # don't flood
if "1" in self.cache[member]['days'][today]:
p1_time = "in " + human_readable_time_from_delta(
datetime.fromisoformat(self.cache[member]['days'][today]['1']) - day_start
)
else:
p1_time = "not yet solved"
if "2" in self.cache[member]['days'][today]:
p2_time = "in " + human_readable_time_from_delta(
datetime.fromisoformat(self.cache[member]['days'][today]['2']) - day_start
)
else:
p2_time = "not yet solved"
self.irc_client.privmsg(
IRC_CHANNEL,
"%d) %s (Scores: total: %d, today: %d) p1 %s, p2 %s"
% (
i + 1,
self.cache[member]['name'],
self.cache[member]['score'],
self.cache[member]['days'][today]['score'],
p1_time,
p2_time
)
)
elif message.startswith("!quit") and msg_from.startswith("stha!"):
self.irc_client.privmsg(IRC_CHANNEL, "Oh, ok ... bye :'(")
self.irc_client.quit()
sys.exit(0)
def calc_scores(self):
member_count = len([x for x in self.cache.keys() if not x.startswith("__")])
for day in map(str, range(1, 26)):
p1_times = []
p2_times = []
for member, member_data in self.cache.items():
if member.startswith("__") or day not in member_data['days']:
continue
self.cache[member]['days'][day]['score'] = 0
if '1' in member_data['days'][day]:
p1_times.append(member_data['days'][day]['1'])
if '2' in member_data['days'][day]:
p2_times.append(member_data['days'][day]['2'])
for member, member_data in self.cache.items():
if member.startswith("__") or day not in member_data['days']:
continue
if '1' in member_data['days'][day] and member_data['days'][day]['1'] in p1_times:
score = member_count - sorted(p1_times).index(member_data['days'][day]['1'])
self.cache[member]['days'][day]['score'] += score
if '2' in member_data['days'][day] and member_data['days'][day]['2'] in p2_times:
score = member_count - sorted(p2_times).index(member_data['days'][day]['2'])
self.cache[member]['days'][day]['score'] += score
def update_leaderboard(self):
try:
new_leaderboard = fetch_leaderboard(self.__session_id)
except:
return # didn't work this time? Well, we'll just try again in 15min ...
now = datetime.now()
new_stars = {}
for member, member_data in new_leaderboard['members'].items():
if member not in self.cache:
self.cache[member] = {
'name': member_data['name'],
'days': {},
}
self.cache[member]['global_score'] = int(member_data['global_score'])
self.cache[member]['score'] = int(member_data['local_score'])
self.cache[member]['stars'] = int(member_data['stars'])
for day in member_data['completion_day_level']:
day_start = datetime(now.year, 12, int(day), 6, 0, 0)
if day not in self.cache[member]['days']:
self.cache[member]['days'][day] = {}
for part in member_data['completion_day_level'][day]:
if part not in self.cache[member]['days'][day]:
completion_time = datetime.fromtimestamp(
member_data['completion_day_level'][day][part]['get_star_ts']
)
if member_data['name'] not in new_stars:
new_stars[member_data['name']] = {}
finishing_time = human_readable_time_from_delta(completion_time - day_start)
new_stars[member_data['name']]['d' + day + 'p' + part] = finishing_time
self.cache[member]['days'][day][part] = completion_time.isoformat()
if len(new_stars) > 0:
self.irc_client.privmsg(IRC_CHANNEL, "New Stars found:")
for member, parts in new_stars.items():
line = member + ": "
line += ", ".join(
"%s (%s)"
% (part, new_stars[member][part]) for part in sorted(new_stars[member].keys())
)
self.irc_client.privmsg(IRC_CHANNEL, line)
self.cache['__last_update__'] = datetime.now().isoformat()
self.cache.save()
self.calc_scores()
def run(self):
while 1:
self.scheduler.run_pending()
sleep(1)
if __name__ == '__main__':
ircbot = IrcBot()
ircbot.run()