From 9960989835e5a206947fbe5165a858ed04fd9af3 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Wed, 25 Oct 2023 21:40:58 +0200 Subject: [PATCH 01/28] added verifier --- verifier.py | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 verifier.py diff --git a/verifier.py b/verifier.py new file mode 100644 index 0000000..365ab83 --- /dev/null +++ b/verifier.py @@ -0,0 +1,82 @@ +import dateparser +from datetime import date +import random + +class Status(Enum): + PASS = 0 + FAIL = 1 + RATE_LIMIT = 2 + COMPLETED = 3 + NOT_LOGGED_IN = 4 + UNKNOWN = 5 + +pending = random.shuffle(fetch_all_pending()) # returns a shuffled list + +# p consists of: id, timestamp, answer, status, + +for p in pending: + sub_id = p["id"] + today = date.today().day # returns the current day (number between 1 and 31) + year = date.today().year + solution = get_day_solution(today) # is equal to todays solution, if there is one. Is None otherwise. + if p["timestamp"]day != today: + update_status(sub_id,"INVALID") + continue + if solution: + if solution == p["answer"]: + update_status(sub_id,"CORRECT") + else: + update_status(sub_id,"INCORRECT") + else: + status, response = submit_answer(year, today, 1, p["answer"]) + if status == Status.PASS: + update_status(sub_id,"CORRECT") + set_day_solution(today,p["answer"]) + elif status == Status.FAIL: + update_status(sub_id,"INCORRECT") + elif status == Status.RATE_LIMIT: + sleep(response) + +def submit_answer(year, day, level, answer): + payload = {'level': level, 'answer': answer} + r = requests.post( + f'https://adventofcode.com/{year}/day/{int(day)}/answer', + data=payload, + headers={ + "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de', + } + cookies={'session': config.get_config()['session_cookie']} + ) + response = r.text + if "That's the right answer" in response: + return Status.PASS, None + elif "That's not the right answer" in response: + return Status.FAIL, None + elif 'You gave an answer too recently' in response: + timeout = get_timeout(response) + return Status.RATE_LIMIT, timeout + elif 'Did you already complete it?' in response: + return Status.COMPLETED, None + elif '[Log In]' in response: + return Status.NOT_LOGGED_IN, None + else: + return Status.UNKNOWN, response + +def update_status(submission_id,status): + # changes the "STATUS" value of the submission by <SUBMISSION_ID> to <STATUS> +def get_day_solution(day): + # tries to get solution for day <DAY> from solution database, returns None if there is none +def fetch_all_pending(): + # gets all database entries with the status "pending" as a list of dictionaries +def set_day_solution(day,solution): + # changes the "SOLUTION" value of <DAY> to <SOLUTION> in the solution database + + +def get_timeout(resp): + return dateparser("now")-date.parser(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp))).seconds + + + + + + \ No newline at end of file -- GitLab From 23bc1296cccc2a402126ad218b089b6d4d3eba13 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Thu, 26 Oct 2023 21:42:02 +0200 Subject: [PATCH 02/28] moved verifier to aocp --- verifier.py => aocp/verifier.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename verifier.py => aocp/verifier.py (100%) diff --git a/verifier.py b/aocp/verifier.py similarity index 100% rename from verifier.py rename to aocp/verifier.py -- GitLab From 8df759cb2ec0987a9f582a66bec327e8c85c7158 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Thu, 26 Oct 2023 22:07:37 +0200 Subject: [PATCH 03/28] put into function --- aocp/verifier.py | 69 ++++++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/aocp/verifier.py b/aocp/verifier.py index 365ab83..e04df7b 100644 --- a/aocp/verifier.py +++ b/aocp/verifier.py @@ -1,6 +1,13 @@ import dateparser from datetime import date import random +from enum import Enum +from flask import current_app +import requests +from flask import app + +def get_timeout(resp): + return dateparser("now")-date.parser(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp))).seconds class Status(Enum): PASS = 0 @@ -10,32 +17,32 @@ class Status(Enum): NOT_LOGGED_IN = 4 UNKNOWN = 5 -pending = random.shuffle(fetch_all_pending()) # returns a shuffled list +#pending = random.shuffle(fetch_all_pending()) # returns a shuffled list # p consists of: id, timestamp, answer, status, - -for p in pending: - sub_id = p["id"] - today = date.today().day # returns the current day (number between 1 and 31) - year = date.today().year - solution = get_day_solution(today) # is equal to todays solution, if there is one. Is None otherwise. - if p["timestamp"]day != today: - update_status(sub_id,"INVALID") - continue - if solution: - if solution == p["answer"]: - update_status(sub_id,"CORRECT") +def verify(): + for p in pending: + sub_id = p["id"] + today = date.today().day # returns the current day (number between 1 and 31) + year = date.today().year + solution = get_day_solution(today) # is equal to todays solution, if there is one. Is None otherwise. + if p["timestamp"].day != today: + update_status(sub_id,"INVALID") + continue + if solution: + if solution == p["answer"]: + update_status(sub_id,"CORRECT") + else: + update_status(sub_id,"INCORRECT") else: - update_status(sub_id,"INCORRECT") - else: - status, response = submit_answer(year, today, 1, p["answer"]) - if status == Status.PASS: - update_status(sub_id,"CORRECT") - set_day_solution(today,p["answer"]) - elif status == Status.FAIL: - update_status(sub_id,"INCORRECT") - elif status == Status.RATE_LIMIT: - sleep(response) + status, response = submit_answer(year, today, 1, p["answer"]) + if status == Status.PASS: + update_status(sub_id,"CORRECT") + set_day_solution(today,p["answer"]) + elif status == Status.FAIL: + update_status(sub_id,"INCORRECT") + elif status == Status.RATE_LIMIT: + sleep(response) def submit_answer(year, day, level, answer): payload = {'level': level, 'answer': answer} @@ -44,8 +51,8 @@ def submit_answer(year, day, level, answer): data=payload, headers={ "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de', - } - cookies={'session': config.get_config()['session_cookie']} + }, + cookies={'session': current_app.config["SECRET_AOC_COOKIE"]} ) response = r.text if "That's the right answer" in response: @@ -63,20 +70,20 @@ def submit_answer(year, day, level, answer): return Status.UNKNOWN, response def update_status(submission_id,status): + pass # changes the "STATUS" value of the submission by <SUBMISSION_ID> to <STATUS> def get_day_solution(day): + pass # tries to get solution for day <DAY> from solution database, returns None if there is none def fetch_all_pending(): + pass # gets all database entries with the status "pending" as a list of dictionaries def set_day_solution(day,solution): + pass # changes the "SOLUTION" value of <DAY> to <SOLUTION> in the solution database -def get_timeout(resp): - return dateparser("now")-date.parser(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp))).seconds - - - + +submit_answer(2022,1,1,"30") - \ No newline at end of file -- GitLab From 3d96e2b514c73ff88d7de5263bee539f94cc30fc Mon Sep 17 00:00:00 2001 From: Jake <j.vondoemming@stud.uni-goettingen.de> Date: Thu, 26 Oct 2023 22:26:39 +0200 Subject: [PATCH 04/28] added missing requirements --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index ac80811..d4bce02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,5 @@ Jinja2>=3.1.2 MarkupSafe>=2.1.3 packaging>=23.2 Werkzeug>=3.0.0 +dateparser>=1.1.8 +requests>=2.31.0 -- GitLab From a5c5f87931490696f6d90dc8263a667969751ec7 Mon Sep 17 00:00:00 2001 From: Jake <j.vondoemming@stud.uni-goettingen.de> Date: Thu, 26 Oct 2023 22:27:28 +0200 Subject: [PATCH 05/28] added test-verifier command --- Makefile | 4 ++++ aocp/__init__.py | 4 ++++ aocp/verifier.py | 12 +++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a117a6a..31e1b23 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,10 @@ devserver: routes instance/aocp.sqlite routes: flask --app aocp routes +.PHONY: test-verifier +test-verifier: + flask --app aocp test-verifier + .PHONY: init-db init-db: instance/aocp.sqlite diff --git a/aocp/__init__.py b/aocp/__init__.py index dd91762..9e12b5e 100644 --- a/aocp/__init__.py +++ b/aocp/__init__.py @@ -1,5 +1,6 @@ from . import db from . import auth +from . import verifier import os import json @@ -51,6 +52,9 @@ def create_app(test_config=None): # Initialize the database db.init_app(app) + # Initialize the verifier + verifier.init_app(app) + # Register Blueprints (Paths) app.register_blueprint(auth.bp) diff --git a/aocp/verifier.py b/aocp/verifier.py index e04df7b..176a96b 100644 --- a/aocp/verifier.py +++ b/aocp/verifier.py @@ -6,6 +6,8 @@ from flask import current_app import requests from flask import app +import click + def get_timeout(resp): return dateparser("now")-date.parser(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp))).seconds @@ -84,6 +86,14 @@ def set_day_solution(day,solution): -submit_answer(2022,1,1,"30") +#submit_answer(2022,1,1,"30") +@click.command('test-verifier') +def test_verifier_command(): + """Execute the verifier once for testing.""" + click.echo('Testing verifier...') + verify() + +def init_app(app): + app.cli.add_command(test_verifier_command) -- GitLab From 2ee79052e58da1af6a1096bb57a656bb9f7b337e Mon Sep 17 00:00:00 2001 From: Jake <j.vondoemming@stud.uni-goettingen.de> Date: Thu, 26 Oct 2023 22:30:08 +0200 Subject: [PATCH 06/28] test-verifier is now in debug mode --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 31e1b23..6d5dbd7 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ routes: .PHONY: test-verifier test-verifier: - flask --app aocp test-verifier + flask --app aocp test-verifier --debug .PHONY: init-db init-db: instance/aocp.sqlite -- GitLab From 4a17a2e651de602dc1d6dfc92fe6f42abe2d585d Mon Sep 17 00:00:00 2001 From: Jake <j.vondoemming@stud.uni-goettingen.de> Date: Thu, 26 Oct 2023 22:30:45 +0200 Subject: [PATCH 07/28] test-verifier is now in debug mode --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6d5dbd7..3417683 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ routes: .PHONY: test-verifier test-verifier: - flask --app aocp test-verifier --debug + flask --app aocp --debug test-verifier .PHONY: init-db init-db: instance/aocp.sqlite -- GitLab From 15fce4bee791df656989492e891f8fbdc6490fff Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Thu, 26 Oct 2023 22:47:52 +0200 Subject: [PATCH 08/28] submit_solution can now be tested --- aocp/verifier.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/aocp/verifier.py b/aocp/verifier.py index 176a96b..b2f0ec6 100644 --- a/aocp/verifier.py +++ b/aocp/verifier.py @@ -5,11 +5,12 @@ from enum import Enum from flask import current_app import requests from flask import app +import re import click def get_timeout(resp): - return dateparser("now")-date.parser(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp))).seconds + return (dateparser.parse("now")-dateparser.parse(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp)))).seconds class Status(Enum): PASS = 0 @@ -19,10 +20,10 @@ class Status(Enum): NOT_LOGGED_IN = 4 UNKNOWN = 5 -#pending = random.shuffle(fetch_all_pending()) # returns a shuffled list # p consists of: id, timestamp, answer, status, def verify(): + pending = fetch_all_pending() # returns a shuffled list for p in pending: sub_id = p["id"] today = date.today().day # returns the current day (number between 1 and 31) @@ -54,21 +55,36 @@ def submit_answer(year, day, level, answer): headers={ "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de', }, - cookies={'session': current_app.config["SECRET_AOC_COOKIE"]} + cookies={'session': current_app.config["SESSION_COOKIE"]} ) response = r.text if "That's the right answer" in response: + click.echo("CORRECT ANSWER") + return Status.PASS, None elif "That's not the right answer" in response: + click.echo("WROOOOONG") + return Status.FAIL, None elif 'You gave an answer too recently' in response: timeout = get_timeout(response) + click.echo("RATE LIMIT: "+str(timeout)) + return Status.RATE_LIMIT, timeout + elif 'Did you already complete it?' in response: + click.echo("ALREADY COMPLETED") + return Status.COMPLETED, None + elif '[Log In]' in response: + click.echo("NOT LOGGED IN") + return Status.NOT_LOGGED_IN, None + else: + click.echo("HMMM- SUS") + return Status.UNKNOWN, response def update_status(submission_id,status): @@ -93,7 +109,7 @@ def set_day_solution(day,solution): def test_verifier_command(): """Execute the verifier once for testing.""" click.echo('Testing verifier...') - verify() + submit_answer(2022,1,1,"30") def init_app(app): app.cli.add_command(test_verifier_command) -- GitLab From e024ad5460f26f4319187e2aa553de266c1c7f3a Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Fri, 27 Oct 2023 00:27:00 +0200 Subject: [PATCH 09/28] started working on score.py --- score.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 score.py diff --git a/score.py b/score.py new file mode 100644 index 0000000..c9a94bc --- /dev/null +++ b/score.py @@ -0,0 +1,15 @@ +def calculate_points(day, timedelta, is_first): + # get "base points" depending on day + base_points = 1+ day // 5 + bonus_time_points = timedelta // 8 + + +def is_first_solve(sub_id,day): + # query all "CORRECT"s of <DAY> and return the SUBMISSION ID first one + first = get_first(day) + return sub_id = first + +def get_datetime_info(timestamp): + day = timestamp.day() + aoc_hour = ( timestamp.hour -6 )% 24 + return day, aoc_hour -- GitLab From e5226afdd92971b74c6aaadd219935180ed6a583 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Fri, 27 Oct 2023 11:01:28 +0200 Subject: [PATCH 10/28] added proper timezone conversion --- score.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/score.py b/score.py index c9a94bc..ffb2a33 100644 --- a/score.py +++ b/score.py @@ -1,3 +1,6 @@ +from datetime import datetime +from zoneinfo import ZoneInfo + def calculate_points(day, timedelta, is_first): # get "base points" depending on day base_points = 1+ day // 5 @@ -10,6 +13,7 @@ def is_first_solve(sub_id,day): return sub_id = first def get_datetime_info(timestamp): - day = timestamp.day() - aoc_hour = ( timestamp.hour -6 )% 24 - return day, aoc_hour + conv_time = timestamp.astimezone(ZoneInfo("EST")) + day = conv_time.day + hour = conv_time.hour + return day,hour -- GitLab From 147573649549c5ec1c4979dcfb41bf3b6d53562d Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Fri, 27 Oct 2023 11:06:53 +0200 Subject: [PATCH 11/28] calculate_points done --- score.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/score.py b/score.py index ffb2a33..9a5d6d1 100644 --- a/score.py +++ b/score.py @@ -4,7 +4,19 @@ from zoneinfo import ZoneInfo def calculate_points(day, timedelta, is_first): # get "base points" depending on day base_points = 1+ day // 5 - bonus_time_points = timedelta // 8 + # all ranges inclusive + # 0-8: 2 points + # 8-16: 1 point + # 16-24: 0 points + bonus_time_points = (24-timedelta) // 8 + if is_first: + bonus_first = 1 + else: + bonus_first = 0 + score = base_points + bonus_time_points + bonus_first + return score + + def is_first_solve(sub_id,day): -- GitLab From cc346947bd17088fe82b59ee98c7f991ff7e7460 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Fri, 27 Oct 2023 11:08:30 +0200 Subject: [PATCH 12/28] added comments and missing function --- score.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/score.py b/score.py index 9a5d6d1..4744461 100644 --- a/score.py +++ b/score.py @@ -3,29 +3,29 @@ from zoneinfo import ZoneInfo def calculate_points(day, timedelta, is_first): # get "base points" depending on day - base_points = 1+ day // 5 - # all ranges inclusive + base_points = 1+ day // 5 # base points increase by 1 every 5 days. + # bonus time points: (all ranges inclusive) # 0-8: 2 points # 8-16: 1 point # 16-24: 0 points bonus_time_points = (24-timedelta) // 8 - if is_first: + if is_first: # bonus point if person is first to solve on given day. bonus_first = 1 else: bonus_first = 0 score = base_points + bonus_time_points + bonus_first return score - - - def is_first_solve(sub_id,day): - # query all "CORRECT"s of <DAY> and return the SUBMISSION ID first one first = get_first(day) return sub_id = first def get_datetime_info(timestamp): - conv_time = timestamp.astimezone(ZoneInfo("EST")) + conv_time = timestamp.astimezone(ZoneInfo("EST")) # convert to EST time day = conv_time.day hour = conv_time.hour return day,hour + +def get_first(day): + pass + # query all "CORRECT"s of <DAY> and return the SUBMISSION ID first one -- GitLab From 91b9538d14658dfb549deefea5734cdd8ab7ece4 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Mon, 30 Oct 2023 21:43:44 +0100 Subject: [PATCH 13/28] added comments --- score.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/score.py b/score.py index 4744461..6016f98 100644 --- a/score.py +++ b/score.py @@ -2,6 +2,18 @@ from datetime import datetime from zoneinfo import ZoneInfo def calculate_points(day, timedelta, is_first): + """ + Calculates the points based on day, time and if the solver was first. + + + Parameters: + day -- Day of the solution. + timedelta -- Hour of the solution. + is_first -- Boolean (True if person was first to solve problem). + + Returns: + score -- the score + """ # get "base points" depending on day base_points = 1+ day // 5 # base points increase by 1 every 5 days. # bonus time points: (all ranges inclusive) -- GitLab From 62085606044f58c6fde63e363dd91512b54a0e6a Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Mon, 30 Oct 2023 21:46:11 +0100 Subject: [PATCH 14/28] moved everything related to aoc to extra file --- aoc.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ aocp/verifier.py | 55 ------------------------------------------- 2 files changed, 61 insertions(+), 55 deletions(-) create mode 100644 aoc.py diff --git a/aoc.py b/aoc.py new file mode 100644 index 0000000..002d635 --- /dev/null +++ b/aoc.py @@ -0,0 +1,61 @@ +import dateparser +from datetime import date +from enum import Enum +from flask import current_app +import requests +from flask import app +import re +import click + +def get_timeout(resp): + return (dateparser.parse("now")-dateparser.parse(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp)))).seconds + +class Status(Enum): + PASS = 0 + FAIL = 1 + RATE_LIMIT = 2 + COMPLETED = 3 + NOT_LOGGED_IN = 4 + UNKNOWN = 5 + + + +def submit_answer(year, day, level, answer): + payload = {'level': level, 'answer': answer} + r = requests.post( + f'https://adventofcode.com/{year}/day/{int(day)}/answer', + data=payload, + headers={ + "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de', + }, + cookies={'session': current_app.config["SESSION_COOKIE"]} + ) + response = r.text + if "That's the right answer" in response: + click.echo("CORRECT ANSWER") + + return Status.PASS, None + elif "That's not the right answer" in response: + click.echo("WROOOOONG") + + return Status.FAIL, None + elif 'You gave an answer too recently' in response: + timeout = get_timeout(response) + click.echo("RATE LIMIT: "+str(timeout)) + + return Status.RATE_LIMIT, timeout + + elif 'Did you already complete it?' in response: + click.echo("ALREADY COMPLETED") + + return Status.COMPLETED, None + + elif '[Log In]' in response: + click.echo("NOT LOGGED IN") + + return Status.NOT_LOGGED_IN, None + + else: + click.echo("HMMM- SUS") + + return Status.UNKNOWN, response \ No newline at end of file diff --git a/aocp/verifier.py b/aocp/verifier.py index b2f0ec6..516919d 100644 --- a/aocp/verifier.py +++ b/aocp/verifier.py @@ -1,25 +1,9 @@ import dateparser from datetime import date -import random -from enum import Enum from flask import current_app -import requests from flask import app -import re - import click -def get_timeout(resp): - return (dateparser.parse("now")-dateparser.parse(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp)))).seconds - -class Status(Enum): - PASS = 0 - FAIL = 1 - RATE_LIMIT = 2 - COMPLETED = 3 - NOT_LOGGED_IN = 4 - UNKNOWN = 5 - # p consists of: id, timestamp, answer, status, def verify(): @@ -47,45 +31,6 @@ def verify(): elif status == Status.RATE_LIMIT: sleep(response) -def submit_answer(year, day, level, answer): - payload = {'level': level, 'answer': answer} - r = requests.post( - f'https://adventofcode.com/{year}/day/{int(day)}/answer', - data=payload, - headers={ - "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de', - }, - cookies={'session': current_app.config["SESSION_COOKIE"]} - ) - response = r.text - if "That's the right answer" in response: - click.echo("CORRECT ANSWER") - - return Status.PASS, None - elif "That's not the right answer" in response: - click.echo("WROOOOONG") - - return Status.FAIL, None - elif 'You gave an answer too recently' in response: - timeout = get_timeout(response) - click.echo("RATE LIMIT: "+str(timeout)) - - return Status.RATE_LIMIT, timeout - - elif 'Did you already complete it?' in response: - click.echo("ALREADY COMPLETED") - - return Status.COMPLETED, None - - elif '[Log In]' in response: - click.echo("NOT LOGGED IN") - - return Status.NOT_LOGGED_IN, None - - else: - click.echo("HMMM- SUS") - - return Status.UNKNOWN, response def update_status(submission_id,status): pass -- GitLab From 54b1b945227470b633db8fa0dd4b6c227db32cb6 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 19:01:24 +0100 Subject: [PATCH 15/28] verifier works now probably --- aocp/verifier.py | 55 ++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/aocp/verifier.py b/aocp/verifier.py index 516919d..680a7d2 100644 --- a/aocp/verifier.py +++ b/aocp/verifier.py @@ -3,33 +3,35 @@ from datetime import date from flask import current_app from flask import app import click +from . import db as database +import time +from . import aoc +from typing import cast # p consists of: id, timestamp, answer, status, def verify(): - pending = fetch_all_pending() # returns a shuffled list - for p in pending: - sub_id = p["id"] - today = date.today().day # returns the current day (number between 1 and 31) - year = date.today().year - solution = get_day_solution(today) # is equal to todays solution, if there is one. Is None otherwise. - if p["timestamp"].day != today: - update_status(sub_id,"INVALID") - continue + db = database.get_db() + pending = fetch_all_pending() # returns a dataframe + for index, p in pending.iterrows(): + (uid,day,val) = cast(tuple,index) + year = 2022 + solution = db.fetch_day_solution(day) # is equal to todays solution, if there is one. Is None otherwise. if solution: - if solution == p["answer"]: - update_status(sub_id,"CORRECT") + if solution == val: + db.update_submission_status(userid=uid,day=day,value=val,status="CORRECT") else: - update_status(sub_id,"INCORRECT") + db.update_submission_status(userid=uid,day=day,value=val,status="INCORRECT") else: - status, response = submit_answer(year, today, 1, p["answer"]) - if status == Status.PASS: - update_status(sub_id,"CORRECT") - set_day_solution(today,p["answer"]) - elif status == Status.FAIL: - update_status(sub_id,"INCORRECT") - elif status == Status.RATE_LIMIT: - sleep(response) + status, timeout = aoc.submit_answer(year, day, 1, val) + if status == aoc.Status.PASS: + db.update_submission_status(userid=uid,day=day,value=val,status="CORRECT") + db.set_day_solution(day,val) + elif status == aoc.Status.FAIL: + db.update_submission_status(userid=uid,day=day,value=val,status="INCORRECT") + elif status == aoc.Status.RATE_LIMIT: + time.sleep(timeout) + def update_status(submission_id,status): @@ -39,7 +41,7 @@ def get_day_solution(day): pass # tries to get solution for day <DAY> from solution database, returns None if there is none def fetch_all_pending(): - pass + return database.get_db().fetch_submissions(status="PENDING") # gets all database entries with the status "pending" as a list of dictionaries def set_day_solution(day,solution): pass @@ -54,7 +56,14 @@ def set_day_solution(day,solution): def test_verifier_command(): """Execute the verifier once for testing.""" click.echo('Testing verifier...') - submit_answer(2022,1,1,"30") - + #submit_answer(2022,1,1,"30") + current_app.logger.debug(database.get_db().set_day_puzzle(day=3,puzzle=b'203nsk20')) + current_app.logger.debug(database.get_db().set_day_puzzle(day=7,puzzle=b'2138')) + current_app.logger.debug(database.get_db().set_day_puzzle(day=20,puzzle=b'abcabca')) + current_app.logger.debug(database.get_db().set_day_puzzle(day=1,puzzle=b'aksjndaks')) + current_app.logger.debug(database.get_db().set_day_solution(3,solution="123")) + current_app.logger.debug(database.get_db().set_day_solution(7,solution="13")) + current_app.logger.debug(database.get_db().set_day_solution(20,solution="abcabca")) + current_app.logger.debug(verify()) def init_app(app): app.cli.add_command(test_verifier_command) -- GitLab From 4203bfa3f5e7d41ccf64e3c5bb53d7b67092a78d Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 19:04:09 +0100 Subject: [PATCH 16/28] added download function --- aocp/aoc.py | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100755 aocp/aoc.py diff --git a/aocp/aoc.py b/aocp/aoc.py new file mode 100755 index 0000000..c7bbdec --- /dev/null +++ b/aocp/aoc.py @@ -0,0 +1,94 @@ +from typing import Literal +import dateparser +from datetime import date, datetime +from enum import Enum +from flask import current_app +import requests +from flask import app +import re +import click +import requests + +current_year = date.today().year - 1 + +def get_timeout(resp:str)->int: + """ + extracts a timeout from the response text. + returns the timeout in sec or 0 if parsing wasnt successful. + Example: get_timeout("please wait 20m") would return 1200. + """ + current_time = datetime.now() + timeout = dateparser.parse(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp))) + if timeout: + return (current_time - timeout).seconds + else: + return 0 + + +class Status(Enum): + PASS = 0 + FAIL = 1 + RATE_LIMIT = 2 + COMPLETED = 3 + NOT_LOGGED_IN = 4 + UNKNOWN = 5 + + + +def submit_answer(year, day, level, answer)->tuple[Status,int|None]: + payload = {'level': level, 'answer': answer} + r = requests.post( + f'https://adventofcode.com/{year}/day/{int(day)}/answer', + data=payload, + headers={ + "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de', + }, + cookies={'session': current_app.config["SESSION_COOKIE"]} + ) + response = r.text + if "That's the right answer" in response: + #click.echo("CORRECT ANSWER") + + return Status.PASS, None + elif "That's not the right answer" in response: + #click.echo("WROOOOONG") + + return Status.FAIL, None + elif 'You gave an answer too recently' in response: + timeout = get_timeout(response) + click.echo("RATE LIMIT: "+str(timeout)) + + return Status.RATE_LIMIT, timeout + + elif 'Did you already complete it?' in response: + #click.echo("ALREADY COMPLETED") + + return Status.COMPLETED, None + + elif '[Log In]' in response: + #click.echo("NOT LOGGED IN") + + return Status.NOT_LOGGED_IN, None + + else: + #click.echo("HMMM- SUS") + return Status.UNKNOWN, response + +def get_puzzle(day:int): + puzzle = requests.get("https://adventofcode.com/"+str(current_year)+"/day/"+str(day)+"/input",allow_redirects=True, headers={ + "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de', + },cookies={'session': current_app.config["SESSION_COOKIE"]}).content + return puzzle + + +@click.command('test-aoc-py') +def test_aoc_py_command(): + """testing commands""" + current_app.logger.info("testing get_puzzle") + current_app.logger.debug(get_puzzle(20)) + +def init_app(app): + """ + Gets called by __init__.py on initialization of the flask App. + """ + app.cli.add_command(test_aoc_py_command) -- GitLab From ab279f28410e2460adc6403c426461f5309b1dc1 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 19:04:27 +0100 Subject: [PATCH 17/28] added things for testing purposes --- Makefile | 5 +++++ aocp/__init__.py | 7 +++++++ temp.css | 15 +++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 temp.css diff --git a/Makefile b/Makefile index 1734e6b..4f16d4d 100644 --- a/Makefile +++ b/Makefile @@ -23,3 +23,8 @@ init-db: reset-db: $(RM) instance/aocp.sqlite flask --app aocp init-db + + +.PHONY: test-aoc-py +test-aoc-py: + flask --app aocp --debug test-aoc-py \ No newline at end of file diff --git a/aocp/__init__.py b/aocp/__init__.py index adaa288..195e0dd 100644 --- a/aocp/__init__.py +++ b/aocp/__init__.py @@ -1,6 +1,7 @@ from . import db from . import auth from . import verifier +from . import aoc import os import json @@ -93,4 +94,10 @@ def create_app(test_config=None): auth.init_app(app) app.register_blueprint(auth.bp) + # Initialize aoc API + aoc.init_app(app) + + # Initialize verifier + verifier.init_app(app) + return app diff --git a/temp.css b/temp.css new file mode 100644 index 0000000..8d0a17e --- /dev/null +++ b/temp.css @@ -0,0 +1,15 @@ +*{ + color: #1d4289; + color: #00bf7d ; + color: #00b4c5 ; + color: #d80c18 ; + color: #8a0008 ; + color: #65942f ; + color: #056010 ; + color: #683abf ; + color: #2015d0 ; + color: #d54920 ; + color: #bc4498 ; + color: #9e6421 ; + color: #22808d ; +} \ No newline at end of file -- GitLab From 90a40bc8a2270d248242ddb430b0730362e2a4b8 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 19:10:30 +0100 Subject: [PATCH 18/28] =?UTF-8?q?m=C3=BCll=20entfernt.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- temp.css | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 temp.css diff --git a/temp.css b/temp.css deleted file mode 100644 index 8d0a17e..0000000 --- a/temp.css +++ /dev/null @@ -1,15 +0,0 @@ -*{ - color: #1d4289; - color: #00bf7d ; - color: #00b4c5 ; - color: #d80c18 ; - color: #8a0008 ; - color: #65942f ; - color: #056010 ; - color: #683abf ; - color: #2015d0 ; - color: #d54920 ; - color: #bc4498 ; - color: #9e6421 ; - color: #22808d ; -} \ No newline at end of file -- GitLab From 4841c37e8e68fd018390d755cc3a26b4d18704a7 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 20:26:41 +0100 Subject: [PATCH 19/28] moved to aocp --- score.py => aocp/score.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename score.py => aocp/score.py (100%) diff --git a/score.py b/aocp/score.py similarity index 100% rename from score.py rename to aocp/score.py -- GitLab From 3a6897629ebe56d37510baf95f6972984248a997 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 20:37:45 +0100 Subject: [PATCH 20/28] added documentation --- aocp/aoc.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/aocp/aoc.py b/aocp/aoc.py index c7bbdec..1f179e7 100755 --- a/aocp/aoc.py +++ b/aocp/aoc.py @@ -26,6 +26,10 @@ def get_timeout(resp:str)->int: class Status(Enum): + """ + possible status after sending a request to aoc website. + gets returned by submit_answer + """ PASS = 0 FAIL = 1 RATE_LIMIT = 2 @@ -35,7 +39,11 @@ class Status(Enum): -def submit_answer(year, day, level, answer)->tuple[Status,int|None]: +def submit_answer(year:int, day:int, level:int, answer:str)->tuple[Status,int|None]: + """ + submits an answer to the aoc website for a given year and day. + returns a Status according to the result and, if there is a timeout, the timeout in seconds. + """ payload = {'level': level, 'answer': answer} r = requests.post( f'https://adventofcode.com/{year}/day/{int(day)}/answer', @@ -74,8 +82,11 @@ def submit_answer(year, day, level, answer)->tuple[Status,int|None]: #click.echo("HMMM- SUS") return Status.UNKNOWN, response -def get_puzzle(day:int): - puzzle = requests.get("https://adventofcode.com/"+str(current_year)+"/day/"+str(day)+"/input",allow_redirects=True, headers={ +def get_puzzle(year:int, day:int)->bytes: + """ + returns the puzzle of a day as bytes. + """ + puzzle = requests.get("https://adventofcode.com/"+str(year)+"/day/"+str(day)+"/input",allow_redirects=True, headers={ "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de', },cookies={'session': current_app.config["SESSION_COOKIE"]}).content return puzzle -- GitLab From b5b6297c24115750b038a77ed2d4daac0c9b9ae6 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 20:42:10 +0100 Subject: [PATCH 21/28] no int --- aocp/aoc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aocp/aoc.py b/aocp/aoc.py index 1f179e7..a9dff99 100755 --- a/aocp/aoc.py +++ b/aocp/aoc.py @@ -46,7 +46,7 @@ def submit_answer(year:int, day:int, level:int, answer:str)->tuple[Status,int|No """ payload = {'level': level, 'answer': answer} r = requests.post( - f'https://adventofcode.com/{year}/day/{int(day)}/answer', + f'https://adventofcode.com/{year}/day/{day}/answer', data=payload, headers={ "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de', -- GitLab From 76d2a879cba6cd27a4db8222bc12fca234c627bc Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 20:44:35 +0100 Subject: [PATCH 22/28] removed dupliate file --- aoc.py | 61 ---------------------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 aoc.py diff --git a/aoc.py b/aoc.py deleted file mode 100644 index 002d635..0000000 --- a/aoc.py +++ /dev/null @@ -1,61 +0,0 @@ -import dateparser -from datetime import date -from enum import Enum -from flask import current_app -import requests -from flask import app -import re -import click - -def get_timeout(resp): - return (dateparser.parse("now")-dateparser.parse(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp)))).seconds - -class Status(Enum): - PASS = 0 - FAIL = 1 - RATE_LIMIT = 2 - COMPLETED = 3 - NOT_LOGGED_IN = 4 - UNKNOWN = 5 - - - -def submit_answer(year, day, level, answer): - payload = {'level': level, 'answer': answer} - r = requests.post( - f'https://adventofcode.com/{year}/day/{int(day)}/answer', - data=payload, - headers={ - "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de', - }, - cookies={'session': current_app.config["SESSION_COOKIE"]} - ) - response = r.text - if "That's the right answer" in response: - click.echo("CORRECT ANSWER") - - return Status.PASS, None - elif "That's not the right answer" in response: - click.echo("WROOOOONG") - - return Status.FAIL, None - elif 'You gave an answer too recently' in response: - timeout = get_timeout(response) - click.echo("RATE LIMIT: "+str(timeout)) - - return Status.RATE_LIMIT, timeout - - elif 'Did you already complete it?' in response: - click.echo("ALREADY COMPLETED") - - return Status.COMPLETED, None - - elif '[Log In]' in response: - click.echo("NOT LOGGED IN") - - return Status.NOT_LOGGED_IN, None - - else: - click.echo("HMMM- SUS") - - return Status.UNKNOWN, response \ No newline at end of file -- GitLab From 7ae09d1d2476f170d49729fb390321991aebbf1e Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 20:55:03 +0100 Subject: [PATCH 23/28] added documentation, removed unnecessary functions --- aocp/score.py | 1 + aocp/verifier.py | 35 +++++++++++++++++------------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/aocp/score.py b/aocp/score.py index 6016f98..09f2232 100644 --- a/aocp/score.py +++ b/aocp/score.py @@ -39,5 +39,6 @@ def get_datetime_info(timestamp): return day,hour def get_first(day): + pass # query all "CORRECT"s of <DAY> and return the SUBMISSION ID first one diff --git a/aocp/verifier.py b/aocp/verifier.py index 680a7d2..35ef56e 100644 --- a/aocp/verifier.py +++ b/aocp/verifier.py @@ -11,7 +11,15 @@ from typing import cast # p consists of: id, timestamp, answer, status, def verify(): - db = database.get_db() + """ + fetches all currently pending submissions from the database and checks + if theyre correct. Checks if a solution already exists in the solution db + and uses it; makes a request to the aoc website otherwise. + if the submitted answer is correct the status of the submission in the db + gets updated accordingly. + if a ratelimit is returned this program will sleep for the remaining time. + """ + db = database.get_db() # dataframe pending = fetch_all_pending() # returns a dataframe for index, p in pending.iterrows(): (uid,day,val) = cast(tuple,index) @@ -31,26 +39,17 @@ def verify(): db.update_submission_status(userid=uid,day=day,value=val,status="INCORRECT") elif status == aoc.Status.RATE_LIMIT: time.sleep(timeout) + elif status == aoc.Status.NOT_LOGGED_IN: + current_app.logger.error("no/invalid aoc session cookie.") + elif status == aoc.Status.COMPLETED: + current_app.logger.warning("request made although puzzle already solved. not good.") + elif status == aoc.Status.UNKNOWN: + current_app.logger.warning("unknown status returned.") - -def update_status(submission_id,status): - pass - # changes the "STATUS" value of the submission by <SUBMISSION_ID> to <STATUS> -def get_day_solution(day): - pass - # tries to get solution for day <DAY> from solution database, returns None if there is none def fetch_all_pending(): - return database.get_db().fetch_submissions(status="PENDING") - # gets all database entries with the status "pending" as a list of dictionaries -def set_day_solution(day,solution): - pass - # changes the "SOLUTION" value of <DAY> to <SOLUTION> in the solution database - - - -#submit_answer(2022,1,1,"30") - + return database.get_db().fetch_submissions(status="PENDING").sample(frac=1) + # gets all database entries with the status "pending" as a dataframe @click.command('test-verifier') def test_verifier_command(): -- GitLab From 42a4d1f80498eda66f251b33937c812ea8898485 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 20:58:09 +0100 Subject: [PATCH 24/28] using config now to determine year --- aocp/aoc.py | 2 +- aocp/verifier.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aocp/aoc.py b/aocp/aoc.py index a9dff99..5ab961c 100755 --- a/aocp/aoc.py +++ b/aocp/aoc.py @@ -9,7 +9,7 @@ import re import click import requests -current_year = date.today().year - 1 +current_year = current_app.config["YEAR"] def get_timeout(resp:str)->int: """ diff --git a/aocp/verifier.py b/aocp/verifier.py index 35ef56e..a9f314f 100644 --- a/aocp/verifier.py +++ b/aocp/verifier.py @@ -23,7 +23,7 @@ def verify(): pending = fetch_all_pending() # returns a dataframe for index, p in pending.iterrows(): (uid,day,val) = cast(tuple,index) - year = 2022 + year = current_app.config["YEAR"] solution = db.fetch_day_solution(day) # is equal to todays solution, if there is one. Is None otherwise. if solution: if solution == val: -- GitLab From 237627cb4203a920ab161e40b139a712d5530f83 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 20:58:52 +0100 Subject: [PATCH 25/28] comment --- aocp/aoc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aocp/aoc.py b/aocp/aoc.py index 5ab961c..6689b52 100755 --- a/aocp/aoc.py +++ b/aocp/aoc.py @@ -64,7 +64,7 @@ def submit_answer(year:int, day:int, level:int, answer:str)->tuple[Status,int|No return Status.FAIL, None elif 'You gave an answer too recently' in response: timeout = get_timeout(response) - click.echo("RATE LIMIT: "+str(timeout)) + #click.echo("RATE LIMIT: "+str(timeout)) return Status.RATE_LIMIT, timeout -- GitLab From 87b682fda96a4457ceb94a36f47cc453d8de82ca Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 21:04:17 +0100 Subject: [PATCH 26/28] comments added --- aocp/score.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/aocp/score.py b/aocp/score.py index 09f2232..8fdf747 100644 --- a/aocp/score.py +++ b/aocp/score.py @@ -1,7 +1,10 @@ from datetime import datetime from zoneinfo import ZoneInfo -def calculate_points(day, timedelta, is_first): +# timedelta is hour after conversion! +# return 0 points if not current day + +def calculate_points(day:int, timedelta:int, is_first:bool)->int: """ Calculates the points based on day, time and if the solver was first. @@ -28,17 +31,18 @@ def calculate_points(day, timedelta, is_first): score = base_points + bonus_time_points + bonus_first return score -def is_first_solve(sub_id,day): +def is_first_solve(sub_id:tuple[int,int,int],day:int): + # incomplete function first = get_first(day) return sub_id = first -def get_datetime_info(timestamp): +def get_datetime_info(timestamp:datetime): conv_time = timestamp.astimezone(ZoneInfo("EST")) # convert to EST time day = conv_time.day hour = conv_time.hour return day,hour def get_first(day): - + # missing function pass # query all "CORRECT"s of <DAY> and return the SUBMISSION ID first one -- GitLab From 895870298d3fc22b335aa4a5743b207ce1a787b3 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 21:04:56 +0100 Subject: [PATCH 27/28] oopsie --- aocp/score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aocp/score.py b/aocp/score.py index 8fdf747..63b55b1 100644 --- a/aocp/score.py +++ b/aocp/score.py @@ -34,7 +34,7 @@ def calculate_points(day:int, timedelta:int, is_first:bool)->int: def is_first_solve(sub_id:tuple[int,int,int],day:int): # incomplete function first = get_first(day) - return sub_id = first + return sub_id == first def get_datetime_info(timestamp:datetime): conv_time = timestamp.astimezone(ZoneInfo("EST")) # convert to EST time -- GitLab From 5d782925a83ced94a777b95482e80291d4f39d26 Mon Sep 17 00:00:00 2001 From: selina <selina.baehr@stud.uni-goettingen.de> Date: Sat, 11 Nov 2023 21:08:17 +0100 Subject: [PATCH 28/28] vscodium happy now --- aocp/verifier.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aocp/verifier.py b/aocp/verifier.py index a9f314f..ba4fcf3 100644 --- a/aocp/verifier.py +++ b/aocp/verifier.py @@ -38,7 +38,8 @@ def verify(): elif status == aoc.Status.FAIL: db.update_submission_status(userid=uid,day=day,value=val,status="INCORRECT") elif status == aoc.Status.RATE_LIMIT: - time.sleep(timeout) + if timeout: + time.sleep(timeout) elif status == aoc.Status.NOT_LOGGED_IN: current_app.logger.error("no/invalid aoc session cookie.") elif status == aoc.Status.COMPLETED: -- GitLab