From 021884a5635b3bca0575af0c434d8de1edb1285f Mon Sep 17 00:00:00 2001 From: Brad Brown Date: Tue, 31 May 2022 13:40:22 -0500 Subject: [PATCH] expand local DB to store more data --- pyproject.toml | 1 + wordle.db | Bin 0 -> 24576 bytes wordlinator/app/__init__.py | 25 ++++++- wordlinator/db.py | 126 ++++++++++++++++++++++++++++++++ wordlinator/sheets/__init__.py | 1 + wordlinator/twitter/__init__.py | 30 ++------ 6 files changed, 157 insertions(+), 26 deletions(-) create mode 100644 wordle.db create mode 100644 wordlinator/db.py diff --git a/pyproject.toml b/pyproject.toml index 70a2bec..b6e8d19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ wordlinator = "wordlinator.app:sync_main" update = "wordlinator.app:sync_update" show-user = "wordlinator.app:sync_show_user" show-missing = "wordlinator.app:sync_show_missing" +db-load = "wordlinator.app:load_db_scores" [tool.mypy] ignore_missing_imports = true diff --git a/wordle.db b/wordle.db new file mode 100644 index 0000000000000000000000000000000000000000..f9ce1109ba69b739519e9d7b95ae43702930af25 GIT binary patch literal 24576 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 C%M~~P literal 0 HcmV?d00001 diff --git a/wordlinator/app/__init__.py b/wordlinator/app/__init__.py index 347d9d2..48b2fac 100644 --- a/wordlinator/app/__init__.py +++ b/wordlinator/app/__init__.py @@ -4,6 +4,7 @@ import asyncio import rich import rich.table +import wordlinator.db import wordlinator.sheets import wordlinator.twitter import wordlinator.utils @@ -56,6 +57,19 @@ def print_score_table(wordle_day, scores): rich.print(table) +def _save_db_scores(wordle_day: wordlinator.utils.WordleDay, scores: dict): + db = wordlinator.db.WordleDb() + hole_data = wordle_day.golf_hole + if not hole_data: + return + game_no = hole_data.game_no + for user, score_list in scores.items(): + if not db.get_user(user): + continue + for day, score_entry in enumerate(score_list, start=1): + db.add_score(user, game_no, day, score_entry) + + async def main_update( wordle_day: wordlinator.utils.WordleDay = wordlinator.utils.WORDLE_TODAY, ): @@ -65,7 +79,9 @@ async def main_update( if not any((s is not None for s in today_scores.values())): raise ValueError("No scores pulled!") - sheets_client.update_scores(today_scores) + updated_scores = sheets_client.update_scores(today_scores) + + _save_db_scores(wordle_day, updated_scores) print_score_table(wordle_day, today_scores) @@ -108,6 +124,13 @@ def _get_day(): return wordle_day +def load_db_scores(): + wordle_day = _get_day() + client = wordlinator.sheets.SheetsClient(wordle_day=wordle_day) + scores = client.get_scores() + _save_db_scores(wordle_day, scores) + + def sync_main(): wordle_day = _get_day() asyncio.run(main(wordle_day=wordle_day)) diff --git a/wordlinator/db.py b/wordlinator/db.py new file mode 100644 index 0000000..4ad9b1e --- /dev/null +++ b/wordlinator/db.py @@ -0,0 +1,126 @@ +import sqlite3 + + +class WordleDb: + def __init__(self): + self.con = sqlite3.connect("wordle.db") + cur = self.con.cursor() + cur.execute( + """CREATE TABLE IF NOT EXISTS user + (id INTEGER PRIMARY KEY, + username varchar(50) NOT NULL, + user_id varchar(20) NOT NULL)""" + ) + cur.execute( + """CREATE TABLE IF NOT EXISTS game ( + id INTEGER PRIMARY KEY, + game INTEGER NOT NULL)""" + ) + cur.execute( + """CREATE TABLE IF NOT EXISTS hole + (id INTEGER PRIMARY KEY, + hole INTEGER NOT NULL, + game_id INTEGER NOT NULL, + FOREIGN KEY (game_id) + REFERENCES game (id) + )""" + ) + cur.execute( + """CREATE TABLE IF NOT EXISTS score + (id INTEGER PRIMARY KEY, + score INTEGER NOT NULL, + user_id INTEGER NOT NULL, + game_id INTEGER NOT NULL, + hole_id INTEGER NOT NULL, + FOREIGN KEY (game_id) + REFERENCES game (id), + FOREIGN KEY (user_id) + REFERENCES user (id), + FOREIGN KEY (hole_id) + REFERENCES hole (id), + UNIQUE(user_id, game_id, hole_id) + )""" + ) + self.con.commit() + + def get_user(self, username): + cur = self.con.cursor() + res = list(cur.execute(f"SELECT * FROM user WHERE username = '{username}'")) + return res[0] if res else None + + def get_user_id(self, username): + user = self.get_user(username) + if not user: + return None + return user[2] + + def add_user(self, username, user_id): + cur = self.con.cursor() + cur.execute( + f"INSERT INTO user (username, user_id) VALUES ('{username}', '{user_id}')" + ) + self.con.commit() + + def get_or_create_round(self, round_no): + cur = self.con.cursor() + res = list(cur.execute(f"SELECT * FROM game WHERE game = {round_no}")) + if not res: + list(cur.execute(f"INSERT INTO game (game) VALUES ({round_no})")) + self.con.commit() + res = list(cur.execute(f"SELECT * FROM game WHERE game = {round_no}")) + return res[0] + + def get_or_create_hole(self, round_no, hole_no): + round_id = self.get_or_create_round(round_no)[0] + cur = self.con.cursor() + res = list( + cur.execute( + f"SELECT * FROM hole WHERE hole = {hole_no} AND game_id = {round_id}" + ) + ) + if not res: + cur.execute( + f"INSERT INTO hole (hole, game_id) VALUES ({hole_no}, {round_id})" + ) + res = list( + cur.execute( + "SELECT * FROM hole " + f"WHERE hole = {hole_no} AND game_id = {round_id}" + ) + ) + self.con.commit() + return res[0] + + def create_round_holes(self, round_no): + for hole_no in range(1, 19): + self.get_or_create_hole(round_no, hole_no) + + def add_score(self, username, game, hole, score): + if not score: + return + user = self.get_user(username) + if not user: + raise ValueError("No such user!") + user_id = user[0] + + hole = self.get_or_create_hole(game, hole) + _, hole_id, game_id = hole + + cur = self.con.cursor() + res = list( + cur.execute( + f"""SELECT score FROM score + WHERE user_id = {user_id} AND game_id = {game_id} AND hole_id = {hole_id}""" + ) + ) + if res: + cmd = f"""UPDATE score + SET score = {score} + WHERE user_id = {user_id} + AND game_id = {game_id} + AND hole_id = {hole_id}""" + else: + cmd = f"""INSERT INTO score (score, user_id, game_id, hole_id) + VALUES ({score}, {user_id}, {game_id}, {hole_id})""" + cur.execute(cmd) + self.con.commit() diff --git a/wordlinator/sheets/__init__.py b/wordlinator/sheets/__init__.py index e4f78d5..3d6bffe 100644 --- a/wordlinator/sheets/__init__.py +++ b/wordlinator/sheets/__init__.py @@ -145,6 +145,7 @@ class SheetsClient: current_row[day_idx] = score_val current_scores[name] = current_row self.write_scores(current_scores) + return current_scores def main(): diff --git a/wordlinator/twitter/__init__.py b/wordlinator/twitter/__init__.py index 81a2e62..c0903c0 100644 --- a/wordlinator/twitter/__init__.py +++ b/wordlinator/twitter/__init__.py @@ -4,13 +4,13 @@ import datetime import enum import os import re -import sqlite3 import authlib.integrations.httpx_client import dateutil.parser import httpx import rich +import wordlinator.db import wordlinator.utils BASE_URL = "https://api.twitter.com/2" @@ -90,26 +90,6 @@ class WordleTweet: ) -class UserDb: - def __init__(self): - self.con = sqlite3.connect("users.db") - cur = self.con.cursor() - cur.execute( - """CREATE TABLE IF NOT EXISTS users (username text, user_id text)""" - ) - self.con.commit() - - def get_user(self, username): - cur = self.con.cursor() - res = list(cur.execute(f"SELECT * from users WHERE username = '{username}'")) - return res[0] if res else None - - def add_user(self, username, user_id): - cur = self.con.cursor() - cur.execute(f"INSERT INTO users VALUES ('{username}', '{user_id}')") - self.con.commit() - - class TwitterClient(httpx.AsyncClient): SEARCH_PATH = "tweets/search/recent" USER_PATH = "users/by/username/{username}" @@ -125,7 +105,7 @@ class TwitterClient(httpx.AsyncClient): auth = authlib.integrations.httpx_client.OAuth1Auth(**oauth_creds) kwargs["auth"] = auth super().__init__(base_url=BASE_URL, **kwargs) - self.db = UserDb() + self.db = wordlinator.db.WordleDb() self.wordle_day = wordle_day if not oauth_creds: self.headers["Authorization"] = f"Bearer {TOKEN}" @@ -145,9 +125,9 @@ class TwitterClient(httpx.AsyncClient): return await self.get(self.USER_PATH.format(username=username)) async def get_user_id(self, username: str): - db_user = self.db.get_user(username) - if db_user: - return db_user[1] + db_user_id = self.db.get_user_id(username) + if db_user_id: + return db_user_id else: twitter_user = await self.get_user_by(username) user_id = None