Skip to content

Add Contributions Command #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dbqueries.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ def open_con(running_server=True):
return False


def query(_query, con):
def query(_query, con, exec_args=None):
back = True
if con is None:
phone_api.bot_crashed("Database Crash")
return {}
cur = con.cursor()
cur.execute(_query)
cur.execute(_query, exec_args)
if _query[:6] == "SELECT":
back = cur.fetchall()
field_names = [i[0] for i in cur.description]
Expand Down
78 changes: 78 additions & 0 deletions pe_api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import datetime

import requests
import dbqueries
from bs4 import BeautifulSoup
Expand Down Expand Up @@ -79,6 +81,8 @@ def keep_session_alive():
l = len(currently_solved)
for i in range(1, l+1):
if previously_solved[i - 1] != currently_solved[i - 1]:
if is_contributor(db_member, i):
add_contribution(db_member, i)
solved.append(i)

all_solved.append([format_member[0], solved, db_member['discord_id'], format_member[4]])
Expand Down Expand Up @@ -253,6 +257,80 @@ def get_all_members_who_solved(problem):
return solvers


# Our heuristic for checking if a user is a contributor to a problem is:
# Get the time the user solved a problem from their recent solves history,
# is this less than the most recent solver in the top 100 and are they
# absent from the top solvers list?
def is_contributor(username, problem):
fastest_solvers = get_fastest_solvers(problem)
in_solvers = username in (name for name, _ in fastest_solvers)
if in_solvers: return False
# Check if user has solved faster than last solver in top 100
solved_at = get_user_recent_problems(username).get(problem, datetime.datetime.max)
return solved_at < fastest_solvers[-1][1]


# Unfortunately, the history page for friends only shows recent problems solved
def get_user_recent_problems(username):
"""Return a dictionary of recent problems mapped to datetimes for a given user."""
url = NOT_MINIMAL_BASE_URL.format(f"progress={username};show=history")
data = req_to_project_euler(url, True)
return get_user_recent_problems_from_data(data)


def get_user_recent_problems_from_data(data):
soup = BeautifulSoup(data, "html.parser")
problems = soup.find_all("tr")
problem_dict = {}
for problem in problems:
n, _, date_info = problem.children
fst_child = next(date_info.children)
datetime_str = fst_child if isinstance(fst_child, str) \
else next(fst_child.children)
# 15 Jul 22 (07:34.59)
dt = datetime.datetime.strptime(datetime_str, "%d %b %y (%H:%M.%S)")
problem_dict[int(n.text.strip())] = dt
return problem_dict


def get_fastest_solvers(problem):
url = NOT_MINIMAL_BASE_URL.format(f"fastest={problem}")
data = req_to_project_euler(url, True)
return get_fastest_solvers_from_data(problem, data)


def get_fastest_solvers_from_data(problem, data):
unix_ts = int(problem_def(problem)[2])
dt = datetime.datetime.utcfromtimestamp(unix_ts)
soup = BeautifulSoup(data, "html.parser")
div = soup.find(id="statistics_fastest_solvers_page")

_, *solves = div.find("table").find_all("tr")
solver_info = []
for solve in solves:
columns = solve.find_all("td")
position, username, loc, lang, time = (td.text for td in columns)
if not isinstance(next(columns[1].children), str): # it's an alias
username = next(columns[1].children).attrs["title"]
# seconds, minutes, hours, days, weeks, years
times = [1, 60, 60*60, 60*60*24, 60*60*24*7, 60*60*24*7*52]
total = 0
for raw_time, multiplier in zip(reversed(time.split(", ")), times):
time, *_ = raw_time.partition(" ")
total += int(time)*multiplier
solver_info.append((username, dt + datetime.timedelta(seconds=total)))
return solver_info


def add_contribution(username, problem, conn):
stmt = (
"INSERT INTO problem_contributions (username, problem)"
"VALUES (%s, %s)"
"ON DUPLICATE KEY UPDATE username=username"
)
dbqueries.query(stmt, conn, (username, problem))


# return a binary string like 111110001100... with every 1 marking a solve
def problems_of_member(username):

Expand Down
27 changes: 27 additions & 0 deletions pe_discord_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,33 @@ async def command_kudos(ctx, member: discord.User):
return await ctx.respond("There was some change for user `{0}`! You gained {1} kudos on the following posts (for a total of {2} kudos):".format(username, change, kudos) + k)


@bot.slash_command(name="contributions", description="Display problem contributions")
@option("member", description="The member you want contributions to be displayed for", default=None)
@option("problem", description="Retroactively check if the member is a contributor for this problem", default=None)
async def command_contributions(ctx, member: discord.User, problem: int):

discord_id = ctx.author.id if member is None else member.id

connection = dbqueries.open_con()
if not pe_api.is_discord_linked(discord_id, connection):
dbqueries.close_con(connection)
return await ctx.respond("This user does not have a project euler account linked! Please link with &link first")

if problem:
username = dbqueries.query("SELECT username FROM members WHERE discord_id='{0}';".format(discord_id), connection)[0]["username"]
if pe_api.is_contributor(username, problem):
pe_api.add_contribution(username, problem, connection)

query = (
f"SELECT pc.problem FROM problem_contributions pc WHERE m.discord_id='{discord_id}'"
"INNER JOIN members m ON m.username = pc.username;"
)
contributions = dbqueries.query(query.format(discord_id), connection)
dbqueries.close_con(connection)

return await ctx.respond(f"Know contributions for {discord_id}: {contributions}")


@bot.slash_command(name="easiest", description="Find the easiest problems you haven't solved yet")
@option("member", description="The member you want you want to see the next possible solves", default=None)
@option("method", description="The method used", choices=["By number of solves", "By order of publication", "By ratio of solves per time unit"], default="per_solve")
Expand Down