Merge pull request #108 from stijndcl/compbio_commands

Add commands to track compbio leaderboards
pull/136/head
Stijn De Clercq 2022-03-11 14:17:12 +01:00 committed by GitHub
commit 635560dcd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 123 additions and 17 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ files/stats.json
files/lost.json files/lost.json
files/locked.json files/locked.json
files/ufora_notifications.json files/ufora_notifications.json
files/compbio_benchmarks_2.json
.idea/ .idea/
__pycache__ __pycache__
.env .env

View File

@ -1,3 +1,6 @@
import json
from discord import SlashCommandGroup
from discord.ext import commands from discord.ext import commands
from discord.commands import slash_command, ApplicationContext, Option, AutocompleteContext from discord.commands import slash_command, ApplicationContext, Option, AutocompleteContext
@ -5,6 +8,7 @@ from data import schedule
from data.courses import load_courses, find_course_from_name from data.courses import load_courses, find_course_from_name
from data.embeds.food import Menu from data.embeds.food import Menu
from data.embeds.deadlines import Deadlines from data.embeds.deadlines import Deadlines
from data.menus import leaderboards
from functions import les, config from functions import les, config
from functions.stringFormatters import capitalize from functions.stringFormatters import capitalize
from functions.timeFormatters import skip_weekends from functions.timeFormatters import skip_weekends
@ -83,6 +87,30 @@ class SchoolSlash(commands.Cog):
year = 2018 + int(config.get("year")) year = 2018 + int(config.get("year"))
return await ctx.respond(f"https://studiekiezer.ugent.be/studiefiche/nl/{course.code}/{year}") return await ctx.respond(f"https://studiekiezer.ugent.be/studiefiche/nl/{course.code}/{year}")
_compbio_group = SlashCommandGroup("compbio", "Commands voor compbio opdrachten")
@_compbio_group.command(name="leaderboard", description="Gesorteerd en ingevuld leaderboard")
async def _compbio_lb_slash(self, ctx: ApplicationContext):
lb = leaderboards.CompbioLeaderboard(ctx)
await lb.respond()
@_compbio_group.command(name="submit", description="Link een Dodona-submission aan jouw username")
async def _compbio_submit_slash(self, ctx: ApplicationContext,
submission: Option(int, description="Id van je Dodona indiening.", required=True)):
with open("files/compbio_benchmarks_2.json", "r") as fp:
file = json.load(fp)
submission = str(submission)
if submission in file:
return await ctx.respond("❌ Deze submission is al aan iemand gelinkt.", ephemeral=True)
with open("files/compbio_benchmarks_2.json", "w") as fp:
file[submission] = ctx.user.id
json.dump(file, fp)
return await ctx.respond(f"✅ Submission **{submission}** is aan jouw naam gelinkt.", ephemeral=True)
def setup(client: Didier): def setup(client: Didier):
client.add_cog(SchoolSlash(client)) client.add_cog(SchoolSlash(client))

View File

@ -1,3 +1,4 @@
import json
import math import math
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass, field from dataclasses import dataclass, field
@ -8,11 +9,12 @@ import requests
from discord import ApplicationContext from discord import ApplicationContext
from discord.ext.commands import Context from discord.ext.commands import Context
import settings
from data.menus.paginated import Paginated from data.menus.paginated import Paginated
from enums.numbers import Numbers from enums.numbers import Numbers
from functions import xp from functions import xp
from functions.database import currency, stats, poke, muttn from functions.database import currency, stats, poke, muttn
from functions.utils import get_display_name from functions.utils import get_display_name, get_mention
@dataclass @dataclass
@ -21,6 +23,7 @@ class Leaderboard(Paginated, ABC):
colour: discord.Colour = discord.Colour.blue() colour: discord.Colour = discord.Colour.blue()
fetch_names: bool = True fetch_names: bool = True
ignore_non_pos: bool = True ignore_non_pos: bool = True
reverse: bool = True
def __post_init__(self): def __post_init__(self):
self.data = self.process_data(self.get_data()) self.data = self.process_data(self.get_data())
@ -31,7 +34,7 @@ class Leaderboard(Paginated, ABC):
def process_data(self, entries: list[tuple]) -> Optional[list[tuple]]: def process_data(self, entries: list[tuple]) -> Optional[list[tuple]]:
data = [] data = []
for i, v in enumerate(sorted(entries, key=self.get_value, reverse=True)): for i, v in enumerate(sorted(entries, key=self.get_value, reverse=self.reverse)):
entry_data = self.get_value(v) entry_data = self.get_value(v)
# Leaderboard is empty # Leaderboard is empty
@ -90,13 +93,13 @@ class Leaderboard(Paginated, ABC):
return await ctx.reply(embed=embed, **kwargs) return await ctx.reply(embed=embed, **kwargs)
async def respond(self, **kwargs) -> discord.Message: async def respond(self, **kwargs) -> discord.Message:
if self.data is None: if self.data is None or not self.data:
return await self.empty_leaderboard(self.ctx, **kwargs) return await self.empty_leaderboard(self.ctx, **kwargs)
return await super().respond(**kwargs) return await super().respond(**kwargs)
async def send(self, **kwargs) -> discord.Message: async def send(self, **kwargs) -> discord.Message:
if self.data is None: if self.data is None or not self.data:
return await self.empty_leaderboard(self.ctx, **kwargs) return await self.empty_leaderboard(self.ctx, **kwargs)
return await super().send(**kwargs) return await super().send(**kwargs)
@ -117,6 +120,56 @@ class BitcoinLeaderboard(Leaderboard):
return "Er zijn nog geen personen met Bitcoins." return "Er zijn nog geen personen met Bitcoins."
@dataclass
class CompbioLeaderboard(Leaderboard):
colour: discord.Colour = field(default=discord.Colour.green())
title: str = field(default="Leaderboard Computationele Biologie #2")
reverse: bool = False
def get_submission_user(self, submission_id: str) -> str:
with open("files/compbio_benchmarks_2.json", "r") as fp:
file = json.load(fp)
if submission_id in file:
user_id = file[submission_id]
return get_mention(self.ctx, user_id)
return f"[# {submission_id}]"
def get_data(self) -> list[tuple]:
headers = {"Authorization": f"token {settings.UGENT_GH_TOKEN}"}
result = requests.get(f"https://github.ugent.be/raw/computationele-biologie/benchmarks-2022/main/reconstruction/J02459.1.50mers.md", headers=headers).text
# Remove table headers
result = result.split("\n")[2:]
data = []
for line in result:
try:
cells = line.split("|")
submission_id = cells[1].strip()
mean = float(cells[2].strip().split(" ")[0])
except IndexError:
# Other lines because of markdown formatting
continue
data.append((submission_id, mean, ))
return data
def _should_highlight(self, data) -> bool:
# TODO maybe find a fix for this?
return False
def format_entry(self, index: int, data: tuple) -> str:
return f"{index + 1}: {self.get_submission_user(data[0])} ({self.format_entry_data(data)})"
def format_entry_data(self, data: tuple) -> str:
return f"{str(data[1])} ms"
def get_value(self, data: tuple):
return data[1]
@dataclass @dataclass
class CoronaLeaderboard(Leaderboard): class CoronaLeaderboard(Leaderboard):
colour: discord.Colour = field(default=discord.Colour.red()) colour: discord.Colour = field(default=discord.Colour.red())

View File

@ -45,7 +45,7 @@ def format_command_usage(ctx: Context) -> str:
def format_slash_command_usage(interaction: Interaction) -> str: def format_slash_command_usage(interaction: Interaction) -> str:
# Create a string with the options used # Create a string with the options used
options = " ".join(list(map( options = " ".join(list(map(
lambda o: f"{o['name']}: \"{o['value']}\"", lambda o: f"{o['name']}: \"{o['value']}\"" if "value" in o else o["name"],
interaction.data.get("options", []) interaction.data.get("options", [])
))) )))

View File

@ -4,34 +4,55 @@ import discord
from discord import ApplicationContext from discord import ApplicationContext
from discord.ext.commands import Context from discord.ext.commands import Context
from data import constants import settings
def get_display_name(ctx: Union[ApplicationContext, Context], user_id: int) -> str: def get_member_or_user(ctx: Union[ApplicationContext, Context], user_id: int) -> Optional[Union[discord.Member, discord.User]]:
"""Get a COC Member instance of a user if they are in the server,
otherwise the regular User instance
"""
author = ctx.author if isinstance(ctx, Context) else ctx.user author = ctx.author if isinstance(ctx, Context) else ctx.user
# Check if this is a DM, or the user is not in the guild # Check if this is a DM, or the user is not in the guild
if ctx.guild is None or ctx.guild.get_member(user_id) is None: if ctx.guild is None or ctx.guild.get_member(user_id) is None:
# User is the author, no need to fetch their name # User is the author, no need to fetch their name
if user_id == author.id: if user_id == author.id:
return author.display_name return author
# Get member instance from CoC # Get member instance from CoC
COC = ctx.bot.get_guild(int(constants.DeZandbak)) COC = ctx.bot.get_guild(settings.COC_ID)
member = COC.get_member(user_id) member = COC.get_member(user_id)
if member is not None: if member is not None:
return member.display_name return member
# Try to fetch the user # Try to fetch the user
user = ctx.bot.get_user(user_id) return ctx.bot.get_user(user_id)
if user is not None:
return user.name
# User couldn't be found # Guild exists, use that instead
return ctx.guild.get_member(user_id)
def get_display_name(ctx: Union[ApplicationContext, Context], user_id: int) -> str:
member = get_member_or_user(ctx, user_id)
# Nothing found
if member is None:
return f"[? | {user_id}]" return f"[? | {user_id}]"
mem = ctx.guild.get_member(user_id) if isinstance(member, discord.Member):
return mem.display_name return member.display_name
return member.name
def get_mention(ctx: Union[ApplicationContext, Context], user_id: int) -> str:
member = get_member_or_user(ctx, user_id)
# Nothing found
if member is None:
return f"[? | {user_id}]"
return member.mention
async def reply_to_reference(ctx: Context, content: Optional[str] = None, embed: Optional[discord.Embed] = None, always_mention=False): async def reply_to_reference(ctx: Context, content: Optional[str] = None, embed: Optional[discord.Embed] = None, always_mention=False):

View File

@ -1,5 +1,5 @@
# Beta version of Discord.py fork # Beta version of Discord.py fork
py-cord==2.0.0b1 py-cord==2.0.0b5
python-dotenv==0.14.0 python-dotenv==0.14.0
beautifulsoup4==4.9.1 beautifulsoup4==4.9.1

View File

@ -41,3 +41,6 @@ COC_ID: int = int(os.getenv("COC_ID", "626699611192688641"))
# Ex: 123,456,789 # Ex: 123,456,789
_guilds = os.getenv("SLASHTESTGUILDS", "").replace(" ", "") _guilds = os.getenv("SLASHTESTGUILDS", "").replace(" ", "")
SLASH_TEST_GUILDS: List[int] = list(map(lambda x: int(x), _guilds.split(","))) if _guilds else None SLASH_TEST_GUILDS: List[int] = list(map(lambda x: int(x), _guilds.split(","))) if _guilds else None
# GitHub token (UGent)
UGENT_GH_TOKEN = os.getenv("UGENTGHTOKEN", "")