diff options
author | suhas <hi@suhas.one> | 2023-11-16 23:00:29 -0600 |
---|---|---|
committer | suhas <hi@suhas.one> | 2023-11-16 23:00:29 -0600 |
commit | 2e38b5aefb08abdd34d5244d0998274e5f35ff3e (patch) | |
tree | a221637a7eaa9195c8ae5322d8c73c1ad70e90a2 | |
parent | e0ede3dddabf27cde5a1db35e499885ece51f542 (diff) | |
download | qbb-2e38b5aefb08abdd34d5244d0998274e5f35ff3e.tar.gz |
add stats tracking
27 files changed, 239 insertions, 17 deletions
diff --git a/.gitignore b/.gitignore index be47843..abc5844 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .DS_Store .env +data/* +data/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..30bab2a --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="DiscordProjectSettings"> + <option name="show" value="ASK" /> + <option name="description" value="" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..42069fa --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_20" project-jdk-name="Python 3.11" project-jdk-type="Python SDK"> + <output url="file://$PROJECT_DIR$/out" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0315bb0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/qbb.iml" filepath="$PROJECT_DIR$/.idea/qbb.iml" /> + </modules> + </component> +</project> \ No newline at end of file diff --git a/.idea/qbb.iml b/.idea/qbb.iml new file mode 100644 index 0000000..2cdb1e3 --- /dev/null +++ b/.idea/qbb.iml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <excludeFolder url="file://$MODULE_DIR$/venv" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..7d0de90 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Git" /> + <mapping directory="$PROJECT_DIR$/src/asqlite" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/__init__.py diff --git a/bot.py b/bot.py index 9240d32..3b75565 100644 --- a/bot.py +++ b/bot.py @@ -22,6 +22,7 @@ class QBBBot(Bot): async def setup_hook(self): await self.load_extension('cogs.tossup') await self.load_extension('cogs.solo') + await self.load_extension('cogs.stats') await self.load_extension('jishaku') diff --git a/cogs/__init__.py b/cogs/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cogs/__init__.py diff --git a/cogs/__pycache__/__init__.cpython-311.pyc b/cogs/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..17c16fd --- /dev/null +++ b/cogs/__pycache__/__init__.cpython-311.pyc Binary files differdiff --git a/cogs/__pycache__/solo.cpython-311.pyc b/cogs/__pycache__/solo.cpython-311.pyc index 6ede884..d6d2e68 100644 --- a/cogs/__pycache__/solo.cpython-311.pyc +++ b/cogs/__pycache__/solo.cpython-311.pyc Binary files differdiff --git a/cogs/__pycache__/stats.cpython-311.pyc b/cogs/__pycache__/stats.cpython-311.pyc new file mode 100644 index 0000000..dde7701 --- /dev/null +++ b/cogs/__pycache__/stats.cpython-311.pyc Binary files differdiff --git a/cogs/__pycache__/tossup.cpython-311.pyc b/cogs/__pycache__/tossup.cpython-311.pyc index 08b770b..419e663 100644 --- a/cogs/__pycache__/tossup.cpython-311.pyc +++ b/cogs/__pycache__/tossup.cpython-311.pyc Binary files differdiff --git a/cogs/solo.py b/cogs/solo.py index aab0b77..1652960 100644 --- a/cogs/solo.py +++ b/cogs/solo.py @@ -17,7 +17,7 @@ class Solo(GroupCog, name="solo"): @command(description="Do a tossup in solo mode (only you can control the tossup)") async def tossup(self, ctx: Interaction, category: Optional[question_category] = None): c = AsyncClient() - params = {'difficulties': [2,3,4,5]} + params: dict = {'difficulties': [2,3,4,5]} if category is not None: params['categories'] = category diff --git a/cogs/stats.py b/cogs/stats.py new file mode 100644 index 0000000..03031ec --- /dev/null +++ b/cogs/stats.py @@ -0,0 +1,36 @@ +from discord.ext.commands import Cog, Bot +from discord.app_commands import command +from discord import Interaction, Embed, Color +from prisma import Prisma +from prisma.models import User, CategoryBreakdown +from common.types import category_field_translations + +reverse_categories = {v: k for k, v in category_field_translations.items()} + +class Stats(Cog): + def __init__(self, bot: Bot) -> None: + self.bot = bot + super().__init__() + + @command(description="Get your statistics for qbb") + async def stats(self, ctx: Interaction): + db = Prisma(auto_register=True) + await db.connect() + stats = await User.prisma().find_first(where={'id': ctx.user.id}) + cb = await CategoryBreakdown.prisma().find_first(where={'userId': ctx.user.id}) + if stats is None or cb is None: + embed = Embed(title="No Stats!", description="You have no stats! Trying using the bot and then running this command", color=Color.red()) + return await ctx.response.send_message(embed=embed) + embed = Embed(title="Your Stats!", description=f"""**Number of correct tossups:** {stats.questions_correct} + **Number of incorrect tossups:** {stats.questions_incorrect} + """) + print(stats.category_breakdown.__str__()) + for cat in category_field_translations.values(): + corr = getattr(cb, f'{cat}_correct') + incorr = getattr(cb, f'{cat}_incorrect') + embed.add_field(name=reverse_categories[cat], value=f'{corr+incorr} heard ({corr} correct)') + await ctx.response.send_message(embed=embed) + await db.disconnect() + +async def setup(bot: Bot) -> None: + await bot.add_cog(Stats(bot)) diff --git a/cogs/tossup.py b/cogs/tossup.py index ae01519..3b62724 100644 --- a/cogs/tossup.py +++ b/cogs/tossup.py @@ -1,12 +1,10 @@ from discord.ext.commands import Cog, Bot from discord.app_commands import command, describe -from discord import Interaction, Embed, ButtonStyle -from discord.ui import View, Button, Modal, button, TextInput +from discord import Interaction, Embed from nltk import sent_tokenize from httpx import AsyncClient -from typing import Literal, Optional +from typing import Optional from common.types import question_category -from components.AnswerModal import Answer from components.TossupButtons import TossupButtons class Tossup(Cog): @@ -18,7 +16,7 @@ class Tossup(Cog): async def tossup(self, ctx: Interaction, category: Optional[question_category] = None): c = AsyncClient() - params = {"difficulties": [2, 3, 4, 5]} + params: dict = {"difficulties": [2, 3, 4, 5]} if category is not None: params["categories"] = category @@ -30,9 +28,10 @@ class Tossup(Cog): view: TossupButtons = TossupButtons(tossup) embed = Embed(title="Random Tossup", description=tossup["sentences"][0]) - embed.set_author( - name=f"{tossup['set']['name']} Packet {tossup['packetNumber']} Question {tossup['questionNumber']} (Category: {tossup['category']})" - ) + # embed.set_author( + # name=f"{tossup['set']['name']} Packet {tossup['packetNumber']} Question {tossup['questionNumber']} (Category: {tossup['category']})" + # ) + embed.set_author(name=tossup['answer']) embed.set_footer(text="Questions obtained from qbreader.org") await ctx.response.send_message(embed=embed, view=view) await c.aclose() diff --git a/common/__pycache__/types.cpython-311.pyc b/common/__pycache__/types.cpython-311.pyc index 3d7bbd1..131488c 100644 --- a/common/__pycache__/types.cpython-311.pyc +++ b/common/__pycache__/types.cpython-311.pyc Binary files differdiff --git a/common/types.py b/common/types.py index e9716b1..b28b457 100644 --- a/common/types.py +++ b/common/types.py @@ -14,3 +14,18 @@ question_category = Literal[ "Other Academic", "Trash", ] + +category_field_translations = { + 'Literature': 'literature', + 'History': 'history', + 'Science': 'science', + 'Fine Arts': 'fine_arts', + 'Religion': 'religion', + 'Mythology': 'mythology', + 'Philosophy': 'philosophy', + 'Social Science': 'social_science', + 'Current Events': 'current_events', + 'Geography': 'geography', + 'Other Academic': 'other_academic', + 'Trash': 'trash' +} diff --git a/components/AnswerModal.py b/components/AnswerModal.py index 9bd5f17..9d7b1cb 100644 --- a/components/AnswerModal.py +++ b/components/AnswerModal.py @@ -1,18 +1,28 @@ -from discord.ui import Modal, TextInput, View +from __future__ import annotations +from discord.ui import Modal, TextInput from httpx import AsyncClient from discord import Interaction, Color +from prisma import Prisma +from prisma.models import User +from typing import TYPE_CHECKING, Literal +if TYPE_CHECKING: + from .TossupButtons import TossupButtons, SoloTossupButtons +from discord import Button +from common.types import category_field_translations class Answer(Modal, title="Submit Answer"): - def __init__(self, correct_answer: str, view: View) -> None: + def __init__(self, correct_answer: str, view: TossupButtons) -> None: self.correct_answer = correct_answer self.view = view + self.stop_working = False super().__init__() answer = TextInput(label="Answer", placeholder="Your answer here") async def on_submit(self, interaction: Interaction) -> None: c = AsyncClient() + db = Prisma(auto_register=True) answer_check_resp = await c.get( "https://qbreader.org/api/check-answer", params={ @@ -21,8 +31,13 @@ class Answer(Modal, title="Submit Answer"): }, ) answer_check_data = answer_check_resp.json() + await db.connect() if answer_check_data["directive"] == "accept": + if self.stop_working: + return await interaction.response.send_message('Someone has already answered this correctly!', ephemeral=True) + if interaction.message is None: + return e = interaction.message.embeds[0] e.color = Color.green() e.title = '[CORRECT!] Random Tossup' @@ -32,17 +47,40 @@ class Answer(Modal, title="Submit Answer"): e.add_field(name='Answered by', value=interaction.user.mention) items = self.view.children for item in items: - item.disabled = True + if isinstance(item, Button): + item.disabled = True + self.stop_working = True await interaction.response.edit_message(embed=e, view=self.view) + await User.prisma().upsert(where={'id': interaction.user.id}, data={ + 'create': {'questions_correct': 1, 'id': interaction.user.id, 'questions_incorrect': 0}, + 'update': {'questions_correct': {'increment': 1}} + }) + category = category_field_translations[self.view.tossup['category']] + await db.execute_raw(f""" + INSERT INTO CategoryBreakdown(userId, {category}_correct) VALUES (?, 0) + ON CONFLICT(userId) DO UPDATE SET {category}_correct={category}_correct+1 + """, interaction.user.id) elif answer_check_data['directive'] == 'prompt': await interaction.response.send_message("Prompt! Try answering the question again", ephemeral=True) else: await interaction.response.send_message(f"Incorrect! You've been locked out from the question. The correct answer was {self.view.tossup['answer']}", ephemeral=True) self.view.already_answered.append(interaction.user.id) + await User.prisma().upsert( + where={'id': interaction.user.id}, + data={ + 'create': {'questions_correct': 0, 'id': interaction.user.id, 'questions_incorrect': 1}, + 'update': {'questions_incorrect': {'increment': 1}} + } + ) + category = category_field_translations[self.view.tossup['category']] + await db.execute_raw(f""" + INSERT INTO CategoryBreakdown(userId, {category}_incorrect) VALUES (?, 0) + ON CONFLICT(userId) DO UPDATE SET {category}_incorrect={category}_incorrect+1 + """, interaction.user.id) await c.aclose() class SoloAnswer(Modal, title="Submit Answer"): - def __init__(self, correct_answer: str, view: View) -> None: + def __init__(self, correct_answer: str, view: SoloTossupButtons) -> None: self.correct_answer = correct_answer self.view = view super().__init__() @@ -61,6 +99,8 @@ class SoloAnswer(Modal, title="Submit Answer"): answer_check_data = answer_check_resp.json() if answer_check_data["directive"] == "accept": + if interaction.message is None: + return e = interaction.message.embeds[0] e.color = Color.green() e.title = '[CORRECT!] Random Tossup' @@ -69,9 +109,12 @@ class SoloAnswer(Modal, title="Submit Answer"): e.add_field(name='Official answer', value=self.view.tossup['answer']) items = self.view.children for item in items: - item.disabled = True + if isinstance(item, Button): + item.disabled = True await interaction.response.edit_message(embed=e, view=self.view) else: + if interaction.message is None: + return e = interaction.message.embeds[0] e.color = Color.red() e.title = '[INCORRECT] Random Tossup' @@ -80,6 +123,7 @@ class SoloAnswer(Modal, title="Submit Answer"): e.add_field(name='Your answer', value=self.answer.value) items = self.view.children for item in items: - item.disabled = True + if isinstance(item, Button): + item.disabled = True await interaction.response.edit_message(embed=e, view=self.view) await c.aclose() diff --git a/components/TossupButtons.py b/components/TossupButtons.py index 7238cac..73edd21 100644 --- a/components/TossupButtons.py +++ b/components/TossupButtons.py @@ -29,6 +29,8 @@ class TossupButtons(View): return await interaction.response.send_message( "you've already answered!", ephemeral=True ) + if interaction.message is None: + return interaction.response.send_message("Couldn't find the message!", ephemeral=True) e = interaction.message.embeds[0] self.i += 1 if self.i == len(self.tossup["sentences"]): @@ -46,13 +48,16 @@ class TossupButtons(View): self.final_answer_votes += 1 button.label = f"vote to reveal answer ({self.final_answer_votes}/3)" if self.final_answer_votes >= 3: + if interaction.message is None: + return interaction.response.send_message("Couldn't find the message!", ephemeral=True) e = interaction.message.embeds[0] e.title = '[SKIPPED] Random Tossup' e.color = Color.orange() e.description = self.tossup["question"] e.add_field(name="Answer", value=self.tossup["answer"]) for item in self.children: - item.disabled = True + if isinstance(item, Button): + item.disabled = True return await interaction.response.edit_message(embed=e, view=self) await interaction.response.edit_message(view=self) await interaction.followup.send("you've voted!", ephemeral=True) @@ -76,6 +81,8 @@ class SoloTossupButtons(View): return await interaction.response.send_message( "not your tossup!", ephemeral=True ) + if interaction.message is None: + return interaction.response.send_message("Couldn't find the message!", ephemeral=True) e = interaction.message.embeds[0] self.i += 1 if self.i == len(self.tossup["sentences"]) - 1: @@ -89,11 +96,14 @@ class SoloTossupButtons(View): return await interaction.response.send_message( "not your tossup!", ephemeral=True ) + if interaction.message is None: + return await interaction.response.send_message('Error!') e = interaction.message.embeds[0] e.title = f'[SKIPPED] Random Tossup' e.color = Color.orange() e.description = self.tossup["question"] e.add_field(name="Answer", value=self.tossup["answer"]) for item in self.children: - item.disabled = True + if isinstance(item, Button): + item.disabled = True return await interaction.response.edit_message(embed=e, view=self) diff --git a/components/__init__.py b/components/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/components/__init__.py diff --git a/components/__pycache__/AnswerModal.cpython-311.pyc b/components/__pycache__/AnswerModal.cpython-311.pyc index 0ca3cb1..8b217c5 100644 --- a/components/__pycache__/AnswerModal.cpython-311.pyc +++ b/components/__pycache__/AnswerModal.cpython-311.pyc Binary files differdiff --git a/components/__pycache__/TossupButtons.cpython-311.pyc b/components/__pycache__/TossupButtons.cpython-311.pyc index fc443d0..9281a92 100644 --- a/components/__pycache__/TossupButtons.cpython-311.pyc +++ b/components/__pycache__/TossupButtons.cpython-311.pyc Binary files differdiff --git a/components/__pycache__/__init__.cpython-311.pyc b/components/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..b9cce83 --- /dev/null +++ b/components/__pycache__/__init__.cpython-311.pyc Binary files differdiff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..576e5ad --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,61 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-py" + recursive_type_depth = 5 +} + +datasource db { + provider = "sqlite" + url = "file:../data/database.db" +} + +model User { + id BigInt @id + questions_correct Int @default(0) + questions_incorrect Int @default(0) + category_breakdown CategoryBreakdown? +} + +model CategoryBreakdown { + userId BigInt @unique + user User @relation(fields: [userId], references: [id]) + id Int @id @default(autoincrement()) + + literature_correct Int @default(0) + literature_incorrect Int @default(0) + + history_correct Int @default(0) + history_incorrect Int @default(0) + + science_correct Int @default(0) + science_incorrect Int @default(0) + + fine_arts_correct Int @default(0) + fine_arts_incorrect Int @default(0) + + religion_correct Int @default(0) + religion_incorrect Int @default(0) + + mythology_correct Int @default(0) + mythology_incorrect Int @default(0) + + philosophy_correct Int @default(0) + philosophy_incorrect Int @default(0) + + social_science_correct Int @default(0) + social_science_incorrect Int @default(0) + + current_events_correct Int @default(0) + current_events_incorrect Int @default(0) + + geography_correct Int @default(0) + geography_incorrect Int @default(0) + + other_academic_correct Int @default(0) + other_academic_incorrect Int @default(0) + + trash_correct Int @default(0) + trash_incorrect Int @default(0) +} diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..4fd12d6 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,7 @@ +{ + "include": [ + "cogs", + "common", + "components" + ] +} |