mirror of
https://github.com/DanilaFe/AdventBot.git
synced 2024-12-22 07:20:10 -08:00
Document and cleanup code.
This commit is contained in:
parent
eb0f3cec07
commit
49de57ae09
148
bot.py
148
bot.py
|
@ -2,17 +2,15 @@ import discord
|
||||||
from discord.ext import tasks
|
from discord.ext import tasks
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
from collections import defaultdict
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
import json
|
import json
|
||||||
|
|
||||||
client = discord.Client()
|
client = discord.Client()
|
||||||
conn = sqlite3.connect('data.db')
|
conn = sqlite3.connect('data.db')
|
||||||
|
star_names = { '1': 'silver', '2': 'gold' }
|
||||||
def setup():
|
aoc_session = os.getenv('AOC_SESSION')
|
||||||
conn.execute('''
|
bot_token = os.getenv('AOC_BOT_TOKEN')
|
||||||
CREATE TABLE IF NOT EXISTS
|
|
||||||
guild(id int primary key, channel int, leaderboard int, last_sync text)
|
|
||||||
''')
|
|
||||||
|
|
||||||
@client.event
|
@client.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
|
@ -32,44 +30,120 @@ async def on_message(message):
|
||||||
'Updated {0.guild} to use channel {0.channel} and leaderboard {1}!'
|
'Updated {0.guild} to use channel {0.channel} and leaderboard {1}!'
|
||||||
.format(message, leaderboard))
|
.format(message, leaderboard))
|
||||||
|
|
||||||
def gather_stars(member):
|
def setup():
|
||||||
|
'''
|
||||||
|
Create all the necessary tables and all that.
|
||||||
|
'''
|
||||||
|
|
||||||
|
conn.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS
|
||||||
|
guild(id int primary key, channel int, leaderboard int, last_sync text)
|
||||||
|
''')
|
||||||
|
|
||||||
|
def member_stars(member):
|
||||||
|
'''
|
||||||
|
Find the stars attributed to a single member
|
||||||
|
in the JSON response. Takes a JSON object
|
||||||
|
from the 'members' dict.
|
||||||
|
'''
|
||||||
|
|
||||||
star_list = []
|
star_list = []
|
||||||
for day in member['completion_day_level']:
|
for day in member['completion_day_level']:
|
||||||
stars = member['completion_day_level'][day]
|
stars = member['completion_day_level'][day]
|
||||||
if "1" in stars:
|
for (star, name) in star_names.items():
|
||||||
star_list.append((member['name'], int(day), 1, stars['1']['get_star_ts']))
|
if star not in stars: continue
|
||||||
if "2" in stars:
|
star_list.append(
|
||||||
star_list.append((member['name'], int(day), 2, stars["2"]['get_star_ts']))
|
(member['name'], day, name, stars[star]['get_star_ts']))
|
||||||
return star_list
|
return star_list
|
||||||
|
|
||||||
def perform_member_diff(old_member, new_member):
|
def all_stars(json):
|
||||||
old_stars = gather_stars(old_member)
|
'''
|
||||||
new_stars = gather_stars(new_member)
|
Find all the stars in a given JSON response.
|
||||||
return [star for star in new_stars if star not in old_stars]
|
The stars are not ordered in any way.
|
||||||
|
'''
|
||||||
|
|
||||||
def perform_diff(old_json, new_json):
|
return [star for m in json['members'].values() for star in member_stars(m)]
|
||||||
if old_json is None:
|
|
||||||
old_json = {'members': {}}
|
def stars_by_day(stars):
|
||||||
else:
|
'''
|
||||||
old_json = json.loads(old_json)
|
Groups stars by day, then by type (silver/gold).
|
||||||
new_json = json.loads(new_json)
|
Helpful when trying to figure out if a given star
|
||||||
diff = { 'join': [], 'new_stars': [] }
|
was "early" (first/second/third) for a particular puzzle.
|
||||||
for new_member in new_json['members'].keys():
|
'''
|
||||||
if new_member not in old_json['members']:
|
|
||||||
diff['join'].append(new_json['members'][new_member]['name'])
|
by_day = defaultdict(lambda: defaultdict(lambda: []))
|
||||||
continue
|
for star in stars:
|
||||||
member_diff = perform_member_diff(old_json['members'][new_member], new_json['members'][new_member])
|
by_day[star[1]][star[2]].append(star)
|
||||||
diff['new_stars'] += member_diff
|
return by_day
|
||||||
return diff
|
|
||||||
|
def read_json(s):
|
||||||
|
'''
|
||||||
|
Try to parse a JSON response. If the argument
|
||||||
|
is None (which it can be if we're pulling from a fresh database)
|
||||||
|
fill it with just enough dummy enough to make the update
|
||||||
|
algorithms work.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if s is None: return {'members': {}}
|
||||||
|
return json.loads(s)
|
||||||
|
|
||||||
|
def detect_early_stars(stars):
|
||||||
|
'''
|
||||||
|
Detects stars for each puzzle that are 'first', 'second', and 'third'.
|
||||||
|
This helps make the announcements a little more interesting!
|
||||||
|
'''
|
||||||
|
|
||||||
|
by_day = stars_by_day(stars)
|
||||||
|
early_stars = []
|
||||||
|
for (day, names) in by_day.items():
|
||||||
|
for (name, stars) in names.items():
|
||||||
|
stars.sort(key=lambda star: int(star[3]))
|
||||||
|
early_stars += list(zip(['first', 'second', 'third'], stars))
|
||||||
|
return early_stars
|
||||||
|
|
||||||
|
def find_updates(old_json, new_json):
|
||||||
|
'''
|
||||||
|
Find interesting differences between the old JSON and the new JSON,
|
||||||
|
both given as either strings or None.
|
||||||
|
|
||||||
|
Interesting differences include member joins (for whom
|
||||||
|
we do not print new stars, since they weren't on the learderboard
|
||||||
|
when they won them), early stars (first/second/third) and other
|
||||||
|
stars (anyone winning a star at any point).
|
||||||
|
'''
|
||||||
|
|
||||||
|
old_json = read_json(old_json)
|
||||||
|
new_json = read_json(new_json)
|
||||||
|
|
||||||
|
old_stars = all_stars(old_json)
|
||||||
|
new_stars = all_stars(new_json)
|
||||||
|
|
||||||
|
join = [m for m in new_json['members'] if m not in old_json['members']]
|
||||||
|
early_stars = [ star for star in detect_early_stars(new_stars)
|
||||||
|
if star[1] not in old_stars
|
||||||
|
and star[1][0] not in join ]
|
||||||
|
skip_stars = [early_star for (place, early_star) in early_stars]
|
||||||
|
ann_stars = [ star for star in new_stars
|
||||||
|
if star not in skip_stars
|
||||||
|
and star not in old_stars
|
||||||
|
and star[0] not in join ]
|
||||||
|
return { 'join': join, 'early_stars': early_stars, 'ann_stars': ann_stars }
|
||||||
|
|
||||||
|
async def send_updates(id, channel, diff):
|
||||||
|
'''
|
||||||
|
Send updates via the Discord client given a diff
|
||||||
|
produced by `find_updates`.
|
||||||
|
'''
|
||||||
|
|
||||||
async def send_diff(id, channel, diff):
|
|
||||||
announcements = []
|
announcements = []
|
||||||
for user in diff['join']:
|
for user in diff['join']:
|
||||||
full_text = "User {0} joined the leaderboard!".format(user)
|
full_text = "User {0} joined the leaderboard!".format(user)
|
||||||
announcements.append(full_text)
|
announcements.append(full_text)
|
||||||
for (user, day, star, ts) in diff['new_stars']:
|
for (place, (user, day, star, ts)) in diff['early_stars']:
|
||||||
star_name = "silver" if star == 1 else "gold"
|
full_text = "{0} won the {1} {2} star from day {3}!".format(user, place, star, day)
|
||||||
full_text = "{0} won the {1} star from day {2}!".format(user, star_name, day)
|
announcements.append(full_text)
|
||||||
|
for (user, day, star, ts) in diff['ann_stars']:
|
||||||
|
full_text = "{0} won the {1} star from day {2}!".format(user, star, day)
|
||||||
announcements.append(full_text)
|
announcements.append(full_text)
|
||||||
|
|
||||||
if len(announcements) != 0:
|
if len(announcements) != 0:
|
||||||
|
@ -79,17 +153,17 @@ async def send_diff(id, channel, diff):
|
||||||
@tasks.loop(minutes=1.0)
|
@tasks.loop(minutes=1.0)
|
||||||
async def update_aoc():
|
async def update_aoc():
|
||||||
url_string = "https://adventofcode.com/2020/leaderboard/private/view/{0}.json"
|
url_string = "https://adventofcode.com/2020/leaderboard/private/view/{0}.json"
|
||||||
cookies = { 'session': os.getenv("AOC_SESSION") }
|
cookies = { 'session': aoc_session }
|
||||||
|
|
||||||
async with ClientSession(cookies=cookies) as session:
|
async with ClientSession(cookies=cookies) as session:
|
||||||
for (id, channel, leaderboard, json) in conn.execute('select * from guild'):
|
for (id, channel, leaderboard, json) in conn.execute('select * from guild'):
|
||||||
async with session.get(url_string.format(leaderboard)) as resp:
|
async with session.get(url_string.format(leaderboard)) as resp:
|
||||||
new_json = await resp.text()
|
new_json = await resp.text()
|
||||||
diff = perform_diff(json, new_json)
|
diff = find_updates(json, new_json)
|
||||||
await send_diff(id, channel, diff)
|
await send_updates(id, channel, diff)
|
||||||
conn.execute('update guild set last_sync=? where id=?', (new_json, id))
|
conn.execute('update guild set last_sync=? where id=?', (new_json, id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
update_aoc.start()
|
update_aoc.start()
|
||||||
client.run(os.environ['AOC_BOT_TOKEN'])
|
client.run(bot_token)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user