diff --git a/pure-pulsars/.dockerignore b/pure-pulsars/.dockerignore
new file mode 100644
index 0000000..05cb0ae
--- /dev/null
+++ b/pure-pulsars/.dockerignore
@@ -0,0 +1,16 @@
+__pycache__
+.github
+.ruff_cache
+tests
+docs
+.vscode
+.venv
+
+Pipfile
+poetry.lock
+code-jam-11.code-workspace
+config.env
+.gitignore
+.pre-commit-config.yaml
+pyproject.toml
+requirements-dev.txt
diff --git a/pure-pulsars/.github/workflows/lint.yaml b/pure-pulsars/.github/workflows/lint.yaml
new file mode 100644
index 0000000..7f67e80
--- /dev/null
+++ b/pure-pulsars/.github/workflows/lint.yaml
@@ -0,0 +1,35 @@
+# GitHub Action workflow enforcing our code style.
+
+name: Lint
+
+# Trigger the workflow on both push (to the main repository, on the main branch)
+# and pull requests (against the main repository, but from any repo, from any branch).
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit.
+# It is useful for pull requests coming from the main repository since both triggers will match.
+concurrency: lint-${{ github.sha }}
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ env:
+ # The Python version your project uses. Feel free to change this if required.
+ PYTHON_VERSION: "3.12"
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Python ${{ env.PYTHON_VERSION }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.PYTHON_VERSION }}
+
+ - name: Run pre-commit hooks
+ uses: pre-commit/action@v3.0.1
diff --git a/pure-pulsars/.gitignore b/pure-pulsars/.gitignore
new file mode 100644
index 0000000..25e61c4
--- /dev/null
+++ b/pure-pulsars/.gitignore
@@ -0,0 +1,115 @@
+# Files generated by the interpreter
+__pycache__/
+*.py[cod]
+
+# Environment specific
+.venv
+venv
+.env
+env
+
+# Unittest reports
+.coverage*
+
+# Logs
+*.log
+
+# PyEnv version selector
+.python-version
+
+#api chache
+apicache
+
+# Built objects
+*.so
+dist/
+build/
+
+# IDEs
+# PyCharm
+.idea/
+# VSCode
+.vscode/
+# MacOS
+.DS_Store
+test_wiki_rand.py
+*firebase-adminsdk*.json
+code-jam-11.code-workspace
+src/cmds/shutdown.py
+
+# Secrets!
+secrets/
+src/test.py
+
+docker.env
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+firebase-debug.log*
+firebase-debug.*.log*
+
+# Firebase cache
+.firebase/
+
+# Firebase config
+
+# Uncomment this if you'd like others to create their own Firebase project.
+# For a team working on the same Firebase project(s), it is recommended to leave
+# it commented so all members can deploy to the same project(s) in .firebaserc.
+# .firebaserc
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# Other
+*throttle.ctrl*
+docker.env
diff --git a/pure-pulsars/.pre-commit-config.yaml b/pure-pulsars/.pre-commit-config.yaml
new file mode 100644
index 0000000..cf63ef6
--- /dev/null
+++ b/pure-pulsars/.pre-commit-config.yaml
@@ -0,0 +1,17 @@
+# Pre-commit configuration.
+# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing
+
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.6.0
+ hooks:
+ - id: check-toml
+ - id: check-yaml
+ - id: trailing-whitespace
+ args: [--markdown-linebreak-ext=md]
+
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.5.0
+ hooks:
+ - id: ruff
+ - id: ruff-format
diff --git a/pure-pulsars/Dockerfile b/pure-pulsars/Dockerfile
new file mode 100644
index 0000000..b60a8b0
--- /dev/null
+++ b/pure-pulsars/Dockerfile
@@ -0,0 +1,13 @@
+FROM python:3.12.4-slim
+
+WORKDIR /app
+
+COPY requirements.txt requirements.txt
+
+RUN pip install -r requirements.txt
+
+COPY . .
+
+RUN "ls"
+
+CMD ["python", "src/main.py"]
diff --git a/pure-pulsars/LICENSE.txt b/pure-pulsars/LICENSE.txt
new file mode 100644
index 0000000..65f77a7
--- /dev/null
+++ b/pure-pulsars/LICENSE.txt
@@ -0,0 +1,7 @@
+Copyright 2021 theheretic_ WONG-TONG48 teald spenpal lotus.css Xanthian
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/pure-pulsars/Pipfile b/pure-pulsars/Pipfile
new file mode 100644
index 0000000..fcf281a
--- /dev/null
+++ b/pure-pulsars/Pipfile
@@ -0,0 +1,22 @@
+# Sample Pipfile.
+
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+humanize = "~4.10.0"
+fake-useragent = "~1.5.1"
+aiohttp = "~3.9.5"
+pywikibot = "~9.2.1"
+"discord.py" = "~2.4.0"
+firebase-admin = "~6.5.0"
+python-dotenv = "~1.0.1"
+
+[dev-packages]
+ruff = "~=0.5.0"
+pre-commit = "~=3.7.1"
+
+[requires]
+python_version = "3.12"
diff --git a/pure-pulsars/README.md b/pure-pulsars/README.md
new file mode 100644
index 0000000..a07e141
--- /dev/null
+++ b/pure-pulsars/README.md
@@ -0,0 +1,321 @@
+
+
+
+
+
+[Wiki-Wabbit](https://wikiwabbit.com/) is a discord bot that brings the fun of bingeing Wikipedia articles to your dicord server!
+
+# Directory
+
++ [Features](#Features)
++ [Technical Points](#Technical-Points)
++ [Install Guide](#Install-Guide)
+ + [Discord Token](#Discord-Token)
+ + [Firebase Service Account](#Firebase-Service-Account)
+ + [Gemini API Key](#Gemini-API-Key)
+ + [API Ninjas API Key](#API-Ninjas-Key)
+ + [Env Settings](#Env-Settings)
+ + [Docker](#Docker)
+
++ [Shout Outs](#Shout-Outs)
++ [Connection to the theme (Information Overload!)](#connection-to-the-theme-information-overload)
++ [Team Contributions](#Breakdown-of-Team-Contributions)
++ [Tour de Features](#Tour-de-Features)
+ + [Wiki Guesser](#wiki-guesser)
+ + [Wiki Random](#wiki-random)
+ + [Wiki Animal](#wiki-animal)
+ + [Rabbit Hole](#rabbit-hole)
+ + [Leaderboard and User Info](#Leaderboard-and-User-Info)
+ + [Help and Sync](#Help-and-Sync)
+
+# Features
+
+| Command | Description |
+|-----------------|----------------------------------------------------------------------------|
+| [`/wiki-guesser`](#wiki-guesser) | Starts a game of wiki-guesser! Try and find what wikipedia article you're in. |
+| [`/wiki-random`](#wiki-random) | Get a random wikipedia article. |
+| [`/wiki-animal`](#wiki-animal) | Starts a game of wiki-animal! Try and guess the animal's mass! |
+| [`/wiki-search`](#wiki-search) | Get a Wikipedia article that you searched for. |
+| [`/rabbit-hole`](#rabbit-hole) | Dive into wikipedia with AI-guided random exploration! |
+| [`/leaderboard`](#Leaderboard-and-User-Info) | Returns your guild's leaderboard. |
+| [`/user-info`](#Leaderboard-and-User-Info) | Returns your stats. |
+| [`/help`](#Help-and-Sync) | Display a message with much of the same info as this table has. |
+| [`/sync`](#Help-and-Sync) | Sync the command tree. |
+| `/reset-scores` | Reset scores of all users in this guild for this guild. |
+| `/never` | 🥚 Never use this command! |
+
+
+# Technical Points
+
+
+
+`Wiki-Wabbit` uses `pywikibot` and the Wikipedia REST API. Alongside that, it also makes use of:
+
++ Firebase
++ Google's Gemini API
++ Ninja API
+
+# Install Guide
+
+[github_install_link]: https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository
+
+First, [clone this repository locally][github_install_link].
+
+We require a few resources to get our bot up and running. For starters, we need
+to collect some API tokens used by the bot. You should save these in a safe
+place; once they're all collected, we'll be putting them into our
+[`config.env`](config.env) file.
+
+The first thing we need is a Discord API token.
+
+### Discord Token
+
+Goto the [Discord Application Page](https://discord.com/developers/applications) you should see this screen.
+
+
+After that go and create a discord application by clicking the `New Application` button
+
+![](docs/assets/gifs/app-setup.gif)
+
+Now go down to "Bot" on the left menu and click `Reset Token` that will be your
+discord token, put it somewhere safe for later.
+
+While here be sure to turn on the 3 switches below `Privileged Gateway Intents`
+ + Presence Intent
+ + Server Members Intent
+ + Message Content Intent
+
+You should also goto `Installation` in the sidebar menu and goto the Install Link, you can add your bot to your discord server with this, but I would wait till after the bot is running to use it.
+
+### Firebase Service Account
+
+Go to the [Firebase Console](https://console.firebase.google.com/) and click the get started with a Firebase project.
+Its name can be anything, so don't worry about that.
+After it's made, click on the build tab on the left hand of the screen and scroll down to `Realtime Database`, this will be our database for the bot.
+
+
+After this, we still need to tell the bot what firebase account we are using.
+Head to project settings, it should pop up if you click on the gear near
+`Project Overview`. From there navigate to `Service Accounts` and click
+`Generate a New Private Key`. This should download a `json` file that serves as
+your key for Firebase. Put that file in a safe place that you can get to easily
+(e.g., `secrets/firebase_api_key.json`, which is ignored in our `.gitignore`
+file).
+
+And that's all for Firebase.
+
+### Gemini API Key
+One of our Commands makes use of AI, and since we don't want to force the end user to run a complex AI model, we are using the Google Gemini free plan.
+
+Goto the [Google AI Studio](https://aistudio.google.com/app/apikey) and just click `Create API key`.
+Once you have your key, you can store that somewhere safe for later.
+Well that was easy.
+
+
+#### Warning
+
+The API key may need to be associated with a Google Cloud Project. You can use
+the Firebase project created in the previous step, which should be available
+from the "Search Google Cloud project" search bar with the same name you gave
+it.
+
+### API Ninjas Key
+Goto [API Ninjas](https://api-ninjas.com/) and create an account and log in. After you make it, [goto your profile](https://api-ninjas.com/profile).
+From here scroll down a bit and click `Show API Key`, copy it, and save it somewhere safe for later.
+
+Now that we have all of our keys and service accounts, it's time to configure the bot!
+
+### Env Settings.
+We have included a [config.env](config.env) to allow you to easily set up environment variables for the bot.
++ `TOKEN` is your [Discord API token](#discord-token)
++ `CERT_PATH` is your [Firebase Service Account file path](#firebase-service-account).
++ `GEMINI_API_KEY` is your [Google Gemini API key](#gemini-api-key).
++ `NINJA_API_KEY` is your [API Ninjas API key](#api-ninjas-key).
+
+
+> [!IMPORTANT]
+> ### Rename `config.env` to `.env`
+> After you set these, be sure rename `config.env` to just `.env` so the docker knows what to use. _You will get a `KeyError` if you do not do this step_!
+
+### Docker
+After you set up your env you can run
+```
+docker build --tag wikiwabbit .
+```
+and then after that finishes with no errors run
+```
+docker run wikiwabbit
+```
+and everything should be up and running. Return to discord, and check out your bot - however, you might want to kick and reinvite it to auto sync the command tree.
+
+# Shout Outs
+
+Thank you Python Discord for putting this on, I dont think anyone in our team has ever done something like this. And I will do this continuing forward. Really happy with the result.
+
+Enjoy the bot, a lot of work went into this. It was a passion for all of us apart of this team, we had a great time, we poured our all into our features, thank you to the team, theheretic_, WONG-TONG48, teald, spenpal, lotus.css, Xanthian.
+
+# Connection to the theme (Information Overload!)
+
+Wikipedia and wiki sites are notorious for offering users a simple means of digging more deeply into topics---especially topics relatively removed from their original intent. This can lead to a straightforward search becoming a meandering path through various topics, leading to an _overload_ of _information_. The colloquialism for this is "going down a [wiki rabbit hole](https://en.wikipedia.org/wiki/Wiki_rabbit_hole)".
+
+Wiki-wabbit is a tool for expanding on this experience through Discord. Alongside the eponymous `/rabbit-hole` command, other commands allow for engaging with Wikipedia's wealth of information in other ways, such as playing games and pulling up random articles.
+
+# Breakdown of Team Contributions
+
+## `theheretic_`
+
++ `wikiutils` Implementation (with WONG_TONG)
++ `Database` Implementation + refactoring (with lotus.css)
++ `Leaderboard` Implementation + refactoring (with lotus.css)
++ `Help` Implementation
++ `User Info` Implementation
++ Web hosting
++ Docker
++ Initial Idea
++ Team Engagement
++ Install Guide
++ General Debugging
++ Hosting the bot
++ Optimization
+
+## `WONG_TONG48`
+
++ Setup filesystem
++ `wikiguesser` Implementation
++ Created `wikiguesser` (`Button`) classes
++ `wikiutils` Implementation (with theheretic_)
++ `wiki-search` Implementation
+
+## `teald`
+
++ `ArticleGenerator` Implementation (+ minimal testing)
++ `GiveUpButton` Implementation
++ `wikiutils` refactoring
++ Documentation work (docstrings + some README work)
++ Manual testing for functions/installs and instructions
++ Wiki-Wabbit logo, banners, and icons
++ Initial [Connection to the Theme](#connection-to-the-theme-information-overload)
+
+## `spenpal`
+
++ `RabbitHole` Implementation
++ Conventional Commits Foundation
++ Added additional "annoying" linting rules
++ Regex assist
+
+## `lotus.css`
+
++ `Database` Implementation (with theheretic_)
++ `Leaderboard` Implementation (with theheretic_)
++ `User` Implementation
++ `Button` Classes Implementaion + Refactoring
+
+## `Xanthian`
+
++ `wikianimal` Implementation
++ [Website](https://wikiwabbit.com/)
+
+
+# Tour de Features
+
+### Wiki-Guesser
+The goal of wiki guesser is to encourage user's to guess the topic of the wikipedia article based on excerpts from the article. This can get user's overloaded with information due to the sheer number of possible topics that could appear, and the clues given.
+
+Wiki Guesser was our initial feature and led to the creation of wiki utils and button class to abstract reusable components that would be used in our other features. This includes responding to interactions, sending modals, accepting inputs, interfacing with wikipedia, and our database.
+
+1. Users can play ranked or unranked. If ranked, their score and username will be added to the leaderboard.
+2. Utilizing wiki-random, an article is chosen.
+3. An excerpt of the article's main text is taken, and all direct reference to the article's subject (title) is censored.
+4. A portion of the excerpt is shown to the user.
+5. A user can choose to:
+ - Show More
+ - This will reveal a larger portion of text from the article.
+ - Give Up
+ - Giving up will end the game, but no points will be awarded.
+ - Guess
+ - Accept a guess as to the article's topic from the user.
+ - Show Links
+ - Show links inside that article to try and gain insight into what it is.
+6. If the user's guess is not an exact match, we do a wiki-search. If the search leads to the same article, than we can accept the user's input as accurate enough to win!
+
+
+![](docs/assets/gifs/wiki-guesser.gif)
+
+
+### Wiki-Random
+This will retrieve a random article for the user.
+
+Wikipedia's articles are vast yet wildly inconsistent in quality. Rather than grab a random article that might be uninteresting, the following steps are taken.
+
+1. Generate a random date within the last 8 years.
+2. Request a list of the top 1000 wikipedia articles for that random day.
+3. Return a single random article from this recent and popular list to the user.
+
+![](docs/assets/gifs/wiki-random.gif)
+
+
+### Wiki-Animal
+Wiki animal is built on both the wiki guesser feature and the wikiutils retrieve article by category feature.
+1. When a user starts a wiki-animal game the bot searches through a specific category of mammals on Wikipedia, and returns a random mammal's Wikipedia page.
+2. Pywikibot allows us to ask for the most appropriate image for the article, which is used to provide the user a basis for judging the animals mass.
+3. Because most wikipedia articles do not have consistent weight data, a query is sent to ninja's animal API service to retrieve weight details of the random animal.
+4. Even ninjas API has inconsistently formatted weight data, so we use a regex and Pint to process the string into a quantity with magnitude and units.
+5. When a user guesses the animals weight, their input is converted to a quantity as well and compared to the animals retrieved quantity. This allows users to guess more naturally in their preferred imperial / metric units of measurement.
+
+![](docs/assets/gifs/wiki-animal.gif)
+
+
+### Wiki-Search
+Searches wikipedia for an article based on user's query.
+
+1. Attempt to go directly to a wikipedia article with the same title as the query.
+2. If article does not exist, use pywikibot to search wikipedia, and retrieve a list of articles that match this query.
+3. We check the list to find the single best result and return that article to the user.
+4. If no articles are found, inform the user.
+
+![](docs/assets/gifs/wiki-search.gif)
+
+
+### Rabbit Hole
+Rabbit Hole is an AI bot command designed to turn the vast sea of Wikipedia into an interactive, educational journey. Rabbit Hole randomly guides you through the knowledge labyrinth, helping you manage information overload using AI summarization, while making learning fun and engaging.
+
+**Core Functionality**
+- Random Exploration
+ - Simply type the command, and Rabbit Hole will fetch a random Wikipedia page.
+- AI Summarization
+ - Rabbit Hole utilizes Google Gemini to summarize large contexts of information effectively and efficiently, and presents the summary to the user.
+- Topic Suggestions
+ - Along with the summary, Rabbit Hole suggests related topics such as "Greenhouse Gas Emissions," "Global Warming," and "Paris Agreement."
+ - The user can then choose to explore any of these topics, leading to another summary and a fresh set of related topics.
+
+Connection to the Theme: "Information Overload"
+
+Rabbit Hole directly addresses the theme of "Information Overload" by offering tools and features designed to help users navigate and manage vast amounts of information effectively. Instead of bombarding users with endless text, Rabbit Hole curates, summarizes, and organizes content in a way that keeps the experience informative yet manageable. By allowing users to control the depth of information and guiding them through related topics, Rabbit Hole transforms overwhelming data into a structured, enjoyable learning journey.
+
+![](docs/assets/gifs/rabbit-hole.gif)
+
+### Leaderboard and User Info
+Display user score data for the top players in the current guild.
+
+If option *globe* is set to yes, you can view top scores for players around the world.
+
+![](docs/assets/gifs/leader_board_and_user_info.gif)
+
+### Help and Sync
+- Display help text from commands available in the bot comamnd tree.
+- Sync the command tree to the guild it is called in, aka update the commands in the guild.
+
+![](docs/assets/gifs/help-sync.gif)
diff --git a/pure-pulsars/apicache/433f9e1f75249f188a770f9d9f67786a4c255461655ae643f9ef54460bf5721e b/pure-pulsars/apicache/433f9e1f75249f188a770f9d9f67786a4c255461655ae643f9ef54460bf5721e
new file mode 100644
index 0000000..56d5840
Binary files /dev/null and b/pure-pulsars/apicache/433f9e1f75249f188a770f9d9f67786a4c255461655ae643f9ef54460bf5721e differ
diff --git a/pure-pulsars/apicache/4d0dbf069a249df676fe8ff79142e9764546794fc98cf0c760edc043b3ee42aa b/pure-pulsars/apicache/4d0dbf069a249df676fe8ff79142e9764546794fc98cf0c760edc043b3ee42aa
new file mode 100644
index 0000000..63d868d
Binary files /dev/null and b/pure-pulsars/apicache/4d0dbf069a249df676fe8ff79142e9764546794fc98cf0c760edc043b3ee42aa differ
diff --git a/pure-pulsars/apicache/4e96588b331700e9a938f8342b237045373ef2cb7c875ecea18ec46a87918a07 b/pure-pulsars/apicache/4e96588b331700e9a938f8342b237045373ef2cb7c875ecea18ec46a87918a07
new file mode 100644
index 0000000..de238e5
Binary files /dev/null and b/pure-pulsars/apicache/4e96588b331700e9a938f8342b237045373ef2cb7c875ecea18ec46a87918a07 differ
diff --git a/pure-pulsars/apicache/69c0d95c3ca45f73c9bfa6cd83a7449a3ef6462100438fcc69f06a0706d97894 b/pure-pulsars/apicache/69c0d95c3ca45f73c9bfa6cd83a7449a3ef6462100438fcc69f06a0706d97894
new file mode 100644
index 0000000..890c650
Binary files /dev/null and b/pure-pulsars/apicache/69c0d95c3ca45f73c9bfa6cd83a7449a3ef6462100438fcc69f06a0706d97894 differ
diff --git a/pure-pulsars/apicache/7cfaec1f27117f8ea367ddea098b195816c214bfde039c6dd6dd513253911342 b/pure-pulsars/apicache/7cfaec1f27117f8ea367ddea098b195816c214bfde039c6dd6dd513253911342
new file mode 100644
index 0000000..cd13a72
Binary files /dev/null and b/pure-pulsars/apicache/7cfaec1f27117f8ea367ddea098b195816c214bfde039c6dd6dd513253911342 differ
diff --git a/pure-pulsars/apicache/94c856b200d56102e4659ee51d2e4da645f5e9b6a50a1fce375aa04259b8d54b b/pure-pulsars/apicache/94c856b200d56102e4659ee51d2e4da645f5e9b6a50a1fce375aa04259b8d54b
new file mode 100644
index 0000000..e7d81b5
Binary files /dev/null and b/pure-pulsars/apicache/94c856b200d56102e4659ee51d2e4da645f5e9b6a50a1fce375aa04259b8d54b differ
diff --git a/pure-pulsars/apicache/9f11b0439eb06bef739387ec407c7d0a5edefcdc636c4304b9af31db3c7925e1 b/pure-pulsars/apicache/9f11b0439eb06bef739387ec407c7d0a5edefcdc636c4304b9af31db3c7925e1
new file mode 100644
index 0000000..4cb0a50
Binary files /dev/null and b/pure-pulsars/apicache/9f11b0439eb06bef739387ec407c7d0a5edefcdc636c4304b9af31db3c7925e1 differ
diff --git a/pure-pulsars/apicache/c7f5338aadf76deb1353379f610ab2dad243690109dc2aab447dd1cf0f4b8f42 b/pure-pulsars/apicache/c7f5338aadf76deb1353379f610ab2dad243690109dc2aab447dd1cf0f4b8f42
new file mode 100644
index 0000000..6660f67
Binary files /dev/null and b/pure-pulsars/apicache/c7f5338aadf76deb1353379f610ab2dad243690109dc2aab447dd1cf0f4b8f42 differ
diff --git a/pure-pulsars/apicache/cc3b54665f747e46ade902cb48d649df1e3b12e6b0c5fc7db163d940cc02c6f6 b/pure-pulsars/apicache/cc3b54665f747e46ade902cb48d649df1e3b12e6b0c5fc7db163d940cc02c6f6
new file mode 100644
index 0000000..6034177
Binary files /dev/null and b/pure-pulsars/apicache/cc3b54665f747e46ade902cb48d649df1e3b12e6b0c5fc7db163d940cc02c6f6 differ
diff --git a/pure-pulsars/apicache/d65462133933cc756527560ec65be3a9974e8a7cf311c973fbc674eab03ff8c6 b/pure-pulsars/apicache/d65462133933cc756527560ec65be3a9974e8a7cf311c973fbc674eab03ff8c6
new file mode 100644
index 0000000..cad2c1c
Binary files /dev/null and b/pure-pulsars/apicache/d65462133933cc756527560ec65be3a9974e8a7cf311c973fbc674eab03ff8c6 differ
diff --git a/pure-pulsars/apicache/d74c64572226ab7e4d8bbb7e955e30d28a74f6f2dceff67c33212aef8224ba79 b/pure-pulsars/apicache/d74c64572226ab7e4d8bbb7e955e30d28a74f6f2dceff67c33212aef8224ba79
new file mode 100644
index 0000000..75f630f
Binary files /dev/null and b/pure-pulsars/apicache/d74c64572226ab7e4d8bbb7e955e30d28a74f6f2dceff67c33212aef8224ba79 differ
diff --git a/pure-pulsars/apicache/d934630d399207b70f2a7fa3767a935a253115bbf0f4038470328f3e14693f53 b/pure-pulsars/apicache/d934630d399207b70f2a7fa3767a935a253115bbf0f4038470328f3e14693f53
new file mode 100644
index 0000000..7fe1358
Binary files /dev/null and b/pure-pulsars/apicache/d934630d399207b70f2a7fa3767a935a253115bbf0f4038470328f3e14693f53 differ
diff --git a/pure-pulsars/apicache/f0f7eb555e45d6862a79ce7625d64711b81b9e55f01cd01b22533047891e8a71 b/pure-pulsars/apicache/f0f7eb555e45d6862a79ce7625d64711b81b9e55f01cd01b22533047891e8a71
new file mode 100644
index 0000000..949c296
Binary files /dev/null and b/pure-pulsars/apicache/f0f7eb555e45d6862a79ce7625d64711b81b9e55f01cd01b22533047891e8a71 differ
diff --git a/pure-pulsars/apicache/f29bd7c3a6230da409c19422436193574de8f37983408001b0787fcd35f649e7 b/pure-pulsars/apicache/f29bd7c3a6230da409c19422436193574de8f37983408001b0787fcd35f649e7
new file mode 100644
index 0000000..0127e72
Binary files /dev/null and b/pure-pulsars/apicache/f29bd7c3a6230da409c19422436193574de8f37983408001b0787fcd35f649e7 differ
diff --git a/pure-pulsars/apicache/fe54b0e338b43d99cf99b964d89ee468fb0b773ed3470f85e90d4e520b4d91c5 b/pure-pulsars/apicache/fe54b0e338b43d99cf99b964d89ee468fb0b773ed3470f85e90d4e520b4d91c5
new file mode 100644
index 0000000..42f02ba
Binary files /dev/null and b/pure-pulsars/apicache/fe54b0e338b43d99cf99b964d89ee468fb0b773ed3470f85e90d4e520b4d91c5 differ
diff --git a/pure-pulsars/config.env b/pure-pulsars/config.env
new file mode 100644
index 0000000..9c26d72
--- /dev/null
+++ b/pure-pulsars/config.env
@@ -0,0 +1,4 @@
+TOKEN="discord/api/key"
+CERT_PATH="path/to/firebase/admin/sdk.json"
+GEMINI_API_KEY=""
+NINJA_API_KEY=""
diff --git a/pure-pulsars/docs/assets/Logo/banner_1_draft_2.png b/pure-pulsars/docs/assets/Logo/banner_1_draft_2.png
new file mode 100644
index 0000000..aaa9773
Binary files /dev/null and b/pure-pulsars/docs/assets/Logo/banner_1_draft_2.png differ
diff --git a/pure-pulsars/docs/assets/Logo/banner_1_draft_2_no_background.svg b/pure-pulsars/docs/assets/Logo/banner_1_draft_2_no_background.svg
new file mode 100644
index 0000000..8dee430
--- /dev/null
+++ b/pure-pulsars/docs/assets/Logo/banner_1_draft_2_no_background.svg
@@ -0,0 +1,110 @@
+
+
+
+
diff --git a/pure-pulsars/docs/assets/Logo/banner_1_draft_2_no_background_dark_mode.svg b/pure-pulsars/docs/assets/Logo/banner_1_draft_2_no_background_dark_mode.svg
new file mode 100644
index 0000000..52c01b1
--- /dev/null
+++ b/pure-pulsars/docs/assets/Logo/banner_1_draft_2_no_background_dark_mode.svg
@@ -0,0 +1,112 @@
+
+
+
+
diff --git a/pure-pulsars/docs/assets/Logo/banner_1_no_background.png b/pure-pulsars/docs/assets/Logo/banner_1_no_background.png
new file mode 100644
index 0000000..2b98403
Binary files /dev/null and b/pure-pulsars/docs/assets/Logo/banner_1_no_background.png differ
diff --git a/pure-pulsars/docs/assets/Logo/banner_1_no_background_dark_mode.png b/pure-pulsars/docs/assets/Logo/banner_1_no_background_dark_mode.png
new file mode 100644
index 0000000..fa995b9
Binary files /dev/null and b/pure-pulsars/docs/assets/Logo/banner_1_no_background_dark_mode.png differ
diff --git a/pure-pulsars/docs/assets/Logo/logo_1_draft_1.png b/pure-pulsars/docs/assets/Logo/logo_1_draft_1.png
new file mode 100644
index 0000000..cca1ebd
Binary files /dev/null and b/pure-pulsars/docs/assets/Logo/logo_1_draft_1.png differ
diff --git a/pure-pulsars/docs/assets/Logo/logo_1_draft_1_no_background.svg b/pure-pulsars/docs/assets/Logo/logo_1_draft_1_no_background.svg
new file mode 100644
index 0000000..a63f96d
--- /dev/null
+++ b/pure-pulsars/docs/assets/Logo/logo_1_draft_1_no_background.svg
@@ -0,0 +1,103 @@
+
+
+
+
diff --git a/pure-pulsars/docs/assets/Logo/logo_1_draft_1_no_background_dark_mode.svg b/pure-pulsars/docs/assets/Logo/logo_1_draft_1_no_background_dark_mode.svg
new file mode 100644
index 0000000..793e2cb
--- /dev/null
+++ b/pure-pulsars/docs/assets/Logo/logo_1_draft_1_no_background_dark_mode.svg
@@ -0,0 +1,107 @@
+
+
+
+
diff --git a/pure-pulsars/docs/assets/Logo/logo_1_no_background.png b/pure-pulsars/docs/assets/Logo/logo_1_no_background.png
new file mode 100644
index 0000000..cca1ebd
Binary files /dev/null and b/pure-pulsars/docs/assets/Logo/logo_1_no_background.png differ
diff --git a/pure-pulsars/docs/assets/Logo/logo_1_no_background_dark_mode.png b/pure-pulsars/docs/assets/Logo/logo_1_no_background_dark_mode.png
new file mode 100644
index 0000000..f43d42f
Binary files /dev/null and b/pure-pulsars/docs/assets/Logo/logo_1_no_background_dark_mode.png differ
diff --git a/pure-pulsars/docs/assets/discord_application_page.png b/pure-pulsars/docs/assets/discord_application_page.png
new file mode 100644
index 0000000..f53a429
Binary files /dev/null and b/pure-pulsars/docs/assets/discord_application_page.png differ
diff --git a/pure-pulsars/docs/assets/gifs/app-setup.gif b/pure-pulsars/docs/assets/gifs/app-setup.gif
new file mode 100644
index 0000000..79a840a
Binary files /dev/null and b/pure-pulsars/docs/assets/gifs/app-setup.gif differ
diff --git a/pure-pulsars/docs/assets/gifs/help-sync.gif b/pure-pulsars/docs/assets/gifs/help-sync.gif
new file mode 100644
index 0000000..88fd160
Binary files /dev/null and b/pure-pulsars/docs/assets/gifs/help-sync.gif differ
diff --git a/pure-pulsars/docs/assets/gifs/leader_board_and_user_info.gif b/pure-pulsars/docs/assets/gifs/leader_board_and_user_info.gif
new file mode 100644
index 0000000..0725b80
Binary files /dev/null and b/pure-pulsars/docs/assets/gifs/leader_board_and_user_info.gif differ
diff --git a/pure-pulsars/docs/assets/gifs/rabbit-hole.gif b/pure-pulsars/docs/assets/gifs/rabbit-hole.gif
new file mode 100644
index 0000000..f573732
Binary files /dev/null and b/pure-pulsars/docs/assets/gifs/rabbit-hole.gif differ
diff --git a/pure-pulsars/docs/assets/gifs/wiki-animal.gif b/pure-pulsars/docs/assets/gifs/wiki-animal.gif
new file mode 100644
index 0000000..c7ea0b4
Binary files /dev/null and b/pure-pulsars/docs/assets/gifs/wiki-animal.gif differ
diff --git a/pure-pulsars/docs/assets/gifs/wiki-guesser.gif b/pure-pulsars/docs/assets/gifs/wiki-guesser.gif
new file mode 100644
index 0000000..cea4c18
Binary files /dev/null and b/pure-pulsars/docs/assets/gifs/wiki-guesser.gif differ
diff --git a/pure-pulsars/docs/assets/gifs/wiki-random.gif b/pure-pulsars/docs/assets/gifs/wiki-random.gif
new file mode 100644
index 0000000..cb68180
Binary files /dev/null and b/pure-pulsars/docs/assets/gifs/wiki-random.gif differ
diff --git a/pure-pulsars/docs/assets/gifs/wiki-search.gif b/pure-pulsars/docs/assets/gifs/wiki-search.gif
new file mode 100644
index 0000000..5e85ca7
Binary files /dev/null and b/pure-pulsars/docs/assets/gifs/wiki-search.gif differ
diff --git a/pure-pulsars/docs/assets/realtime_create.png b/pure-pulsars/docs/assets/realtime_create.png
new file mode 100644
index 0000000..6b9494f
Binary files /dev/null and b/pure-pulsars/docs/assets/realtime_create.png differ
diff --git a/pure-pulsars/docs/meetings/meeting_notes_7_19_2024.md b/pure-pulsars/docs/meetings/meeting_notes_7_19_2024.md
new file mode 100644
index 0000000..3825705
--- /dev/null
+++ b/pure-pulsars/docs/meetings/meeting_notes_7_19_2024.md
@@ -0,0 +1,31 @@
+# Meeting 7/19
+
++ Show how many pages there are (e.g., number of links)
+ + Two different sets of data
+ 1) Links
+ 2) Backlinks
+ + Still, could be possible with a little effort
++ Delete & replace links
+ + Less clutter
+ + Or could make it ephemeral
+ + Would remove some group component of this
+ + Just articles
++ Namespaces/categories
+ + Theme a game off of a single keyword
+ + Based on whether there's a category for the "theme" or not(?)
++ Sanjeev's `rabbit hole 🐇`
+ + Summarize with LLM/third party
+ + Suggest related topics of interest
++ Each file in commands is for a single command
++ What kind of git branching we should use?
+ + be like microsoft
+ + branch if you want
++ Small server (TheHeretic)
+ + so much RAM
++ Judging criteria
+ + Docs/comments lacking
+ + Linting?
+ +
+ + Commit messages
+ + [Conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
+ + There is a [VSCode extension for conventional commits](https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits)
diff --git a/pure-pulsars/docs/template.md b/pure-pulsars/docs/template.md
new file mode 100644
index 0000000..0999c13
--- /dev/null
+++ b/pure-pulsars/docs/template.md
@@ -0,0 +1,188 @@
+
+# Python Discord Code Jam Repository Template
+
+## A primer
+
+Hello code jam participants! We've put together this repository template for you to use in [our code jams](https://pythondiscord.com/events/) or even other Python events!
+
+This document contains the following information:
+
+1. [What does this template contain?](#what-does-this-template-contain)
+2. [How do I use this template?](#how-do-i-use-this-template)
+3. [How do I adapt this template to my project?](#how-do-i-adapt-this-template-to-my-project)
+
+> [!TIP]
+> You can also look at [our style guide](https://pythondiscord.com/events/code-jams/code-style-guide/) to get more information about what we consider a maintainable code style.
+
+## What does this template contain?
+
+Here is a quick rundown of what each file in this repository contains:
+
+- [`LICENSE.txt`](LICENSE.txt): [The MIT License](https://opensource.org/licenses/MIT), an OSS approved license which grants rights to everyone to use and modify your project, and limits your liability. We highly recommend you to read the license.
+- [`.gitignore`](.gitignore): A list of files and directories that will be ignored by Git. Most of them are auto-generated or contain data that you wouldn't want to share publicly.
+- [`requirements-dev.txt`](requirements-dev.txt): Every PyPI package used for the project's development, to ensure a common development environment. More on that [below](#using-the-default-pip-setup).
+- [`pyproject.toml`](pyproject.toml): Configuration and metadata for the project, as well as the linting tool Ruff. If you're interested, you can read more about `pyproject.toml` in the [Python Packaging documentation](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/).
+- [`.pre-commit-config.yaml`](.pre-commit-config.yaml): The configuration of the [pre-commit](https://pre-commit.com/) tool.
+- [`.github/workflows/lint.yaml`](.github/workflows/lint.yaml): A [GitHub Actions](https://github.com/features/actions) workflow, a set of actions run by GitHub on their server after each push, to ensure the style requirements are met.
+
+Each of these files have comments for you to understand easily, and modify to fit your needs.
+
+### Ruff: general style rules
+
+Our first tool is Ruff. It will check your codebase and warn you about any non-conforming lines.
+It is run with the command `ruff check` in the project root.
+
+Here is a sample output:
+
+```shell
+$ ruff check
+app.py:1:5: N802 Function name `helloWorld` should be lowercase
+app.py:1:5: ANN201 Missing return type annotation for public function `helloWorld`
+app.py:2:5: D400 First line should end with a period
+app.py:2:5: D403 First word of the first line should be capitalized: `docstring` -> `Docstring`
+app.py:3:15: W292 No newline at end of file
+Found 5 errors.
+```
+
+Each line corresponds to an error. The first part is the file path, then the line number, and the column index.
+Then comes the error code, a unique identifier of the error, and then a human-readable message.
+
+If, for any reason, you do not wish to comply with this specific error on a specific line, you can add `# noqa: CODE` at the end of the line.
+For example:
+
+```python
+def helloWorld(): # noqa: N802
+ ...
+
+```
+
+This will ignore the function naming issue and pass linting.
+
+> [!WARNING]
+> We do not recommend ignoring errors unless you have a good reason to do so.
+
+### Ruff: formatting
+
+Ruff also comes with a formatter, which can be run with the command `ruff format`.
+It follows the same code style enforced by [Black](https://black.readthedocs.io/en/stable/index.html), so there's no need to pick between them.
+
+### Pre-commit: run linting before committing
+
+The second tool doesn't check your code, but rather makes sure that you actually *do* check it.
+
+It makes use of a feature called [Git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) which allow you to run a piece of code before running `git commit`.
+The good thing about it is that it will cancel your commit if the lint doesn't pass. You won't have to wait for GitHub Actions to report issues and have a second fix commit.
+
+It is *installed* by running `pre-commit install` and can be run manually by calling only `pre-commit`.
+
+[Lint before you push!](https://soundcloud.com/lemonsaurusrex/lint-before-you-push)
+
+#### List of hooks
+
+- `check-toml`: Lints and corrects your TOML files.
+- `check-yaml`: Lints and corrects your YAML files.
+- `end-of-file-fixer`: Makes sure you always have an empty line at the end of your file.
+- `trailing-whitespace`: Removes whitespaces at the end of each line.
+- `ruff`: Runs the Ruff linter.
+- `ruff-format`: Runs the Ruff formatter.
+
+## How do I use this template?
+
+### Creating your team repository
+
+One person in the team, preferably the leader, will have to create the repository and add other members as collaborators.
+
+1. In the top right corner of your screen, where **Clone** usually is, you have a **Use this template** button to click.
+ ![use-this-template-button](https://docs.github.com/assets/images/help/repository/use-this-template-button.png)
+2. Give the repository a name and a description.
+ ![create-repository-name](https://docs.github.com/assets/images/help/repository/create-repository-name.png)
+3. Click **Create repository from template**.
+4. Click **Settings** in your newly created repository.
+ ![repo-actions-settings](https://docs.github.com/assets/images/help/repository/repo-actions-settings.png)
+5. In the "Access" section of the sidebar, click **Collaborators**.
+ ![collaborators-settings](https://github.com/python-discord/code-jam-template/assets/63936253/c150110e-d1b5-4e4d-93e0-0a2cf1de352b)
+6. Click **Add people**.
+7. Insert the names of each of your teammates, and invite them. Once they have accepted the invitation in their email, they will have write access to the repository.
+
+You are now ready to go! Sit down, relax, and wait for the kickstart!
+
+> [!IMPORTANT]
+> Don't forget to swap "Python Discord" in the [`LICENSE.txt`](LICENSE.txt) file for the name of each of your team members or the name of your team *after* the start of the code jam.
+
+### Using the default pip setup
+
+Our default setup includes a bare requirements file to be used with a [virtual environment](https://docs.python.org/3/library/venv.html).
+We recommend this if you have never used any other dependency manager, although if you have, feel free to switch to it. More on that [below](#how-do-i-adapt-this-template-to-my-project).
+
+#### Creating the environment
+
+Create a virtual environment in the folder `.venv`.
+
+```shell
+python -m venv .venv
+```
+
+#### Entering the environment
+
+It will change based on your operating system and shell.
+
+```shell
+# Linux, Bash
+$ source .venv/bin/activate
+# Linux, Fish
+$ source .venv/bin/activate.fish
+# Linux, Csh
+$ source .venv/bin/activate.csh
+# Linux, PowerShell Core
+$ .venv/bin/Activate.ps1
+# Windows, cmd.exe
+> .venv\Scripts\activate.bat
+# Windows, PowerShell
+> .venv\Scripts\Activate.ps1
+```
+
+#### Installing the dependencies
+
+Once the environment is created and activated, use this command to install the development dependencies.
+
+```shell
+pip install -r requirements-dev.txt
+```
+
+#### Exiting the environment
+
+Interestingly enough, it is the same for every platform.
+
+```shell
+deactivate
+```
+
+Once the environment is activated, all the commands listed previously should work.
+
+> [!IMPORTANT]
+> We highly recommend that you run `pre-commit install` as soon as possible.
+
+## How do I adapt this template to my project?
+
+If you wish to use Pipenv or Poetry, you will have to move the dependencies in [`requirements-dev.txt`](requirements-dev.txt) to the development dependencies of your tool.
+
+We've included a porting of [`requirements-dev.txt`](requirements-dev.txt) to both [Poetry](samples/pyproject.toml) and [Pipenv](samples/Pipfile) in the [`samples` folder](samples).
+If you use the Poetry setup, make sure to change the project name, description, and authors at the top of the file.
+Also note that the Poetry [`pyproject.toml`](samples/pyproject.toml) file does not include the Ruff configuration, so if you simply replace the file then the Ruff configuration will be lost.
+
+When installing new dependencies, don't forget to [pin](https://pip.pypa.io/en/stable/topics/repeatable-installs/#pinning-the-package-versions) them by adding a version tag at the end.
+For example, if I wish to install [Click](https://click.palletsprojects.com/en/8.1.x/), a quick look at [PyPI](https://pypi.org/project/click/) tells me that `8.1.7` is the latest version.
+I will then add `click~=8.1`, without the last number, to my requirements file or dependency manager.
+
+> [!IMPORTANT]
+> A code jam project is left unmaintained after the end of the event. If the dependencies aren't pinned, the project will break after any major change in an API.
+
+## Final words
+
+> [!IMPORTANT]
+> Don't forget to replace this README with an actual description of your project! Images are also welcome!
+
+We hope this template will be helpful. Good luck in the jam.
diff --git a/pure-pulsars/poetry.lock b/pure-pulsars/poetry.lock
new file mode 100644
index 0000000..ea4de1a
--- /dev/null
+++ b/pure-pulsars/poetry.lock
@@ -0,0 +1,1942 @@
+# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+
+[[package]]
+name = "aiohttp"
+version = "3.9.5"
+description = "Async http client/server framework (asyncio)"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"},
+ {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"},
+ {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"},
+ {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"},
+ {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"},
+ {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"},
+ {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"},
+ {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"},
+ {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"},
+ {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"},
+ {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"},
+ {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"},
+ {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"},
+ {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"},
+ {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"},
+ {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"},
+ {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"},
+ {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"},
+ {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"},
+ {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"},
+ {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"},
+ {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"},
+ {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"},
+ {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"},
+ {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"},
+ {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"},
+]
+
+[package.dependencies]
+aiosignal = ">=1.1.2"
+attrs = ">=17.3.0"
+frozenlist = ">=1.1.1"
+multidict = ">=4.5,<7.0"
+yarl = ">=1.0,<2.0"
+
+[package.extras]
+speedups = ["Brotli", "aiodns", "brotlicffi"]
+
+[[package]]
+name = "aiosignal"
+version = "1.3.1"
+description = "aiosignal: a list of registered asynchronous callbacks"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
+ {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
+]
+
+[package.dependencies]
+frozenlist = ">=1.1.0"
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+description = "Reusable constraint types to use with typing.Annotated"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
+ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
+]
+
+[[package]]
+name = "attrs"
+version = "23.2.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"},
+ {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
+]
+
+[package.extras]
+cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
+dev = ["attrs[tests]", "pre-commit"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
+tests = ["attrs[tests-no-zope]", "zope-interface"]
+tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"]
+tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"]
+
+[[package]]
+name = "cachecontrol"
+version = "0.14.0"
+description = "httplib2 caching for requests"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "cachecontrol-0.14.0-py3-none-any.whl", hash = "sha256:f5bf3f0620c38db2e5122c0726bdebb0d16869de966ea6a2befe92470b740ea0"},
+ {file = "cachecontrol-0.14.0.tar.gz", hash = "sha256:7db1195b41c81f8274a7bbd97c956f44e8348265a1bc7641c37dfebc39f0c938"},
+]
+
+[package.dependencies]
+msgpack = ">=0.5.2,<2.0.0"
+requests = ">=2.16.0"
+
+[package.extras]
+dev = ["CacheControl[filecache,redis]", "black", "build", "cherrypy", "furo", "mypy", "pytest", "pytest-cov", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"]
+filecache = ["filelock (>=3.8.0)"]
+redis = ["redis (>=2.10.5)"]
+
+[[package]]
+name = "cachetools"
+version = "5.4.0"
+description = "Extensible memoizing collections and decorators"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"},
+ {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"},
+]
+
+[[package]]
+name = "certifi"
+version = "2024.7.4"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
+ {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
+]
+
+[[package]]
+name = "cffi"
+version = "1.16.0"
+description = "Foreign Function Interface for Python calling C code."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
+ {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
+ {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
+ {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
+ {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
+ {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
+ {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
+ {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
+ {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
+ {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
+ {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
+ {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
+ {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
+ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
+]
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+name = "cfgv"
+version = "3.4.0"
+description = "Validate configuration and produce human readable error messages."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
+ {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.3.2"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
+ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
+]
+
+[[package]]
+name = "click"
+version = "8.1.7"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+ {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "cryptography"
+version = "43.0.0"
+description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"},
+ {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"},
+ {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"},
+ {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"},
+ {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"},
+ {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"},
+ {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"},
+ {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"},
+ {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"},
+ {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"},
+ {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"},
+]
+
+[package.dependencies]
+cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
+
+[package.extras]
+docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
+docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
+nox = ["nox"]
+pep8test = ["check-sdist", "click", "mypy", "ruff"]
+sdist = ["build"]
+ssh = ["bcrypt (>=3.1.5)"]
+test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
+test-randomorder = ["pytest-randomly"]
+
+[[package]]
+name = "deptry"
+version = "0.17.0"
+description = "A command line utility to check for unused, missing and transitive dependencies in a Python project."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "deptry-0.17.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ddd05503cbae9cce608003bc50691cb2a6d714a9da30bc16a99116eedad5a0c2"},
+ {file = "deptry-0.17.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:31af1dd2f83bddb6cf5abc9f37a86f8ca4b8572fda971a4e7eb0d552a727f454"},
+ {file = "deptry-0.17.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0588827e36f4822517fc66308a85428780e15bbce819e2216d0a5d010edd1998"},
+ {file = "deptry-0.17.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce0eb1408aae315fa757fc9877101079ea6b2ebcae18b261e5d3e0141ba517b2"},
+ {file = "deptry-0.17.0-cp38-abi3-win_amd64.whl", hash = "sha256:d102754cd1f4ba2ed599fccaec54acb6be56bd00e8d03384d0a2bcb8ba8141e1"},
+ {file = "deptry-0.17.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1753b8807c3da82637beb6a0b32df85fea73bcc33a31bcda2087487bd92c336e"},
+ {file = "deptry-0.17.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f34309d3c2f28c459f2e55d93b67c81950cb863e1b210788f3491ab973e42f53"},
+ {file = "deptry-0.17.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac6b569c9623e41f1a18f722ddf8422ca7b0d5f718f9d6c71bc9dfcd9e28cf5d"},
+ {file = "deptry-0.17.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de2feebecb256ccee69b0f8144c678763d7842704959239fa7e7f3fc60f8a1"},
+ {file = "deptry-0.17.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e4724e014c0787452962833cc3030170d267fbd3ac34f6c09b8449d8e8147f39"},
+ {file = "deptry-0.17.0.tar.gz", hash = "sha256:f48a71bab8f46a896fe507c8be5f2b50bb9bab0c44e4dfad00afe87e9a08c14b"},
+]
+
+[package.dependencies]
+click = ">=8.0.0,<9"
+colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""}
+
+[[package]]
+name = "discord-py"
+version = "2.4.0"
+description = "A Python wrapper for the Discord API"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "discord.py-2.4.0-py3-none-any.whl", hash = "sha256:b8af6711c70f7e62160bfbecb55be699b5cb69d007426759ab8ab06b1bd77d1d"},
+ {file = "discord_py-2.4.0.tar.gz", hash = "sha256:d07cb2a223a185873a1d0ee78b9faa9597e45b3f6186df21a95cec1e9bcdc9a5"},
+]
+
+[package.dependencies]
+aiohttp = ">=3.7.4,<4"
+
+[package.extras]
+docs = ["sphinx (==4.4.0)", "sphinx-inline-tabs (==2023.4.21)", "sphinxcontrib-applehelp (==1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (==2.0.1)", "sphinxcontrib-jsmath (==1.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport (==1.2.4)", "typing-extensions (>=4.3,<5)"]
+speed = ["Brotli", "aiodns (>=1.1)", "cchardet (==2.1.7)", "orjson (>=3.5.4)"]
+test = ["coverage[toml]", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "typing-extensions (>=4.3,<5)", "tzdata"]
+voice = ["PyNaCl (>=1.3.0,<1.6)"]
+
+[[package]]
+name = "distlib"
+version = "0.3.8"
+description = "Distribution utilities"
+optional = false
+python-versions = "*"
+files = [
+ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
+ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
+]
+
+[[package]]
+name = "fake-useragent"
+version = "1.5.1"
+description = "Up-to-date simple useragent faker with real world database"
+optional = false
+python-versions = "*"
+files = [
+ {file = "fake-useragent-1.5.1.tar.gz", hash = "sha256:6387269f5a2196b5ba7ed8935852f75486845a1c95c50e72460e6a8e762f5c49"},
+ {file = "fake_useragent-1.5.1-py3-none-any.whl", hash = "sha256:57415096557c8a4e23b62a375c21c55af5fd4ba30549227f562d2c4f5b60e3b3"},
+]
+
+[[package]]
+name = "filelock"
+version = "3.15.4"
+description = "A platform independent file lock."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"},
+ {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"},
+]
+
+[package.extras]
+docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"]
+typing = ["typing-extensions (>=4.8)"]
+
+[[package]]
+name = "firebase-admin"
+version = "6.5.0"
+description = "Firebase Admin Python SDK"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "firebase_admin-6.5.0-py3-none-any.whl", hash = "sha256:fe34ee3ca0e625c5156b3931ca4b4b69b5fc344dbe51bba9706ff674ce277898"},
+ {file = "firebase_admin-6.5.0.tar.gz", hash = "sha256:e716dde1447f0a1cd1523be76ff872df33c4e1a3c079564ace033b2ad60bcc4f"},
+]
+
+[package.dependencies]
+cachecontrol = ">=0.12.6"
+google-api-core = {version = ">=1.22.1,<3.0.0dev", extras = ["grpc"], markers = "platform_python_implementation != \"PyPy\""}
+google-api-python-client = ">=1.7.8"
+google-cloud-firestore = {version = ">=2.9.1", markers = "platform_python_implementation != \"PyPy\""}
+google-cloud-storage = ">=1.37.1"
+pyjwt = {version = ">=2.5.0", extras = ["crypto"]}
+
+[[package]]
+name = "frozenlist"
+version = "1.4.1"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"},
+ {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"},
+ {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"},
+ {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"},
+ {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"},
+ {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"},
+ {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"},
+ {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"},
+ {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"},
+ {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"},
+ {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"},
+ {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"},
+ {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"},
+ {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"},
+ {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"},
+ {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"},
+ {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"},
+ {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"},
+ {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"},
+ {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"},
+ {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"},
+ {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"},
+ {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"},
+ {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"},
+ {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"},
+ {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"},
+ {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"},
+ {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"},
+ {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"},
+ {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"},
+ {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"},
+ {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"},
+ {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"},
+ {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"},
+ {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"},
+ {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"},
+ {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"},
+ {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"},
+ {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"},
+ {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"},
+ {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"},
+ {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"},
+ {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"},
+ {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"},
+ {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"},
+ {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"},
+ {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"},
+ {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"},
+ {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"},
+ {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"},
+ {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"},
+ {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"},
+ {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"},
+ {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"},
+ {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"},
+ {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"},
+ {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"},
+ {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"},
+ {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"},
+ {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"},
+ {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"},
+ {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"},
+ {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"},
+ {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"},
+ {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"},
+ {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"},
+ {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"},
+ {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"},
+ {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"},
+ {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"},
+ {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"},
+ {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"},
+ {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"},
+ {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"},
+ {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"},
+ {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"},
+ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"},
+]
+
+[[package]]
+name = "google-ai-generativelanguage"
+version = "0.6.6"
+description = "Google Ai Generativelanguage API client library"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "google-ai-generativelanguage-0.6.6.tar.gz", hash = "sha256:1739f035caeeeca5c28f887405eec8690f3372daf79fecf26454a97a4f1733a8"},
+ {file = "google_ai_generativelanguage-0.6.6-py3-none-any.whl", hash = "sha256:59297737931f073d55ce1268dcc6d95111ee62850349d2b6cde942b16a4fca5c"},
+]
+
+[package.dependencies]
+google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]}
+google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev"
+proto-plus = ">=1.22.3,<2.0.0dev"
+protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
+
+[[package]]
+name = "google-api-core"
+version = "2.19.1"
+description = "Google API client core library"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"},
+ {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"},
+]
+
+[package.dependencies]
+google-auth = ">=2.14.1,<3.0.dev0"
+googleapis-common-protos = ">=1.56.2,<2.0.dev0"
+grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
+grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
+proto-plus = ">=1.22.3,<2.0.0dev"
+protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0"
+requests = ">=2.18.0,<3.0.0.dev0"
+
+[package.extras]
+grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"]
+grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
+grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
+
+[[package]]
+name = "google-api-python-client"
+version = "2.138.0"
+description = "Google API Client Library for Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "google_api_python_client-2.138.0-py2.py3-none-any.whl", hash = "sha256:1dd279124e4e77cbda4769ffb4abe7e7c32528ef1e18739320fef2a07b750764"},
+ {file = "google_api_python_client-2.138.0.tar.gz", hash = "sha256:31080fbf0e64687876135cc23d1bec1ca3b80d7702177dd17b04131ea889eb70"},
+]
+
+[package.dependencies]
+google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0"
+google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0"
+google-auth-httplib2 = ">=0.2.0,<1.0.0"
+httplib2 = ">=0.19.0,<1.dev0"
+uritemplate = ">=3.0.1,<5"
+
+[[package]]
+name = "google-auth"
+version = "2.32.0"
+description = "Google Authentication Library"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "google_auth-2.32.0-py2.py3-none-any.whl", hash = "sha256:53326ea2ebec768070a94bee4e1b9194c9646ea0c2bd72422785bd0f9abfad7b"},
+ {file = "google_auth-2.32.0.tar.gz", hash = "sha256:49315be72c55a6a37d62819e3573f6b416aca00721f7e3e31a008d928bf64022"},
+]
+
+[package.dependencies]
+cachetools = ">=2.0.0,<6.0"
+pyasn1-modules = ">=0.2.1"
+rsa = ">=3.1.4,<5"
+
+[package.extras]
+aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"]
+enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"]
+pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"]
+reauth = ["pyu2f (>=0.1.5)"]
+requests = ["requests (>=2.20.0,<3.0.0.dev0)"]
+
+[[package]]
+name = "google-auth-httplib2"
+version = "0.2.0"
+description = "Google Authentication Library: httplib2 transport"
+optional = false
+python-versions = "*"
+files = [
+ {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"},
+ {file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"},
+]
+
+[package.dependencies]
+google-auth = "*"
+httplib2 = ">=0.19.0"
+
+[[package]]
+name = "google-cloud-core"
+version = "2.4.1"
+description = "Google Cloud API client core library"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"},
+ {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"},
+]
+
+[package.dependencies]
+google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev"
+google-auth = ">=1.25.0,<3.0dev"
+
+[package.extras]
+grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"]
+
+[[package]]
+name = "google-cloud-firestore"
+version = "2.17.0"
+description = "Google Cloud Firestore API client library"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "google-cloud-firestore-2.17.0.tar.gz", hash = "sha256:3e81b71d963b7e3bcc87fb813236f3864bc7b0a3f207ac4d87bc6595efe2b8a3"},
+ {file = "google_cloud_firestore-2.17.0-py2.py3-none-any.whl", hash = "sha256:a4211c9f4ff9a701103c2217f87855ffb04ec595447753956b2ab97d8492039e"},
+]
+
+[package.dependencies]
+google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]}
+google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev"
+google-cloud-core = ">=1.4.1,<3.0.0dev"
+proto-plus = {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}
+protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
+
+[[package]]
+name = "google-cloud-storage"
+version = "2.18.0"
+description = "Google Cloud Storage API client library"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "google_cloud_storage-2.18.0-py2.py3-none-any.whl", hash = "sha256:e8e1a9577952143c3fca8163005ecfadd2d70ec080fa158a8b305000e2c22fbb"},
+ {file = "google_cloud_storage-2.18.0.tar.gz", hash = "sha256:0aa3f7c57f3632f81b455d91558d2b27ada96eee2de3aaa17f689db1470d9578"},
+]
+
+[package.dependencies]
+google-api-core = ">=2.15.0,<3.0.0dev"
+google-auth = ">=2.26.1,<3.0dev"
+google-cloud-core = ">=2.3.0,<3.0dev"
+google-crc32c = ">=1.0,<2.0dev"
+google-resumable-media = ">=2.6.0"
+requests = ">=2.18.0,<3.0.0dev"
+
+[package.extras]
+protobuf = ["protobuf (<6.0.0dev)"]
+tracing = ["opentelemetry-api (>=1.1.0)"]
+
+[[package]]
+name = "google-crc32c"
+version = "1.5.0"
+description = "A python wrapper of the C library 'Google CRC32C'"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "google-crc32c-1.5.0.tar.gz", hash = "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7"},
+ {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13"},
+ {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346"},
+ {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65"},
+ {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b"},
+ {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02"},
+ {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4"},
+ {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e"},
+ {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c"},
+ {file = "google_crc32c-1.5.0-cp310-cp310-win32.whl", hash = "sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee"},
+ {file = "google_crc32c-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289"},
+ {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273"},
+ {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298"},
+ {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57"},
+ {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438"},
+ {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906"},
+ {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183"},
+ {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd"},
+ {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c"},
+ {file = "google_crc32c-1.5.0-cp311-cp311-win32.whl", hash = "sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709"},
+ {file = "google_crc32c-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968"},
+ {file = "google_crc32c-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae"},
+ {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556"},
+ {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f"},
+ {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876"},
+ {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc"},
+ {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c"},
+ {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a"},
+ {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e"},
+ {file = "google_crc32c-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94"},
+ {file = "google_crc32c-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-win32.whl", hash = "sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4"},
+ {file = "google_crc32c-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-win32.whl", hash = "sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c"},
+ {file = "google_crc32c-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541"},
+ {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325"},
+ {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd"},
+ {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091"},
+ {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178"},
+ {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2"},
+ {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d"},
+ {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2"},
+ {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5"},
+ {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462"},
+ {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314"},
+ {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728"},
+ {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88"},
+ {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb"},
+ {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31"},
+ {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93"},
+]
+
+[package.extras]
+testing = ["pytest"]
+
+[[package]]
+name = "google-generativeai"
+version = "0.7.2"
+description = "Google Generative AI High level API client library and tools."
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "google_generativeai-0.7.2-py3-none-any.whl", hash = "sha256:3117d1ebc92ee77710d4bc25ab4763492fddce9b6332eb25d124cf5d8b78b339"},
+]
+
+[package.dependencies]
+google-ai-generativelanguage = "0.6.6"
+google-api-core = "*"
+google-api-python-client = "*"
+google-auth = ">=2.15.0"
+protobuf = "*"
+pydantic = "*"
+tqdm = "*"
+typing-extensions = "*"
+
+[package.extras]
+dev = ["Pillow", "absl-py", "black", "ipython", "nose2", "pandas", "pytype", "pyyaml"]
+
+[[package]]
+name = "google-resumable-media"
+version = "2.7.1"
+description = "Utilities for Google Media Downloads and Resumable Uploads"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "google-resumable-media-2.7.1.tar.gz", hash = "sha256:eae451a7b2e2cdbaaa0fd2eb00cc8a1ee5e95e16b55597359cbc3d27d7d90e33"},
+ {file = "google_resumable_media-2.7.1-py2.py3-none-any.whl", hash = "sha256:103ebc4ba331ab1bfdac0250f8033627a2cd7cde09e7ccff9181e31ba4315b2c"},
+]
+
+[package.dependencies]
+google-crc32c = ">=1.0,<2.0dev"
+
+[package.extras]
+aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"]
+requests = ["requests (>=2.18.0,<3.0.0dev)"]
+
+[[package]]
+name = "googleapis-common-protos"
+version = "1.63.2"
+description = "Common protobufs used in Google APIs"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"},
+ {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"},
+]
+
+[package.dependencies]
+protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0"
+
+[package.extras]
+grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"]
+
+[[package]]
+name = "grpcio"
+version = "1.65.1"
+description = "HTTP/2-based RPC framework"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "grpcio-1.65.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:3dc5f928815b8972fb83b78d8db5039559f39e004ec93ebac316403fe031a062"},
+ {file = "grpcio-1.65.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:8333ca46053c35484c9f2f7e8d8ec98c1383a8675a449163cea31a2076d93de8"},
+ {file = "grpcio-1.65.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:7af64838b6e615fff0ec711960ed9b6ee83086edfa8c32670eafb736f169d719"},
+ {file = "grpcio-1.65.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb64b4166362d9326f7efbf75b1c72106c1aa87f13a8c8b56a1224fac152f5c"},
+ {file = "grpcio-1.65.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8422dc13ad93ec8caa2612b5032a2b9cd6421c13ed87f54db4a3a2c93afaf77"},
+ {file = "grpcio-1.65.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4effc0562b6c65d4add6a873ca132e46ba5e5a46f07c93502c37a9ae7f043857"},
+ {file = "grpcio-1.65.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a6c71575a2fedf259724981fd73a18906513d2f306169c46262a5bae956e6364"},
+ {file = "grpcio-1.65.1-cp310-cp310-win32.whl", hash = "sha256:34966cf526ef0ea616e008d40d989463e3db157abb213b2f20c6ce0ae7928875"},
+ {file = "grpcio-1.65.1-cp310-cp310-win_amd64.whl", hash = "sha256:ca931de5dd6d9eb94ff19a2c9434b23923bce6f767179fef04dfa991f282eaad"},
+ {file = "grpcio-1.65.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:bbb46330cc643ecf10bd9bd4ca8e7419a14b6b9dedd05f671c90fb2c813c6037"},
+ {file = "grpcio-1.65.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d827a6fb9215b961eb73459ad7977edb9e748b23e3407d21c845d1d8ef6597e5"},
+ {file = "grpcio-1.65.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:6e71aed8835f8d9fbcb84babc93a9da95955d1685021cceb7089f4f1e717d719"},
+ {file = "grpcio-1.65.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a1c84560b3b2d34695c9ba53ab0264e2802721c530678a8f0a227951f453462"},
+ {file = "grpcio-1.65.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27adee2338d697e71143ed147fe286c05810965d5d30ec14dd09c22479bfe48a"},
+ {file = "grpcio-1.65.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f62652ddcadc75d0e7aa629e96bb61658f85a993e748333715b4ab667192e4e8"},
+ {file = "grpcio-1.65.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:71a05fd814700dd9cb7d9a507f2f6a1ef85866733ccaf557eedacec32d65e4c2"},
+ {file = "grpcio-1.65.1-cp311-cp311-win32.whl", hash = "sha256:b590f1ad056294dfaeac0b7e1b71d3d5ace638d8dd1f1147ce4bd13458783ba8"},
+ {file = "grpcio-1.65.1-cp311-cp311-win_amd64.whl", hash = "sha256:12e9bdf3b5fd48e5fbe5b3da382ad8f97c08b47969f3cca81dd9b36b86ed39e2"},
+ {file = "grpcio-1.65.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:54cb822e177374b318b233e54b6856c692c24cdbd5a3ba5335f18a47396bac8f"},
+ {file = "grpcio-1.65.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aaf3c54419a28d45bd1681372029f40e5bfb58e5265e3882eaf21e4a5f81a119"},
+ {file = "grpcio-1.65.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:557de35bdfbe8bafea0a003dbd0f4da6d89223ac6c4c7549d78e20f92ead95d9"},
+ {file = "grpcio-1.65.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8bfd95ef3b097f0cc86ade54eafefa1c8ed623aa01a26fbbdcd1a3650494dd11"},
+ {file = "grpcio-1.65.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e6a8f3d6c41e6b642870afe6cafbaf7b61c57317f9ec66d0efdaf19db992b90"},
+ {file = "grpcio-1.65.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1faaf7355ceed07ceaef0b9dcefa4c98daf1dd8840ed75c2de128c3f4a4d859d"},
+ {file = "grpcio-1.65.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:60f1f38eed830488ad2a1b11579ef0f345ff16fffdad1d24d9fbc97ba31804ff"},
+ {file = "grpcio-1.65.1-cp312-cp312-win32.whl", hash = "sha256:e75acfa52daf5ea0712e8aa82f0003bba964de7ae22c26d208cbd7bc08500177"},
+ {file = "grpcio-1.65.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff5a84907e51924973aa05ed8759210d8cdae7ffcf9e44fd17646cf4a902df59"},
+ {file = "grpcio-1.65.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:1fbd6331f18c3acd7e09d17fd840c096f56eaf0ef830fbd50af45ae9dc8dfd83"},
+ {file = "grpcio-1.65.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:de5b6be29116e094c5ef9d9e4252e7eb143e3d5f6bd6d50a78075553ab4930b0"},
+ {file = "grpcio-1.65.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:e4a3cdba62b2d6aeae6027ae65f350de6dc082b72e6215eccf82628e79efe9ba"},
+ {file = "grpcio-1.65.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941c4869aa229d88706b78187d60d66aca77fe5c32518b79e3c3e03fc26109a2"},
+ {file = "grpcio-1.65.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f40cebe5edb518d78b8131e87cb83b3ee688984de38a232024b9b44e74ee53d3"},
+ {file = "grpcio-1.65.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2ca684ba331fb249d8a1ce88db5394e70dbcd96e58d8c4b7e0d7b141a453dce9"},
+ {file = "grpcio-1.65.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8558f0083ddaf5de64a59c790bffd7568e353914c0c551eae2955f54ee4b857f"},
+ {file = "grpcio-1.65.1-cp38-cp38-win32.whl", hash = "sha256:8d8143a3e3966f85dce6c5cc45387ec36552174ba5712c5dc6fcc0898fb324c0"},
+ {file = "grpcio-1.65.1-cp38-cp38-win_amd64.whl", hash = "sha256:76e81a86424d6ca1ce7c16b15bdd6a964a42b40544bf796a48da241fdaf61153"},
+ {file = "grpcio-1.65.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:cb5175f45c980ff418998723ea1b3869cce3766d2ab4e4916fbd3cedbc9d0ed3"},
+ {file = "grpcio-1.65.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b12c1aa7b95abe73b3e04e052c8b362655b41c7798da69f1eaf8d186c7d204df"},
+ {file = "grpcio-1.65.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:3019fb50128b21a5e018d89569ffaaaa361680e1346c2f261bb84a91082eb3d3"},
+ {file = "grpcio-1.65.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ae15275ed98ea267f64ee9ddedf8ecd5306a5b5bb87972a48bfe24af24153e8"},
+ {file = "grpcio-1.65.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f096ffb881f37e8d4f958b63c74bfc400c7cebd7a944b027357cd2fb8d91a57"},
+ {file = "grpcio-1.65.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2f56b5a68fdcf17a0a1d524bf177218c3c69b3947cb239ea222c6f1867c3ab68"},
+ {file = "grpcio-1.65.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:941596d419b9736ab548aa0feb5bbba922f98872668847bf0720b42d1d227b9e"},
+ {file = "grpcio-1.65.1-cp39-cp39-win32.whl", hash = "sha256:5fd7337a823b890215f07d429f4f193d24b80d62a5485cf88ee06648591a0c57"},
+ {file = "grpcio-1.65.1-cp39-cp39-win_amd64.whl", hash = "sha256:1bceeec568372cbebf554eae1b436b06c2ff24cfaf04afade729fb9035408c6c"},
+ {file = "grpcio-1.65.1.tar.gz", hash = "sha256:3c492301988cd720cd145d84e17318d45af342e29ef93141228f9cd73222368b"},
+]
+
+[package.extras]
+protobuf = ["grpcio-tools (>=1.65.1)"]
+
+[[package]]
+name = "grpcio-status"
+version = "1.62.2"
+description = "Status proto mapping for gRPC"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "grpcio-status-1.62.2.tar.gz", hash = "sha256:62e1bfcb02025a1cd73732a2d33672d3e9d0df4d21c12c51e0bbcaf09bab742a"},
+ {file = "grpcio_status-1.62.2-py3-none-any.whl", hash = "sha256:206ddf0eb36bc99b033f03b2c8e95d319f0044defae9b41ae21408e7e0cda48f"},
+]
+
+[package.dependencies]
+googleapis-common-protos = ">=1.5.5"
+grpcio = ">=1.62.2"
+protobuf = ">=4.21.6"
+
+[[package]]
+name = "httplib2"
+version = "0.22.0"
+description = "A comprehensive HTTP client library."
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"},
+ {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"},
+]
+
+[package.dependencies]
+pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""}
+
+[[package]]
+name = "humanize"
+version = "4.10.0"
+description = "Python humanize utilities"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "humanize-4.10.0-py3-none-any.whl", hash = "sha256:39e7ccb96923e732b5c2e27aeaa3b10a8dfeeba3eb965ba7b74a3eb0e30040a6"},
+ {file = "humanize-4.10.0.tar.gz", hash = "sha256:06b6eb0293e4b85e8d385397c5868926820db32b9b654b932f57fa41c23c9978"},
+]
+
+[package.extras]
+tests = ["freezegun", "pytest", "pytest-cov"]
+
+[[package]]
+name = "identify"
+version = "2.6.0"
+description = "File identification library for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"},
+ {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"},
+]
+
+[package.extras]
+license = ["ukkonen"]
+
+[[package]]
+name = "idna"
+version = "3.7"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
+ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
+
+[[package]]
+name = "msgpack"
+version = "1.0.8"
+description = "MessagePack serializer"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"},
+ {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"},
+ {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"},
+ {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"},
+ {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"},
+ {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"},
+ {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"},
+ {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"},
+ {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"},
+ {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"},
+ {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"},
+ {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"},
+ {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"},
+ {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"},
+ {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"},
+ {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"},
+ {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"},
+ {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"},
+ {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"},
+ {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"},
+ {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"},
+ {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"},
+ {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"},
+ {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"},
+ {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"},
+ {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"},
+ {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"},
+ {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"},
+ {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"},
+ {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"},
+ {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"},
+ {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"},
+ {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"},
+ {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"},
+ {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"},
+ {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"},
+ {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"},
+ {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"},
+ {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"},
+ {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"},
+ {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"},
+ {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"},
+ {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"},
+ {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"},
+ {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"},
+ {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"},
+ {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"},
+ {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"},
+ {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"},
+ {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"},
+ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"},
+ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"},
+ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"},
+ {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"},
+ {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"},
+ {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"},
+]
+
+[[package]]
+name = "multidict"
+version = "6.0.5"
+description = "multidict implementation"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"},
+ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"},
+ {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"},
+ {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"},
+ {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"},
+ {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"},
+ {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"},
+ {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"},
+ {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"},
+ {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"},
+ {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"},
+ {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"},
+ {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"},
+ {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"},
+ {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"},
+ {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"},
+ {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"},
+ {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"},
+ {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"},
+ {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"},
+ {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"},
+ {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"},
+ {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"},
+ {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"},
+ {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"},
+ {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"},
+ {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"},
+ {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"},
+ {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"},
+ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"},
+]
+
+[[package]]
+name = "mwparserfromhell"
+version = "0.6.6"
+description = "MWParserFromHell is a parser for MediaWiki wikicode."
+optional = false
+python-versions = ">= 3.8"
+files = [
+ {file = "mwparserfromhell-0.6.6-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:d6995b9cfe6ec79556db0232a39210ac11aa69ee304cfc95b29c51be381e202b"},
+ {file = "mwparserfromhell-0.6.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebc70f8a24aa60e54728be740f1c12a4acb1b12d1cc947d87b067cc1c83339fd"},
+ {file = "mwparserfromhell-0.6.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9136696d6b29838adcf8f428e3f7028b2c6e788fc05fe1beeb4b135429c356df"},
+ {file = "mwparserfromhell-0.6.6-cp310-cp310-win32.whl", hash = "sha256:6b11dea3bcdebe4554933169eade815e9d6b898175faa5a20a744524fd99210f"},
+ {file = "mwparserfromhell-0.6.6-cp310-cp310-win_amd64.whl", hash = "sha256:6a89edf53f15877223d923e122e9a97f3f7b85f56dc56d91a3d77b89c9dd4126"},
+ {file = "mwparserfromhell-0.6.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fff66e97f7c02aa0fd57ff8f702977a9c5a1d72ef55b64ee9b146291e4c41057"},
+ {file = "mwparserfromhell-0.6.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59633d3cc09993af75ced8dfbd6800e1e38e64620851a095575621548448875c"},
+ {file = "mwparserfromhell-0.6.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:007d0859e5467241b73c6e974df039a074609ce4e2b9df8c2263a8920554d032"},
+ {file = "mwparserfromhell-0.6.6-cp311-cp311-win32.whl", hash = "sha256:dbe5976b1b524e26aa2eb71b6219960f2578f56b536c68e0a79deb63e3b7f710"},
+ {file = "mwparserfromhell-0.6.6-cp311-cp311-win_amd64.whl", hash = "sha256:063c1e79befd1f55d77c358e0f5006f5ecf88ddf218ff6af55188d686139330e"},
+ {file = "mwparserfromhell-0.6.6-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:910d36bc70e8bea758380e75c12fd47626b295abec9f73a6099d8f937a649e77"},
+ {file = "mwparserfromhell-0.6.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2febd92a55a3f19b461833267726cb81429c3d6cb0006ad1691dfa849789e5d"},
+ {file = "mwparserfromhell-0.6.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b75fae6d01c8fda19dbf127175122d7aa2964ef6454690e6868bbc3d80a7bc1"},
+ {file = "mwparserfromhell-0.6.6-cp312-cp312-win32.whl", hash = "sha256:19e9a4bcd85707c83172405eb2a9a046eff9d38dd7f1a56a5e5ecbbfef4a640a"},
+ {file = "mwparserfromhell-0.6.6-cp312-cp312-win_amd64.whl", hash = "sha256:cdc46c115b2495d4025920b7b30a6885a96d2b797ccc4009bf3cc02940ae55d3"},
+ {file = "mwparserfromhell-0.6.6-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:fd05481adc0806f4b8f8f8cb309ec56924b17ce386cb1c2f73919d8a012e6b16"},
+ {file = "mwparserfromhell-0.6.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03e03b8bec729af850457d045b04d0c9d3e296ff8bf66b455f754cccb29c3bea"},
+ {file = "mwparserfromhell-0.6.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d2422659abb29191a0fa096cf8bead837ac3ecd343065569b2acc7a84ecf866"},
+ {file = "mwparserfromhell-0.6.6-cp38-cp38-win32.whl", hash = "sha256:a58251a5d5c77abdfd061624dc05667c2774e93e8178a2fbd1a3b45f8673f1a9"},
+ {file = "mwparserfromhell-0.6.6-cp38-cp38-win_amd64.whl", hash = "sha256:e28ffa9a7e0748ec64002a84234201ef69c2d4a710508baf9cc25f4ee274c6bd"},
+ {file = "mwparserfromhell-0.6.6-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:746bad799179684994ecee72a26352e0bbe2b697f6a7e35dc5ad151606bcb8ab"},
+ {file = "mwparserfromhell-0.6.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50c482e703d2d51401f7e36a71ae9493901f170225940196292f97398713dde5"},
+ {file = "mwparserfromhell-0.6.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1915fe4f5e5ae34f16242d4cd98da2adc81a810ab94105ec2af3dc95d7ce74aa"},
+ {file = "mwparserfromhell-0.6.6-cp39-cp39-win32.whl", hash = "sha256:54e2dd30edc1a358408d14343b30dcca0b4613227781e4bbee968bd4395d94ff"},
+ {file = "mwparserfromhell-0.6.6-cp39-cp39-win_amd64.whl", hash = "sha256:1960bcc5115ea57427df130150edf1dbfc2fb03465e548e630bb6eb37976d793"},
+ {file = "mwparserfromhell-0.6.6.tar.gz", hash = "sha256:71afec1e9784ba576e95d6f34845582d3c733a3a52ba770dd8a9c3a40e5b649f"},
+]
+
+[[package]]
+name = "nodeenv"
+version = "1.9.1"
+description = "Node.js virtual environment builder"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
+ {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
+]
+
+[[package]]
+name = "packaging"
+version = "24.1"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
+ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.2.2"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
+ {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
+]
+
+[package.extras]
+docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
+type = ["mypy (>=1.8)"]
+
+[[package]]
+name = "pluggy"
+version = "1.5.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+ {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pre-commit"
+version = "3.7.1"
+description = "A framework for managing and maintaining multi-language pre-commit hooks."
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"},
+ {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"},
+]
+
+[package.dependencies]
+cfgv = ">=2.0.0"
+identify = ">=1.0.0"
+nodeenv = ">=0.11.1"
+pyyaml = ">=5.1"
+virtualenv = ">=20.10.0"
+
+[[package]]
+name = "proto-plus"
+version = "1.24.0"
+description = "Beautiful, Pythonic protocol buffers."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"},
+ {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"},
+]
+
+[package.dependencies]
+protobuf = ">=3.19.0,<6.0.0dev"
+
+[package.extras]
+testing = ["google-api-core (>=1.31.5)"]
+
+[[package]]
+name = "protobuf"
+version = "4.25.4"
+description = ""
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "protobuf-4.25.4-cp310-abi3-win32.whl", hash = "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4"},
+ {file = "protobuf-4.25.4-cp310-abi3-win_amd64.whl", hash = "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d"},
+ {file = "protobuf-4.25.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b"},
+ {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835"},
+ {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040"},
+ {file = "protobuf-4.25.4-cp38-cp38-win32.whl", hash = "sha256:7e372cbbda66a63ebca18f8ffaa6948455dfecc4e9c1029312f6c2edcd86c4e1"},
+ {file = "protobuf-4.25.4-cp38-cp38-win_amd64.whl", hash = "sha256:051e97ce9fa6067a4546e75cb14f90cf0232dcb3e3d508c448b8d0e4265b61c1"},
+ {file = "protobuf-4.25.4-cp39-cp39-win32.whl", hash = "sha256:90bf6fd378494eb698805bbbe7afe6c5d12c8e17fca817a646cd6a1818c696ca"},
+ {file = "protobuf-4.25.4-cp39-cp39-win_amd64.whl", hash = "sha256:ac79a48d6b99dfed2729ccccee547b34a1d3d63289c71cef056653a846a2240f"},
+ {file = "protobuf-4.25.4-py3-none-any.whl", hash = "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978"},
+ {file = "protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d"},
+]
+
+[[package]]
+name = "pyasn1"
+version = "0.6.0"
+description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"},
+ {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"},
+]
+
+[[package]]
+name = "pyasn1-modules"
+version = "0.4.0"
+description = "A collection of ASN.1-based protocols modules"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"},
+ {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"},
+]
+
+[package.dependencies]
+pyasn1 = ">=0.4.6,<0.7.0"
+
+[[package]]
+name = "pycparser"
+version = "2.22"
+description = "C parser in Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
+ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
+]
+
+[[package]]
+name = "pydantic"
+version = "2.8.2"
+description = "Data validation using Python type hints"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
+ {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
+]
+
+[package.dependencies]
+annotated-types = ">=0.4.0"
+pydantic-core = "2.20.1"
+typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""}
+
+[package.extras]
+email = ["email-validator (>=2.0.0)"]
+
+[[package]]
+name = "pydantic-core"
+version = "2.20.1"
+description = "Core functionality for Pydantic validation and serialization"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"},
+ {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"},
+ {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"},
+ {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"},
+ {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"},
+ {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"},
+ {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"},
+ {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"},
+ {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"},
+ {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"},
+ {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"},
+ {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"},
+ {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"},
+ {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
+
+[[package]]
+name = "pyjwt"
+version = "2.8.0"
+description = "JSON Web Token implementation in Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"},
+ {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"},
+]
+
+[package.dependencies]
+cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""}
+
+[package.extras]
+crypto = ["cryptography (>=3.4.0)"]
+dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
+docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
+tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
+
+[[package]]
+name = "pyparsing"
+version = "3.1.2"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+optional = false
+python-versions = ">=3.6.8"
+files = [
+ {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"},
+ {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"},
+]
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "pytest"
+version = "8.3.2"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"},
+ {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=1.5,<2"
+
+[package.extras]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-asyncio"
+version = "0.23.8"
+description = "Pytest support for asyncio"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"},
+ {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"},
+]
+
+[package.dependencies]
+pytest = ">=7.0.0,<9"
+
+[package.extras]
+docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
+testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
+
+[[package]]
+name = "python-dotenv"
+version = "1.0.1"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
+ {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
+[[package]]
+name = "pywikibot"
+version = "9.2.1"
+description = "Python MediaWiki Bot Framework"
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "pywikibot-9.2.1-py3-none-any.whl", hash = "sha256:a26d918cf88ef56fdb1421b65b09def200cc28031cdc922d72a4198fbfddd225"},
+ {file = "pywikibot-9.2.1.tar.gz", hash = "sha256:3f4fbc57f1765aa0fa1ccf84125bcfa475cae95b9cc0291867b751f3d4ac8fa2"},
+]
+
+[package.dependencies]
+mwparserfromhell = ">=0.5.2"
+packaging = "*"
+requests = ">=2.21.0"
+
+[package.extras]
+create-isbn-edition-py = ["isbnlib", "unidecode"]
+eventstreams = ["sseclient (>=0.0.18,<0.0.23)"]
+flake8 = ["darglint2", "flake8 (>=5.0.4)", "flake8-bugbear (!=24.1.17)", "flake8-comprehensions (>=3.13.0)", "flake8-docstrings (>=1.4.0)", "flake8-future-annotations", "flake8-mock-x2", "flake8-no-u-prefixed-strings (>=0.2)", "flake8-print (>=5.0.0)", "flake8-quotes (>=3.3.2)", "flake8-raise", "flake8-string-format", "flake8-tuple (>=0.4.1)", "pep8-naming (==0.13.3)", "pep8-naming (>=0.14.0)", "pydocstyle (>=6.3.0)"]
+google = ["google (>=1.7)"]
+graphviz = ["pydot (>=1.4.1)"]
+hacking = ["hacking", "importlib-metadata (<5.0.0)"]
+html = ["beautifulsoup4 (>=4.7.1)"]
+http = ["fake-useragent (>=1.4.0)"]
+isbn = ["python-stdnum (>=1.19)"]
+memento = ["memento-client (==0.6.1)"]
+mwoauth = ["mwoauth (>=0.2.4,!=0.3.1)"]
+mysql = ["PyMySQL (>=1.0.0)"]
+scripts = ["isbnlib", "memento-client (==0.6.1)", "unidecode"]
+tkinter = ["Pillow (>=8.1.2,!=10.0,!=10.1)"]
+weblinkchecker-py = ["memento-client (==0.6.1)"]
+wikitextparser = ["wikitextparser (>=0.47.0)"]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.1"
+description = "YAML parser and emitter for Python"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
+ {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
+ {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
+ {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
+ {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
+ {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
+ {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+ {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
+ {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
+ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
+ {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
+ {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
+ {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
+ {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
+ {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
+ {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
+ {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
+ {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
+ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
+]
+
+[[package]]
+name = "requests"
+version = "2.32.3"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
+ {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "rsa"
+version = "4.9"
+description = "Pure-Python RSA implementation"
+optional = false
+python-versions = ">=3.6,<4"
+files = [
+ {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
+ {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
+]
+
+[package.dependencies]
+pyasn1 = ">=0.1.3"
+
+[[package]]
+name = "ruff"
+version = "0.5.5"
+description = "An extremely fast Python linter and code formatter, written in Rust."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "ruff-0.5.5-py3-none-linux_armv6l.whl", hash = "sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf"},
+ {file = "ruff-0.5.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8"},
+ {file = "ruff-0.5.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3"},
+ {file = "ruff-0.5.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16"},
+ {file = "ruff-0.5.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b"},
+ {file = "ruff-0.5.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e"},
+ {file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0"},
+ {file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7"},
+ {file = "ruff-0.5.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884"},
+ {file = "ruff-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091"},
+ {file = "ruff-0.5.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523"},
+ {file = "ruff-0.5.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f"},
+ {file = "ruff-0.5.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8"},
+ {file = "ruff-0.5.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab"},
+ {file = "ruff-0.5.5-py3-none-win32.whl", hash = "sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d"},
+ {file = "ruff-0.5.5-py3-none-win_amd64.whl", hash = "sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445"},
+ {file = "ruff-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6"},
+ {file = "ruff-0.5.5.tar.gz", hash = "sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a"},
+]
+
+[[package]]
+name = "tqdm"
+version = "4.66.4"
+description = "Fast, Extensible Progress Meter"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"},
+ {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[package.extras]
+dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"]
+notebook = ["ipywidgets (>=6)"]
+slack = ["slack-sdk"]
+telegram = ["requests"]
+
+[[package]]
+name = "typing-extensions"
+version = "4.12.2"
+description = "Backported and Experimental Type Hints for Python 3.8+"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
+ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
+]
+
+[[package]]
+name = "uritemplate"
+version = "4.1.1"
+description = "Implementation of RFC 6570 URI Templates"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
+ {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.2.2"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
+ {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "virtualenv"
+version = "20.26.3"
+description = "Virtual Python Environment builder"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"},
+ {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"},
+]
+
+[package.dependencies]
+distlib = ">=0.3.7,<1"
+filelock = ">=3.12.2,<4"
+platformdirs = ">=3.9.1,<5"
+
+[package.extras]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
+
+[[package]]
+name = "yarl"
+version = "1.9.4"
+description = "Yet another URL library"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"},
+ {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"},
+ {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"},
+ {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"},
+ {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"},
+ {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"},
+ {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"},
+ {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"},
+ {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"},
+ {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"},
+ {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"},
+ {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"},
+ {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"},
+ {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"},
+ {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"},
+ {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"},
+ {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"},
+ {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"},
+ {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"},
+ {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"},
+ {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"},
+ {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"},
+ {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"},
+ {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"},
+ {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"},
+ {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"},
+ {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"},
+ {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"},
+ {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"},
+ {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"},
+ {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"},
+ {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"},
+ {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"},
+ {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"},
+ {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"},
+ {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"},
+ {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"},
+ {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"},
+ {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"},
+ {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"},
+ {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"},
+ {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"},
+ {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"},
+ {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"},
+ {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"},
+ {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"},
+ {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"},
+ {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"},
+ {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"},
+ {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"},
+ {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"},
+ {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"},
+ {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"},
+ {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"},
+ {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"},
+ {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"},
+ {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"},
+ {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"},
+ {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"},
+ {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"},
+ {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"},
+ {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"},
+ {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"},
+ {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"},
+ {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"},
+ {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"},
+ {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"},
+ {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"},
+ {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"},
+ {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"},
+ {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"},
+ {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"},
+ {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"},
+ {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"},
+ {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"},
+ {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"},
+ {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"},
+ {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"},
+ {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"},
+ {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"},
+ {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"},
+ {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"},
+ {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"},
+ {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"},
+ {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"},
+ {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"},
+ {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"},
+ {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"},
+ {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"},
+ {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"},
+]
+
+[package.dependencies]
+idna = ">=2.0"
+multidict = ">=4.0"
+
+[metadata]
+lock-version = "2.0"
+python-versions = "3.12.*"
+content-hash = "3b141b3c8a6adfe72d8a7ce875e4cc6ca6cf4a9a1053a7ecc6028b70053d8ff3"
diff --git a/pure-pulsars/pyproject.toml b/pure-pulsars/pyproject.toml
new file mode 100644
index 0000000..91d2b90
--- /dev/null
+++ b/pure-pulsars/pyproject.toml
@@ -0,0 +1,93 @@
+[tool.ruff]
+# Increase the line length. This breaks PEP8 but it is way easier to work with.
+# The original reason for this limit was a standard vim terminal is only 79 characters,
+# but this doesn't really apply anymore.
+line-length = 119
+# Target Python 3.12. If you decide to use a different version of Python
+# you will need to update this value.
+target-version = "py312"
+# Automatically fix auto-fixable issues.
+fix = true
+# The directory containing the source code. If you choose a different project layout
+# you will need to update this value.
+src = ["src"]
+
+[tool.ruff.lint]
+# Enable all linting rules.
+select = ["ALL"]
+# Ignore some of the most obnoxious linting errors.
+ignore = [
+ # Missing docstrings.
+ "D100",
+ "D104",
+ "D105",
+ "D106",
+ "D107",
+ # Docstring whitespace.
+ "D203",
+ "D213",
+ # Docstring punctuation.
+ "D415",
+ # Docstring quotes.
+ "D301",
+ # Builtins.
+ "A",
+ # Print statements.
+ "T20",
+ # TODOs.
+ "TD002",
+ "TD003",
+ "FIX",
+ # Annotations.
+ "ANN101",
+ "ANN102",
+ # Additional codes.
+ "COM812", # missing-trailing-comma
+ "COM819", # prohibited-trailing-comma
+ "D206", # indent-with-spaces
+ "D300", # triple-single-quotes
+ "E111", # indentation-with-invalid-multiple
+ "E114", # indentation-with-invalid-multiple-comment
+ "E117", # over-indented
+ "E501", # line-too-long
+ "INP001", # implicit-namespace-package
+ "ISC001", # single-line-implicit-string-concatenation
+ "ISC002", # multi-line-implicit-string-concatenation
+ "Q000", # bad-quotes-inline-string
+ "Q001", # bad-quotes-multiline-string
+ "Q002", # bad-quotes-docstring
+ "Q003", # avoidable-escaped-quote
+ "W191", # tab-indentation
+]
+
+[tool.poetry]
+name = "wikiwabbit"
+version = "0.1.0"
+description = "A cool discord bot that you can play games with. Centered around the Wikipedia site as its back bone"
+authors = ["Danny "]
+license = "MIT"
+readme = "README.md"
+
+[tool.poetry.dependencies]
+python = "3.12.*"
+humanize = "~4.10.0"
+aiohttp = "~3.9.5"
+pywikibot = "~9.2.1"
+"discord.py" = "~2.4.0"
+firebase-admin = "~6.5.0"
+python-dotenv = "~1.0.1"
+google-generativeai = "^0.7.2"
+
+[tool.poetry.group.dev.dependencies]
+ruff = "~0.5.0"
+pre-commit = "~3.7.1"
+deptry = "^0.17.0"
+
+
+[tool.poetry.group.test.dependencies]
+pytest = "^8.3.1"
+pytest-asyncio = "^0.23.8"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/pure-pulsars/requirements-dev.txt b/pure-pulsars/requirements-dev.txt
new file mode 100644
index 0000000..0a9011b
--- /dev/null
+++ b/pure-pulsars/requirements-dev.txt
@@ -0,0 +1,8 @@
+# This file contains all the development requirements for our linting toolchain.
+# Don't forget to pin your dependencies!
+# This list will have to be migrated if you wish to use another dependency manager.
+
+ruff~=0.5.0
+pre-commit~=3.7.1
+pytest~=8.3.1
+pytest-asyncio~=0.23.8
diff --git a/pure-pulsars/requirements.txt b/pure-pulsars/requirements.txt
new file mode 100644
index 0000000..58b46fa
--- /dev/null
+++ b/pure-pulsars/requirements.txt
@@ -0,0 +1,56 @@
+aiohttp==3.9.5
+aiosignal==1.3.1
+annotated-types==0.7.0
+appdirs==1.4.4
+attrs==23.2.0
+CacheControl==0.14.0
+cachetools==5.4.0
+certifi==2024.7.4
+cffi==1.16.0
+charset-normalizer==3.3.2
+cryptography==43.0.0
+discord.py==2.4.0
+firebase-admin==6.5.0
+flexcache==0.3
+flexparser==0.3.1
+frozenlist==1.4.1
+google-ai-generativelanguage==0.6.6
+google-api-core==2.19.1
+google-api-python-client==2.138.0
+google-auth==2.32.0
+google-auth-httplib2==0.2.0
+google-cloud-core==2.4.1
+google-cloud-firestore==2.17.0
+google-cloud-storage==2.18.0
+google-crc32c==1.5.0
+google-generativeai==0.7.2
+google-resumable-media==2.7.1
+googleapis-common-protos==1.63.2
+grpcio==1.65.1
+grpcio-status==1.62.2
+httplib2==0.22.0
+humanize==4.10.0
+idna==3.7
+msgpack==1.0.8
+multidict==6.0.5
+mwparserfromhell==0.6.6
+packaging==24.1
+Pint==0.24.3
+proto-plus==1.24.0
+protobuf==4.25.4
+pyasn1==0.6.0
+pyasn1_modules==0.4.0
+pycparser==2.22
+pydantic==2.8.2
+pydantic_core==2.20.1
+PyJWT==2.8.0
+pyparsing==3.1.2
+python-dotenv==1.0.1
+pywikibot==9.3.0
+requests==2.32.3
+rsa==4.9
+tqdm==4.66.4
+typing_extensions==4.12.2
+uritemplate==4.1.1
+urllib3==2.2.2
+yarl==1.9.4
diff --git a/pure-pulsars/src/__init__.py b/pure-pulsars/src/__init__.py
new file mode 100644
index 0000000..013c625
--- /dev/null
+++ b/pure-pulsars/src/__init__.py
@@ -0,0 +1 @@
+# For testing purposes.
diff --git a/pure-pulsars/src/button_class.py b/pure-pulsars/src/button_class.py
new file mode 100644
index 0000000..5914344
--- /dev/null
+++ b/pure-pulsars/src/button_class.py
@@ -0,0 +1,438 @@
+"""Class(es) for managing wiki-guesser logic."""
+
+import logging
+import secrets
+from abc import ABC, abstractmethod
+from dataclasses import dataclass
+
+import discord
+from discord import ButtonStyle, Enum, NotFound
+from discord.app_commands.errors import CommandInvokeError
+from discord.utils import MISSING
+from pint import UnitRegistry
+from pywikibot import Page
+from pywikibot.exceptions import InvalidTitleError
+
+from wikiutils import loss_update, make_embed, search_wikipedia
+
+ACCURACY_THRESHOLD = 0.8
+MAX_LEN = 1990
+UREG = UnitRegistry()
+
+
+class GameType(Enum):
+ """Possible games to be played."""
+
+ wikiguesser = 0
+ wikianimal = 1
+
+
+class _Ranked(Enum):
+ YES = 1
+ NO = 0
+
+
+class WinLossManagement(ABC):
+ """Class that contains abstract methods that define what happens upon winning and what happens upon losing."""
+
+ def __init__(self, winargs: dict, lossargs: dict) -> None:
+ """Initialize the WinLossManagement class.
+
+ Args:
+ ----
+ winargs (dict): Arguments for winning.
+ lossargs (dict): Arguments for losing.
+
+ """
+ super().__init__()
+ self.winargs = winargs
+ self.lossargs = lossargs
+
+ @abstractmethod
+ async def on_win(self) -> None:
+ """Clean up on win."""
+
+ @abstractmethod
+ async def on_loss(self) -> None:
+ """Clean up on loss."""
+
+
+@dataclass
+class _Button:
+ """NamedTuple for button information."""
+
+ style: ButtonStyle = ButtonStyle.secondary
+ label: str | None = None
+ disabled: bool = False
+ custom_id: str | None = None
+ url: str | None = None
+ emoji: str | discord.Emoji | discord.PartialEmoji | None = None
+ row: int | None = None
+ sku_id: int | None = None
+ links: list[str] | None = None
+ owners: list[discord.User] | None = None
+ message: str | None = None
+ private: bool | None = None
+ score: list[int] | None = None
+ winlossmanager: WinLossManagement | None = None
+ ranked: bool = False
+ article: Page = None
+ user: int = 0
+ game_type: GameType = GameType.wikiguesser
+ animal_info: dict | None = None
+ view: discord.ui.View | None = None
+
+
+class GiveUpButton(discord.ui.Button):
+ """Button for exiting/"giving up" on game."""
+
+ _end_message: str = "Thank you for trying!"
+
+ def __init__(self, *, info: _Button, view: discord.ui.View) -> None:
+ """Initialize the GiveUpButton class.
+
+ Args:
+ ----
+ info (_Button): Information about the button.
+ view (discord.ui.View): The view to clean up.
+
+ """
+ super().__init__(
+ style=info.style,
+ label=info.label,
+ disabled=info.disabled,
+ custom_id=info.custom_id,
+ url=info.url,
+ emoji=info.emoji,
+ row=info.row,
+ sku_id=info.sku_id,
+ )
+
+ self.ranked = info.ranked
+ self.article = info.article
+ self._view = view
+
+ async def callback(self, interaction: discord.Interaction) -> None:
+ """Exit the game."""
+ logging.debug("GiveUpButton handling exit for %s.", interaction)
+ msg = self._end_message
+ article = self.article
+ embed = await make_embed(article)
+ embed.set_footer(text=msg)
+ if self.ranked:
+ for i in [interaction.guild_id, 0]:
+ await loss_update(i, user=interaction.user)
+ try:
+ await interaction.response.send_message(embed=embed, ephemeral=self.ranked)
+ await self.clean_view(view=self._view)
+ await interaction.message.edit(content=interaction.message.content, view=self._view)
+ except NotFound as e:
+ logging.info("button_class:\nFunc: GiveUpButton\nException %s", e)
+ except CommandInvokeError as e:
+ logging.info("button_class:\nFunc: GiveUpButton\nException %s", e)
+
+ @staticmethod
+ async def clean_view(*, view: discord.ui.View) -> None:
+ """Clean a view of interactible things (e.g., buttons).
+
+ This method is only static because it's likely useful elsewhere.
+ """
+ logging.debug("Clearing child objects: %s", list(view.children))
+
+ view.clear_items()
+
+
+class ExcerptButton(discord.ui.Button):
+ """Button for revealing more of the summary."""
+
+ def __init__(
+ self,
+ *,
+ info: _Button,
+ summary: str,
+ ) -> None:
+ """Initialize the ExcerptButton class.
+
+ Args:
+ ----
+ info (_Button): Information about the button.
+ summary (str): The summary to reveal.
+
+ """
+ super().__init__(
+ style=info.style,
+ label=info.label,
+ disabled=info.disabled,
+ custom_id=info.custom_id,
+ url=info.url,
+ emoji=info.emoji,
+ row=info.row,
+ sku_id=info.sku_id,
+ )
+ self.summary = summary
+ self.score = info.score
+ self.ind = 1
+ self.owners = info.owners
+ self.private = info.private
+
+ async def callback(self, interaction: discord.Interaction) -> None:
+ """Reveal more of the summary."""
+ try:
+ await interaction.response.defer()
+
+ if interaction.user not in self.owners:
+ await interaction.response.send_message("You may not interact with this", ephemeral=True)
+ return
+ self.ind += 1
+ self.score[0] -= (len("".join(self.summary[: self.ind])) - len("".join(self.summary[: self.ind - 1]))) // 2
+
+ if self.summary[: self.ind] == self.summary or len(".".join(self.summary[: self.ind + 1])) > MAX_LEN:
+ self.view.remove_item(self)
+ await interaction.edit_original_response(
+ content=f"Excerpt: {".".join(self.summary[:self.ind])}.", view=self.view
+ )
+ except NotFound as e:
+ logging.info("button_class:\nFunc: ExcerptButton\nException %s", e)
+ except CommandInvokeError as e:
+ logging.info("button_class:\nFunc: ExcerptButton\nException %s", e)
+
+
+class GuessButton(discord.ui.Button):
+ """Button to open guess modal."""
+
+ def __init__(self, *, info: _Button) -> None:
+ """Initialize the GuessButton class.
+
+ Args:
+ ----
+ info (_Button): Information about the button.
+
+ """
+ super().__init__(
+ style=info.style,
+ label=info.label,
+ disabled=info.disabled,
+ custom_id=info.custom_id,
+ url=info.url,
+ emoji=info.emoji,
+ row=info.row,
+ sku_id=info.sku_id,
+ )
+ self.info = info
+ self.ranked = info.ranked
+ self.article = info.article
+ self.score = info.score
+ self.user = info.user
+ self.owners = info.owners
+ self.winlossmanager = info.winlossmanager
+
+ async def callback(self, interaction: discord.Interaction) -> None:
+ """Open guess modal."""
+ try:
+ if interaction.user not in self.owners:
+ await interaction.response.send_message("You may not interact with this", ephemeral=True)
+ return
+ self.guess_modal = GuessInput(
+ title="Guess!",
+ info=self.info,
+ )
+ self.guess_modal.add_item(discord.ui.TextInput(label="Your guess", placeholder="Enter your guess here..."))
+ await interaction.response.send_modal(self.guess_modal)
+ except NotFound as e:
+ logging.info("button_class:\nFunc: GuessButton\nException %s", e)
+ except CommandInvokeError as e:
+ logging.info("button_class:\nFunc: GuessButton\nException %s", e)
+
+
+class GuessInput(discord.ui.Modal):
+ """Input feild for guessing."""
+
+ def __init__(
+ self,
+ *,
+ title: str = MISSING,
+ timeout: float | None = None,
+ custom_id: str = MISSING,
+ info: _Button,
+ ) -> None:
+ """Initialize the GuessInput class.
+
+ This class manages displaying a modal for guessing the article, and
+ retrieving input from the user.
+
+ Args:
+ ----
+ title (str): The title of the modal.
+ timeout (float | None): The time until the modal times out.
+ custom_id (str): The custom ID of the modal.
+ info (_Button): Information about the button.
+
+ """
+ super().__init__(title=title, timeout=timeout, custom_id=custom_id)
+ self.info = info
+ self.ranked = info.ranked
+ self.score = info.score
+ self.user = info.user
+ self.article = info.article
+ self.game_type = info.game_type
+ self.winlossmanager = info.winlossmanager
+
+ async def on_submit(self, interaction: discord.Interaction) -> None:
+ """Guess the article."""
+ user_input = self.children[0].value
+ try:
+ if self.game_type == GameType.wikiguesser:
+ await wikiguesser_on_submit(self.info, interaction, user_input)
+ elif self.game_type == GameType.wikianimal:
+ await wikianimal_on_submit(self.info, interaction, user_input)
+ except NotFound as e:
+ logging.info("button_class:\nFunc: GuessInput\nException %s", e)
+ except CommandInvokeError as e:
+ logging.info("button_class:\nFunc: GuessInput\nException %s", e)
+
+
+async def wikiguesser_on_submit(info: _Button, interaction: discord.Interaction, user_guess: str) -> None:
+ """Guess the article.
+
+ Args:
+ ----
+ info (_Button): Information about the button.
+ interaction (discord.Interaction): The interaction object.
+ user_guess (str): The user's guess.
+
+ """
+ await interaction.response.defer()
+ page = await search_wikipedia(user_guess)
+ try:
+ if page.title() == info.article.title():
+ await info.winlossmanager.on_win(interaction=interaction)
+ info.view.clear_items()
+ await interaction.message.edit(view=info.view)
+ return
+ except InvalidTitleError:
+ await interaction.followup.send(
+ content="Sorry, the article title was not valid.",
+ ephemeral=True,
+ )
+ except AttributeError:
+ await interaction.followup.send(
+ content="Sorry, an error with that article occured, please try a different one.",
+ ephemeral=True,
+ )
+ await info.winlossmanager.on_loss()
+ info.score[0] -= 5
+
+
+async def wikianimal_on_submit(info: _Button, interaction: discord.Interaction, user_guess: str) -> None:
+ """Handle the user's guess for the animal game.
+
+ Args:
+ ----
+ info (_Button): Information about the button.
+ interaction (discord.Interaction): The interaction object.
+ user_guess (str): The user's guess.
+
+ """
+
+ async def win() -> None:
+ article = info.article
+ embed = await make_embed(article)
+ msg = f"Congratulations {interaction.user.mention}! You guessed correctly!"
+ await interaction.response.send_message("Crikey! You're a winner!", ephemeral=True)
+ await interaction.followup.send(content=msg, embed=embed)
+ info.view.clear_items()
+ await interaction.message.edit(view=info.view)
+
+ async def lose() -> None:
+ await interaction.response.send_message("Guess again! Remember to use units like kg!", ephemeral=True)
+ await interaction.followup.send("Sorry, try again.", ephemeral=True)
+
+ guess_weight = UREG.Quantity(user_guess)
+
+ if guess_weight.dimensionless:
+ await lose()
+ return
+
+ animal_weight_range = info.animal_info.get("weight_ranges")
+
+ preferred_units = animal_weight_range[0].units
+ guess_weight.ito(preferred_units)
+ if len(animal_weight_range) > 1 and animal_weight_range[0].units != animal_weight_range[1].units:
+ animal_weight_range.pop()
+
+ if len(animal_weight_range) == 1:
+ if guess_weight.magnitude <= animal_weight_range[0].magnitude:
+ await win()
+ else:
+ await lose()
+ elif (
+ guess_weight.magnitude >= animal_weight_range[0].magnitude
+ and guess_weight.magnitude <= animal_weight_range[1].magnitude
+ ):
+ await win()
+ else:
+ await lose()
+
+
+class LinkListButton(discord.ui.Button):
+ """Button for showing more links from the list."""
+
+ def __init__(self, *, info: _Button) -> None:
+ """Initialize the LinkListButton class.
+
+ Args:
+ ----
+ info (_Button): Information about the button.
+
+ """
+ super().__init__(
+ style=info.style,
+ label=info.label,
+ disabled=info.disabled,
+ custom_id=info.custom_id,
+ url=info.url,
+ emoji=info.emoji,
+ row=info.row,
+ sku_id=info.sku_id,
+ )
+ self.score = info.score
+ self.message = info.message
+ self.links = info.links
+ self.owners = info.owners
+ self.private = info.private
+
+ async def callback(self, interaction: discord.Interaction) -> None:
+ """Show 10 diffrent links."""
+ try:
+ if interaction.user not in self.owners:
+ await interaction.response.send_message("You may not interact with this", ephemeral=True)
+ return
+
+ if not self.links:
+ self.view.remove_item(self)
+ await interaction.message.edit(view=self.view)
+ await interaction.response.send_message("No more links!", ephemeral=True)
+ return
+
+ selected_links = []
+ self.score[0] -= 10
+ for _ in range(10):
+ selected_links.append(self.links.pop(secrets.randbelow(len(self.links))))
+ if len(self.links) == 1:
+ selected_links.append(self.links.pop(0))
+ break
+
+ await interaction.response.send_message(
+ content=f"{self.message}\n```{"\n".join(selected_links)}```",
+ view=self.view,
+ delete_after=180,
+ ephemeral=self.private,
+ )
+ if not interaction.message.content:
+ await interaction.message.delete()
+ return
+ await interaction.message.edit(view=None)
+ except NotFound as e:
+ logging.info("button_class:\nFunc: LinkListButton\nException %s", e)
+ except CommandInvokeError as e:
+ logging.info("button_class:\nFunc: LinkListButton\nException %s", e)
diff --git a/pure-pulsars/src/cmds/__init__.py b/pure-pulsars/src/cmds/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pure-pulsars/src/cmds/challenge.py b/pure-pulsars/src/cmds/challenge.py
new file mode 100644
index 0000000..8e27547
--- /dev/null
+++ b/pure-pulsars/src/cmds/challenge.py
@@ -0,0 +1,65 @@
+"""Challenge command for the Wiki Guesser game.
+
+This command allows users to challenge other users to a game of Wiki Guesser.
+
+
+CODE JAM NOTE:
+-------------
+It has not been implemented yet, and may not be implemented by the end of the
+game jam. The code is here for future reference.
+"""
+
+import asyncio
+
+import discord
+from discord import app_commands
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Command to challenge people to a game of wiki-guesser."""
+
+ @tree.command(name="challenge", description="Challenge someone to a game of wikiguesser!")
+ async def challenge(interaction: discord.Interaction, user: discord.User, points_to_win: int) -> None:
+ """Create Wiki Guesser command.
+
+ Args:
+ ----
+ interaction (discord.Interaction): The interaction object.
+ user (discord.User): The user to challenge.
+ points_to_win (int): The number of points required to win.
+
+
+ Note:
+ ----
+ This command will create a private thread between the two users, and the game will begin
+ in 10 seconds. The game will be played in the thread, and the thread will be deleted after
+ the game ends.
+
+ Warning:
+ -------
+ This command is not yet implemented.
+
+ """
+ if interaction.channel.type in [discord.ChannelType.public_thread, discord.ChannelType.private_thread]:
+ await interaction.response.send_message(
+ "You silly billy! This command won't work in a thread", ephemeral=True
+ )
+ return
+ thread = await interaction.channel.create_thread(
+ name=f"Wikiguesser between {user.name} and {interaction.user.name}",
+ type=discord.ChannelType.private_thread,
+ )
+ await interaction.response.send_message("Join the newly created thread to play, GAME BEGINS IN 10")
+ await thread.add_user(user)
+ await thread.add_user(interaction.user)
+ await countdown(interaction=interaction)
+ await thread.send(points_to_win)
+ await thread.delete()
+
+ async def countdown(interaction: discord.Interaction) -> None:
+ """Countdown to the start of the game."""
+ for i in range(9, -1, -1):
+ await interaction.edit_original_response(
+ content=f"Join the newly created thread to play, GAME BEGINS IN {i}"
+ )
+ await asyncio.sleep(1)
diff --git a/pure-pulsars/src/cmds/help_bot.py b/pure-pulsars/src/cmds/help_bot.py
new file mode 100644
index 0000000..0b9ff03
--- /dev/null
+++ b/pure-pulsars/src/cmds/help_bot.py
@@ -0,0 +1,20 @@
+"""Help command for the bot."""
+
+import discord
+from discord import app_commands
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Create a help command."""
+
+ @tree.command(
+ name="help",
+ description="Responds with help",
+ )
+ async def help_me(interaction: discord.Interaction) -> None:
+ """Display help text from commands available in the bot comamnd tree."""
+ resp = "```"
+ for i in tree.get_commands():
+ resp += f"{i.name} : {i.description}\n"
+ resp += "```"
+ await interaction.response.send_message(content=resp, ephemeral=True)
diff --git a/pure-pulsars/src/cmds/leaderboard.py b/pure-pulsars/src/cmds/leaderboard.py
new file mode 100644
index 0000000..5c966bd
--- /dev/null
+++ b/pure-pulsars/src/cmds/leaderboard.py
@@ -0,0 +1,61 @@
+"""Leaderboard command and logic for the Wiki Guesser game."""
+
+import logging
+
+import discord
+from discord import Enum, app_commands
+from discord.app_commands.errors import CommandInvokeError
+
+from database.database_core import DATA
+
+
+class _GloSer(Enum):
+ Yes = 0
+ No = 1
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Create leaderboard command."""
+
+ def _sort_leaders(e: dict) -> int:
+ return e["score"]
+
+ @tree.command(
+ name="leaderboard",
+ description="Returns your guilds leaderboard",
+ )
+ @app_commands.describe(globe="Do you want the global leaderboard?")
+ async def leaderboard(interaction: discord.Interaction, globe: _GloSer = _GloSer.No) -> None:
+ """Display the leaderboard for the server or the entire world.
+
+ Args:
+ ----
+ interaction (discord.Interaction): The interaction object.
+ globe (_GloSer): Whether to show the global leaderboard or not.
+
+ Notes:
+ -----
+ Another command, ``/reset-scores``, can be used to reset the scores of
+ all users in a server.
+ :w
+
+ """
+ try:
+ ser_id = interaction.guild_id if bool(globe.value) else 0
+ await interaction.response.defer(thinking=True)
+ board = await DATA.get_server(ser_id)
+ lead = list(board.values())
+ lead.sort(key=_sort_leaders, reverse=True)
+ lead = lead[0:10]
+ embed = discord.Embed(
+ title=f"Wikiguesser leaderboard for {interaction.guild.name if ser_id != 0 else "THE ENTIRE WORLD"}",
+ )
+ names = [f"{idx+1}. {uid["name"]} /// {uid["score"]}" for idx, uid in enumerate(lead)]
+ embed.add_field(
+ name="Users /// Score",
+ value=f"{"\n".join(names)}\n",
+ )
+
+ await interaction.followup.send(embed=embed)
+ except CommandInvokeError as e:
+ logging.info("Leaderboard:\nException: %s", e)
diff --git a/pure-pulsars/src/cmds/never.py b/pure-pulsars/src/cmds/never.py
new file mode 100644
index 0000000..df80ec5
--- /dev/null
+++ b/pure-pulsars/src/cmds/never.py
@@ -0,0 +1,37 @@
+"""'Never' command absolutely not meant to be used ever."""
+
+import asyncio
+
+import discord
+from discord import app_commands
+
+from wikiutils import make_embed, search_wikipedia
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Create a never command."""
+
+ @tree.command(
+ name="never",
+ description="Who knows what this does.",
+ )
+ async def never_gonna_give_you_up(interaction: discord.Interaction, user: discord.User) -> None:
+ """Run the never command.
+
+ Args:
+ ----
+ interaction (discord.Interaction): The interaction object.
+ user (discord.User): The user to challenge.
+
+ """
+ await interaction.response.send_message(
+ f"Starting deathmatch between {user.mention} and {interaction.user.mention}..."
+ )
+ article = await search_wikipedia("Rickrolling")
+ await asyncio.sleep(1)
+ embed = await make_embed(article)
+ embed.description = (
+ f"{article.extract(chars=400)}...([read more](https://www.youtube.com/watch?v=dQw4w9WgXcQ))"
+ )
+ embed.set_image(url="https://upload.wikimedia.org/wikipedia/en/f/f7/RickRoll.png")
+ await interaction.followup.send(embed=embed)
diff --git a/pure-pulsars/src/cmds/rabbit_hole.py b/pure-pulsars/src/cmds/rabbit_hole.py
new file mode 100644
index 0000000..23a44a6
--- /dev/null
+++ b/pure-pulsars/src/cmds/rabbit_hole.py
@@ -0,0 +1,156 @@
+"""Logic for the rabbit hole command.
+
+This command interacts with the Wikipedia API and the Google Gemini API to
+curate a Wikipedia experience, getting lost down a "rabbit hole" of information.
+"""
+
+import json
+import logging
+import os
+import random
+
+import discord
+import google.generativeai as genai
+from discord import Embed, app_commands
+from discord.app_commands.errors import CommandInvokeError
+from discord.errors import NotFound
+from google.api_core.exceptions import ResourceExhausted
+from pywikibot import Page
+
+from wikiutils import rand_wiki
+
+sys_ins = """Objective: Summarize a Wikipedia article in a concise and informative manner, retaining key details and ensuring readability. Do not return any commentary or anything else, except the requested summary.
+
+Instructions:
+- Intro: Write a brief summary of the article's intro. This should provide an overview of the most important points in 2-3 sentences.
+- Sections: Identify and summarize the main sections of the article. Each section should have its own heading and a 1-2 sentence summary. Include 4-6 sections that cover the most significant aspects of the topic.
+- Categories: Provide adjacent categories for further exploration. This should include 3 categories and their respective Wikipedia URLs that are relevant to the topic (but not about the topic itself).
+
+Output the summary in the following JSON format:
+{
+ "Intro": "Brief summary of the article's intro.",
+ "Sections": {
+ "Section Name": ["Sentence 1", "Sentence 2"],
+ "Section Name": ["Sentence 1", "Sentence 2"],
+ },
+ "Categories": [
+ {"Name": "Category 1", "URL": "Wikipedia URL"},
+ {"Name": "Category 2", "URL": "Wikipedia URL"},
+ {"Name": "Category 3", "URL": "Wikipedia URL"},
+ ],
+}
+
+Do not format the message with ```json```, as it is not neccessary. Return the JSON object as a string.
+"""
+
+genai.configure(api_key=os.environ["GEMINI_API_KEY"])
+model = genai.GenerativeModel("gemini-1.5-flash", system_instruction=sys_ins)
+
+
+def make_embed(summary: dict) -> Embed:
+ """Create an embed message for Wikipedia.
+
+ Args:
+ ----
+ summary (dict): The summary of the Wikipedia article.
+
+ Returns:
+ -------
+ discord.Embed: The embed message.
+
+ """
+ embed_msg = Embed(title=summary["Title"], description=summary["Intro"], url=summary["URL"], color=0xFFFFFF)
+ for section, sentences in summary["Sections"].items():
+ embed_msg.add_field(name=section, value="\n".join([f"- {sentence}" for sentence in sentences]), inline=False)
+ embed_msg.set_image(url=summary["Image"])
+ embed_msg.set_footer(text="To explore more Wikipedia topics, click one of the buttons below.")
+ return embed_msg
+
+
+class WikiButtons(discord.ui.View):
+ """Buttons for exploring more Wikipedia articles."""
+
+ def __init__(self, pages: list[Page]) -> None:
+ """Initialize the WikiButtons class.
+
+ Args:
+ ----
+ pages (list[Page]): The Wikipedia pages to explore.
+
+ """
+ super().__init__(timeout=None)
+ self.pages = pages
+ self.create_buttons()
+
+ def create_buttons(self) -> None:
+ """Create buttons for each Wikipedia article."""
+
+ def create_callback(page: Page) -> discord.ui.Button.callback:
+ async def button_callback(interaction: discord.Interaction) -> None:
+ await interaction.response.defer(thinking=True, ephemeral=True)
+ await rabbit_hole_helper(interaction, page)
+
+ return button_callback
+
+ for page in self.pages:
+ button = discord.ui.Button(label=page.title(), style=discord.ButtonStyle.green)
+ button.callback = create_callback(page)
+ self.add_item(button)
+
+
+async def rabbit_hole_helper(interaction: discord.Interaction, article: Page) -> None:
+ """Functions to help the rabbit hole."""
+ try:
+ # Generate a summary and return an embed message
+ text = article.extract(intro=False)
+ response = model.generate_content(text)
+ summary = json.loads(response.text)
+
+ # Add additional information to the summary
+ summary["Title"] = article.title()
+ summary["URL"] = article.full_url()
+ image_url = article.page_image()
+ try:
+ image_url = image_url.latest_file_info.url
+ except AttributeError:
+ image_url = None
+ summary["Image"] = image_url
+
+ # Create an embed message with the summary
+ embed = make_embed(summary)
+
+ # Get 3 random Wikipedia pages from the article
+ related_pages = random.sample(tuple(article.linkedPages()), 3)
+
+ await interaction.followup.send(embed=embed, view=WikiButtons(related_pages))
+ except json.JSONDecodeError:
+ logging.info("Failed to decode JSON response: %s", response.text)
+ await interaction.followup.send("Failed to generate a summary. Please try again.")
+ except ResourceExhausted:
+ await interaction.followup.send("Your API key is rate limited. Please try again later.")
+ except NotFound as e:
+ logging.info("Rabbit Hole:\nFunc: rabbit_hole_helper\nException %s", e)
+ except CommandInvokeError as e:
+ logging.info("Rabbit Hole:\nFunc: rabbit_hole_helper\nException %s", e)
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Command to create a Wiki rabbit hole."""
+
+ @tree.command(
+ name="rabbit-hole",
+ description="Dive into wiki-knowledge with Rabbit Hole — information overload through random exploration.",
+ )
+ async def rabbit_hole(interaction: discord.Interaction) -> None:
+ await interaction.response.defer(thinking=True, ephemeral=True)
+
+ try:
+ # Get a random Wikipedia article
+ article = await rand_wiki()
+ except NotFound as e:
+ logging.info("Rabbit Hole:\nFunc: rabbit_hole\nException %s", e)
+ except CommandInvokeError as e:
+ logging.info("Rabbit Hole:\nFunc: rabbit_hole\nException %s", e)
+
+ # Generate a summary, and return an embed message
+ await rabbit_hole_helper(interaction, article)
diff --git a/pure-pulsars/src/cmds/reset_scores.py b/pure-pulsars/src/cmds/reset_scores.py
new file mode 100644
index 0000000..2377c88
--- /dev/null
+++ b/pure-pulsars/src/cmds/reset_scores.py
@@ -0,0 +1,39 @@
+"""Reset scores of all users in this guild for this guild."""
+
+import discord
+from discord import app_commands
+
+from database.database_core import DATA
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Create Wiki Guesser reset-scores command."""
+
+ @tree.command(
+ name="reset-scores",
+ description="Reset scores of all users in this guild for this guild",
+ )
+ async def reset_scores(interaction: discord.Interaction) -> None:
+ """Reset the scores of all users in a server.
+
+ Args:
+ ----
+ interaction (discord.Interaction): The interaction object.
+
+ Notes:
+ -----
+ This command requires the user to have **MANAGE SERVER** (or 'manage_guild') permissions.
+
+ This command will reset the scores of all users in the server to 0.
+
+ """
+ await interaction.response.defer(thinking=True)
+ if interaction.channel.permissions_for(interaction.user).manage_guild:
+ data = await DATA.get_server(interaction.guild_id)
+ for uid in data:
+ await DATA.update_value_for_user(interaction.guild_id, uid, 0, "score")
+ await interaction.followup.send("Scores Reset!", ephemeral=True)
+ else:
+ await interaction.followup.send(
+ "You must have **MANAGE SERVER** permissions to reset scores", ephemeral=True
+ )
diff --git a/pure-pulsars/src/cmds/sync.py b/pure-pulsars/src/cmds/sync.py
new file mode 100644
index 0000000..0180b92
--- /dev/null
+++ b/pure-pulsars/src/cmds/sync.py
@@ -0,0 +1,35 @@
+"""Sync the command tree for the bot."""
+
+import logging
+
+import discord
+from discord import app_commands
+from discord.app_commands.errors import CommandInvokeError
+from discord.errors import NotFound
+
+
+def main(bot: app_commands.CommandTree) -> None:
+ """Command to sync the tree."""
+
+ @bot.command(
+ name="sync",
+ description="Syncs the command tree",
+ )
+ async def sync(inter: discord.Interaction) -> None:
+ """Command to sync the tree.
+
+ It will sync the command tree to the guild it is called in.
+ """
+ try:
+ guild = inter.guild
+ try:
+ await bot.sync(guild=guild)
+ except discord.HTTPException as e:
+ logging.info("Tried to sync: %s", e)
+ msg = f"Synced the tree to {guild.name}"
+ logging.info(msg)
+ await inter.response.send_message(content=msg, ephemeral=True)
+ except NotFound as e:
+ logging.info("Failed to sync: %s", e)
+ except CommandInvokeError as e:
+ logging.info("Failed to sync: %s", e)
diff --git a/pure-pulsars/src/cmds/user_info.py b/pure-pulsars/src/cmds/user_info.py
new file mode 100644
index 0000000..541f2fa
--- /dev/null
+++ b/pure-pulsars/src/cmds/user_info.py
@@ -0,0 +1,82 @@
+"""User info command for the Wiki Guesser game."""
+
+import logging
+from datetime import UTC, datetime
+
+import discord
+import humanize
+from discord import app_commands
+from discord.app_commands.errors import CommandInvokeError
+from discord.errors import NotFound
+
+from database.database_core import DATA, NullUserError
+
+SKIP = ["userid", "last_played"]
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Create user-info command."""
+
+ @tree.command(
+ name="user-info",
+ description="Returns your stats",
+ )
+ @app_commands.describe(user="Who are you asking about, leave blank for self?")
+ async def user_info(interaction: discord.Interaction, user: discord.User = None) -> None:
+ """Get game info for a specific user from the scores database.
+
+ Args:
+ ----
+ interaction (discord.Interaction): The interaction object.
+ user (discord.User): The user to get the game info for.
+
+ Notes:
+ -----
+ If no user is provided, the command will default to the user who invoked the command.
+
+ """
+ try:
+ await interaction.response.defer(thinking=True, ephemeral=True)
+ embed = discord.embeds.Embed()
+ try:
+ if user is None:
+ user = interaction.user
+ user_data = await DATA.get_user(interaction.guild_id, user.id)
+ img = user.display_avatar
+ embed.set_thumbnail(url=img.url)
+ embed.add_field(
+ name="Name",
+ value=user_data["name"],
+ inline=False,
+ )
+ embed.add_field(
+ name="Score",
+ value=user_data["score"],
+ inline=False,
+ )
+ try:
+ embed.add_field(
+ name="W/L",
+ value=(user_data["wins"] / user_data["failure"]),
+ inline=False,
+ )
+ except ZeroDivisionError:
+ embed.add_field(
+ name="W/L",
+ value=(user_data["wins"] / 1),
+ inline=False,
+ )
+ embed.add_field(
+ name="Last Played",
+ value=humanize.naturaltime(datetime.now(UTC).timestamp() - user_data["last_played"]),
+ inline=False,
+ )
+ except NullUserError:
+ await interaction.followup.send(content=f"User: {user.mention} has **not** played any games")
+ return
+
+ await interaction.followup.send(embed=embed, ephemeral=True)
+ except NotFound as e:
+ logging.info("Tried to get User-Info: %s", e)
+ except CommandInvokeError as e:
+ logging.info("Tried to get User-Info: %s", e)
diff --git a/pure-pulsars/src/cmds/wikianimal.py b/pure-pulsars/src/cmds/wikianimal.py
new file mode 100644
index 0000000..a4b4272
--- /dev/null
+++ b/pure-pulsars/src/cmds/wikianimal.py
@@ -0,0 +1,151 @@
+import logging
+import os
+import re
+
+import aiohttp
+import discord
+from discord import NotFound, app_commands
+from discord.app_commands.errors import CommandInvokeError
+from pint import UnitRegistry
+
+import button_class
+from button_class import GameType, GiveUpButton, GuessButton, _Button
+from wikiutils import UA, make_img_embed, search_wikipedia
+
+UREG = UnitRegistry()
+
+WEIGHT_PATTERN = re.compile(r"(\d+ ?(?:kg|tons|lbs|oz|g|pounds|grams))")
+
+
+class IncompatibleAnimalError(Exception):
+ """Error to be thrown if wikipedia animal and/or animal api fail to provide a valid weight range."""
+
+ def __init__(self) -> None:
+ super().__init__("No valid animal weights")
+
+
+class WinLossFunctions(button_class.WinLossManagement):
+ """The Basic Win Loss Function for WikiAnimal."""
+
+ def __init__(self, winargs: dict, lossargs: dict) -> None:
+ super().__init__(winargs, lossargs)
+
+ async def on_win(self) -> None:
+ """Clean up on win."""
+
+ async def on_loss(self) -> None:
+ """Clean up on loss."""
+
+
+async def find_animal_weight(animal_name: str) -> list[str]:
+ """Determine animal's weight ranges based on article text."""
+ api_url = f"https://api.api-ninjas.com/v1/animals?name={animal_name}"
+ async with (
+ aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=20)) as session,
+ session.get(api_url, headers={"X-Api-Key": os.environ["NINJA_API_KEY"]}) as response,
+ ):
+ if not response.ok:
+ logging.error("Error requesting animal data: %s", response.status)
+ raise IncompatibleAnimalError
+ animal_data = await response.json()
+ if animal_data:
+ weight_str = animal_data[0].get("characteristics").get("weight")
+ else:
+ raise IncompatibleAnimalError
+ if weight_str is None:
+ raise IncompatibleAnimalError
+ weight_str = weight_str.replace(",", "")
+ weight_phrases = re.findall(WEIGHT_PATTERN, weight_str)
+ if weight_phrases:
+ weight_ranges = [UREG.Quantity(weight) for weight in weight_phrases]
+ if len(weight_ranges) % 2 == 0:
+ return [weight_ranges[0], weight_ranges[1]]
+ return [weight_ranges[0]]
+ raise IncompatibleAnimalError
+
+
+async def find_new_animal(interaction: discord.Interaction) -> dict:
+ """Return random animal's information."""
+ try:
+ api_url = "https://en.wikipedia.org/wiki/Special:RandomInCategory?wpcategory=Mammals of the United States"
+ async with (
+ aiohttp.ClientSession(headers={"UserAgent": UA}, timeout=aiohttp.ClientTimeout(total=20)) as session,
+ session.get(api_url) as response,
+ ):
+ if response.ok:
+ loc = str(response.real_url)
+ title = loc.split("=")[1].split("&")[0]
+ article = await search_wikipedia(title)
+ animal_name = article.title().split(" ")[-1]
+ weight_ranges = await find_animal_weight(animal_name)
+ except IncompatibleAnimalError:
+ return await find_new_animal(interaction)
+ return {"name": animal_name, "article": article, "weight_ranges": weight_ranges}
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Create Wiki Animal command."""
+
+ @tree.command(
+ name="wiki-animal",
+ description="Starts a game of wiki-animal! Try and guess the animal's mass!",
+ )
+ async def wiki(interaction: discord.Interaction) -> None:
+ try:
+ ranked = False
+ owners = [*interaction.guild.members]
+ score = [1000]
+ await interaction.response.send_message(
+ content="Starting a game of Wiki Animal, one moment while we catch your animal."
+ )
+
+ animal_info = await find_new_animal(interaction)
+
+ article = animal_info.get("article")
+ hint_view = discord.ui.View()
+
+ animal_name = animal_info.get("name")
+
+ args = {"interaction": interaction, "ranked": ranked, "article": article, "scores": score}
+
+ guess_button = GuessButton(
+ info=_Button(
+ label="Guess!",
+ style=discord.ButtonStyle.success,
+ article=article,
+ score=score,
+ user=interaction.user.id,
+ game_type=GameType.wikianimal,
+ owners=owners,
+ winlossmanager=WinLossFunctions(args, args),
+ animal_info=animal_info,
+ view=hint_view,
+ )
+ )
+
+ give_up_button = GiveUpButton(
+ info=_Button(label="Give up", style=discord.ButtonStyle.danger, article=article),
+ view=hint_view,
+ )
+
+ hint_view.add_item(guess_button)
+
+ hint_view.add_item(give_up_button)
+
+ img_embed = make_img_embed(
+ article=article, error_message="Sorry this animal's image is missing. Good Luck!"
+ )
+
+ await interaction.followup.send(
+ content=f"Guess the {animal_name}'s weight.",
+ view=hint_view,
+ wait=True,
+ embed=img_embed,
+ ephemeral=ranked,
+ )
+
+ await interaction.delete_original_response()
+ except CommandInvokeError as e:
+ logging.info("Wiki-Animal:\nFunc: main\nException %s", e)
+ except NotFound as e:
+ logging.info("Wiki-Animal:\nFunc: main\nException %s", e)
diff --git a/pure-pulsars/src/cmds/wikiguesser.py b/pure-pulsars/src/cmds/wikiguesser.py
new file mode 100644
index 0000000..0699404
--- /dev/null
+++ b/pure-pulsars/src/cmds/wikiguesser.py
@@ -0,0 +1,161 @@
+"""Wiki Guesser Command."""
+
+import logging
+
+import discord
+from discord import NotFound, app_commands
+from discord.app_commands.errors import CommandInvokeError
+
+import button_class
+from button_class import ExcerptButton, GiveUpButton, GuessButton, LinkListButton, _Button, _Ranked
+from wikiutils import make_embed, rand_wiki, win_update
+
+ACCURACY_THRESHOLD = 0.8
+MAX_LEN = 1990
+LEN_OF_STR = 10
+
+
+class WinLossFunctions(button_class.WinLossManagement):
+ """The Basic Win Loss Function."""
+
+ def __init__(self, winargs: dict, lossargs: dict) -> None:
+ super().__init__(winargs, lossargs)
+
+ async def on_win(self, interaction: discord.Interaction) -> None:
+ """Clean up on win."""
+ ranked: bool = self.winargs["ranked"]
+ scores: list[int] = self.winargs["scores"]
+ article = self.winargs["article"]
+ embed = await make_embed(article)
+ msg = f"Congratulations {interaction.user.mention}! You figured it out, your score was {scores[0]}!"
+ await interaction.followup.send(content=msg, embed=embed)
+ if ranked:
+ user = interaction.user
+ for i in [interaction.guild_id, 0]:
+ await win_update(i, user, scores[0])
+
+ async def on_loss(self) -> None:
+ """Clean up on loss."""
+ interaction: discord.Interaction = self.lossargs["interaction"]
+ await interaction.followup.send("That's incorrect, please try again.", ephemeral=True)
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Create Wiki Guesser command."""
+
+ @tree.command(
+ name="wiki-guesser",
+ description="Starts a game of wiki-guesser! Try and find what wikipedia article your in.",
+ )
+ @app_commands.describe(ranked="Do you want to play ranked?")
+ async def wiki(interaction: discord.Interaction, ranked: _Ranked = _Ranked.NO) -> None:
+ """Run the wiki-guesser command.
+
+ This command will start a game of wiki-guesser, where you have to guess
+ the wikipedia article you are in.
+
+ Args:
+ ----
+ interaction (discord.Interaction): The interaction object.
+ ranked (_Ranked, optional): Whether the game is ranked or not. Defaults to _Ranked.NO.
+
+ """
+ try:
+ ranked: bool = bool(ranked.value)
+ owners = [interaction.user] if ranked else [*interaction.guild.members]
+ score = [1000]
+ if ranked:
+ await interaction.response.send_message(
+ content=f"Starting a game of **Ranked** Wikiguesser for {owners[0].mention}"
+ )
+ else:
+ await interaction.response.send_message(content="Starting a game of Wikiguesser")
+
+ # * I was encoutering an warning that happened sometimes that said 'rand_wiki' was never awaited but
+ # * when I ran the command again it didn't appear, so I'm just running this twice if it doesn't work the
+ # * first time
+ try:
+ article = await rand_wiki()
+ except AttributeError as e:
+ logging.info("Wiki-Guesser:\nFunc: main\nException %s", e)
+ article = await rand_wiki()
+ logging.info("The current wikiguesser title is %s", article.title())
+
+ links = [link.title() for link in article.linkedPages(total=50)]
+
+ excerpt = article.extract(chars=1200)
+
+ for i in article.title().split():
+ excerpt = excerpt.replace(i, "~~CENSORED~~")
+ excerpt = excerpt.replace(i.lower(), "~~CENSORED~~")
+
+ sentances = [i for i in excerpt.strip("\n").split(".") if i]
+ args = {"interaction": interaction, "ranked": ranked, "article": article, "scores": score}
+ excerpt_view = discord.ui.View()
+ guess_button = GuessButton(
+ info=_Button(
+ label="Guess!",
+ style=discord.ButtonStyle.success,
+ owners=owners,
+ ranked=ranked,
+ article=article,
+ score=score,
+ user=interaction.user.id,
+ view=excerpt_view,
+ winlossmanager=WinLossFunctions(args, args),
+ ),
+ )
+ excerpt_button = ExcerptButton(
+ info=_Button(
+ label="Show more",
+ style=discord.ButtonStyle.primary,
+ owners=owners,
+ private=ranked,
+ view=excerpt_view,
+ score=score,
+ ),
+ summary=sentances,
+ )
+
+ give_up_button = GiveUpButton(
+ info=_Button(
+ label="Give up",
+ style=discord.ButtonStyle.danger,
+ article=article,
+ view=excerpt_view,
+ ),
+ view=excerpt_view,
+ )
+
+ excerpt_view.add_item(excerpt_button)
+ excerpt_view.add_item(guess_button)
+ if ranked:
+ await interaction.followup.send(f"# RANKED WIKIGUESSER FOR {owners[0].mention}", ephemeral=True)
+
+ excerpt_view.add_item(give_up_button)
+
+ await interaction.followup.send(
+ content=f"__**Excerpt**__: {sentances[0]}.", view=excerpt_view, wait=True, ephemeral=ranked
+ )
+
+ view = discord.ui.View()
+ link_button = LinkListButton(
+ info=_Button(
+ label="Show more links in article",
+ links=links,
+ message="Links in article:",
+ owners=owners,
+ private=ranked,
+ score=score,
+ view=view,
+ ),
+ )
+
+ view.add_item(link_button)
+
+ await interaction.followup.send(view=view, wait=True, ephemeral=ranked)
+ await interaction.delete_original_response()
+ except NotFound as e:
+ logging.info("Wiki-Guesser:\nFunc: main\nException %s", e)
+ except CommandInvokeError as e:
+ logging.info("Wiki-Guesser:\nFunc: main\nException %s", e)
diff --git a/pure-pulsars/src/cmds/wikirandom.py b/pure-pulsars/src/cmds/wikirandom.py
new file mode 100644
index 0000000..ecb15bd
--- /dev/null
+++ b/pure-pulsars/src/cmds/wikirandom.py
@@ -0,0 +1,33 @@
+"""Wiki Random Command."""
+
+import logging
+
+import discord
+from discord import NotFound, app_commands
+from discord.app_commands.errors import CommandInvokeError
+
+from wikiutils import make_embed, rand_wiki
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Create random command."""
+
+ @tree.command(
+ name="wiki-random",
+ description="get a random wikipedia article",
+ )
+ async def wiki(interaction: discord.Interaction) -> None:
+ """Run the wiki-random command.
+
+ This command will get a random wikipedia article and send it to the user.
+ """
+ try:
+ await interaction.response.send_message(content="Finding a really cool article...")
+ article = await rand_wiki()
+ embed = await make_embed(article=article)
+ await interaction.followup.send(embed=embed)
+ await interaction.delete_original_response()
+ except NotFound as e:
+ logging.info("Wiki-Random:\nFunc: main\nException %s", e)
+ except CommandInvokeError as e:
+ logging.info("Wiki-Random:\nFunc: main\nException %s", e)
diff --git a/pure-pulsars/src/cmds/wikisearch.py b/pure-pulsars/src/cmds/wikisearch.py
new file mode 100644
index 0000000..2c12bce
--- /dev/null
+++ b/pure-pulsars/src/cmds/wikisearch.py
@@ -0,0 +1,47 @@
+"""Wiki search command."""
+
+import logging
+
+import discord
+from discord import app_commands
+from discord.app_commands.errors import CommandInvokeError
+from discord.errors import NotFound
+from pywikibot.exceptions import InvalidTitleError
+
+from wikiutils import make_embed, search_wikipedia
+
+
+def main(tree: app_commands.CommandTree) -> None:
+ """Create wiki search command."""
+
+ @tree.command(
+ name="wiki-search",
+ description="get a wikipedia article that you searched for",
+ )
+ @app_commands.describe(query="What are you asking it?")
+ async def wiki(interaction: discord.Interaction, query: str) -> None:
+ """Search wikipedia for an article using a query.
+
+ Args:
+ ----
+ interaction (discord.Interaction): The interaction object.
+ query (str): The query to search wikipedia for.
+
+ """
+ try:
+ await interaction.response.send_message(content="Finding your really cool article...")
+ embed = await make_embed(article=await search_wikipedia(query))
+ await interaction.followup.send(embed=embed)
+ await interaction.delete_original_response()
+ except InvalidTitleError:
+ await interaction.followup.send(content="Sorry, the article title was not valid.")
+ await interaction.delete_original_response()
+ except AttributeError:
+ await interaction.followup.send(
+ content="Sorry, an error with that article occured, please try a different one."
+ )
+ await interaction.delete_original_response()
+ except NotFound as e:
+ logging.info("Wiki-Search:\nFunc: main\nException %s", e)
+ except CommandInvokeError as e:
+ logging.info("Wiki-Search:\nFunc: main\nException %s", e)
diff --git a/pure-pulsars/src/database/__init__.py b/pure-pulsars/src/database/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pure-pulsars/src/database/database_core.py b/pure-pulsars/src/database/database_core.py
new file mode 100644
index 0000000..b072bd2
--- /dev/null
+++ b/pure-pulsars/src/database/database_core.py
@@ -0,0 +1,149 @@
+"""Core funcitonaliry for interactions with the scoring database."""
+
+import os
+from typing import ClassVar, Literal
+
+import firebase_admin
+from dotenv import load_dotenv
+from firebase_admin import db
+
+from .user import UserController
+
+if load_dotenv(".env"):
+ ...
+else:
+ load_dotenv("docker.env")
+
+
+class NullUserError(TypeError):
+ """User Doesn't exist in firebase database."""
+
+ def __init__(self) -> None:
+ super().__init__("User doesn't exist")
+
+
+class DatabaseController:
+ """Class to interact with the user section of the Database."""
+
+ SERVER_LIST: ClassVar = []
+
+ def __init__(
+ self,
+ ) -> None:
+ self._cred_obj = cred_obj = firebase_admin.credentials.Certificate(os.environ["CERT_PATH"])
+ self._default_app = firebase_admin.initialize_app(
+ cred_obj,
+ {
+ "databaseURL": "https://pure-pulsars-default-rtdb.firebaseio.com/",
+ },
+ )
+ self.SERVER_LIST = self.get_all_servers()
+
+ async def add_user(
+ self,
+ user_id: int,
+ user: UserController,
+ guild_id: int,
+ ) -> None:
+ """Add a user to the database using ref.set.
+
+ Args:
+ ----
+ user_id (int): The user's id.
+ user (UserController): The user object.
+ guild_id (int): The guild id.
+
+ """
+ database_user = await self.conn(guild_id, user_id)
+ _new_user = user.to_dictionary()
+ database_user.set(_new_user)
+
+ async def update_value_for_user(
+ self,
+ guild_id: int,
+ user_id: str,
+ value: int,
+ key: Literal["last_played", "score", "times_played", "wins", "failure"],
+ ) -> None:
+ """Update the specified value for the specified user.
+
+ Args:
+ ----
+ guild_id (int): The guild id.
+ user_id (str): The user id.
+ value (int): The updated score.
+ key (Literal["last_played", "score", "times_played", "wins", "failure"]): The key to update.
+
+ Raises:
+ ------
+ NullUserError: If the user doesn't exist.
+
+ """
+ """self._ref = db.reference("/users")"""
+ database_user = await self.conn(guild_id, user_id)
+ if database_user.get():
+ database_user.update({key: value})
+ return
+ raise NullUserError
+
+ async def get_user(self, guild_id: int, user_id: int) -> dict[str, int | str]:
+ """Return a user's information as a dict.
+
+ Args:
+ ----
+ guild_id (int): The guild id.
+ user_id (int): The user id.
+
+ Returns:
+ -------
+ dict[str, int | str]: The user's information.
+
+ Raises:
+ ------
+ NullUserError: If the user doesn't exist.
+
+ """
+ database_user = await self.conn(guild_id, user_id)
+ if database_user.get():
+ return database_user.get()
+ raise NullUserError
+
+ async def get_server(self, guild_id: int) -> dict:
+ """Get server information as a dict.
+
+ Args:
+ ----
+ guild_id (int): The guild id.
+
+ Returns:
+ -------
+ dict: The server information.
+
+ """
+ """Get the specified server."""
+ _ref = db.reference(f"/server/{guild_id}/")
+ return _ref.get()
+
+ async def get_all_servers(self) -> list:
+ """Return all servers' information as a list."""
+ return db.reference("/server/").get()
+
+ async def conn(self, guild_id: int, user_id: int) -> db.Reference:
+ """Return a connection to the db, for specific guilds/users.
+
+ Args:
+ ----
+ guild_id (int): The guild id.
+ user_id (int): The user id.
+
+ Returns:
+ -------
+ db.Reference: The reference to the user.
+
+ """
+ _ref = db.reference(f"/server/{guild_id}/")
+ return _ref.child(str(user_id))
+
+
+# Pre-instantiated controller object.
+DATA = DatabaseController()
diff --git a/pure-pulsars/src/database/user.py b/pure-pulsars/src/database/user.py
new file mode 100644
index 0000000..6b67527
--- /dev/null
+++ b/pure-pulsars/src/database/user.py
@@ -0,0 +1,49 @@
+"""User information classes for the database."""
+
+from typing import NamedTuple
+
+
+class _User(NamedTuple):
+ """User NamedTuple."""
+
+ name: str = ""
+ last_played: int = 0
+ score: int = 0
+ times_played: int = 0
+ wins: int = 0
+ failure: int = 0
+
+
+class UserController:
+ """The Basic User Class."""
+
+ def __init__(self, info: _User) -> None:
+ """Initialize the User Class.
+
+ Args:
+ ----
+ info (_User): The user information.
+
+ Notes:
+ -----
+ User information is stored as a NamedTuple. See _User for more
+ information on what names are expected.
+
+ """
+ self.name = info.name
+ self.last_played = info.last_played
+ self.score = info.score
+ self.times_played = info.times_played
+ self.wins = info.wins
+ self.failure = info.failure
+
+ def to_dictionary(self) -> dict:
+ """Return user as a Dictionary."""
+ return {
+ "name": self.name,
+ "last_played": self.last_played,
+ "score": self.score,
+ "times_played": self.times_played,
+ "wins": self.wins,
+ "failure": self.failure,
+ }
diff --git a/pure-pulsars/src/imgs/banner.png b/pure-pulsars/src/imgs/banner.png
new file mode 100644
index 0000000..e1fd2bc
Binary files /dev/null and b/pure-pulsars/src/imgs/banner.png differ
diff --git a/pure-pulsars/src/imgs/logo.png b/pure-pulsars/src/imgs/logo.png
new file mode 100644
index 0000000..802a490
Binary files /dev/null and b/pure-pulsars/src/imgs/logo.png differ
diff --git a/pure-pulsars/src/main.py b/pure-pulsars/src/main.py
new file mode 100644
index 0000000..32095f6
--- /dev/null
+++ b/pure-pulsars/src/main.py
@@ -0,0 +1,90 @@
+"""Main file for the discord bot."""
+
+import logging
+import os
+
+import discord
+from discord import app_commands
+from discord.ext import commands
+from dotenv import load_dotenv
+
+# Imports the commands
+from cmds import (
+ help_bot,
+ leaderboard,
+ never,
+ rabbit_hole,
+ reset_scores,
+ sync,
+ user_info,
+ wikianimal,
+ wikiguesser,
+ wikirandom,
+ wikisearch,
+)
+
+# loads the enviroment variables and initilazies the discord client
+load_dotenv(".env")
+intents = discord.Intents.all()
+client = discord.Client(
+ intents=intents,
+ heartbeat_timeout=120.0,
+)
+client.tree = app_commands.CommandTree(client)
+
+has_ran = False
+
+
+async def _first_run(client: commands.Bot) -> None:
+ """Sync command tree and update the app's discord status."""
+ has_ran = True
+ await client.tree.sync()
+ logging.info("The sync has been ran: %s", has_ran)
+ await client.change_presence(
+ status=discord.Status.online,
+ activity=discord.activity.CustomActivity(
+ "📚 reading wikipedia",
+ emoji="📚",
+ ),
+ )
+
+
+@client.event
+async def on_ready() -> None:
+ """Start the client."""
+ logging.info("Bot is ready")
+ if not has_ran:
+ await _first_run(client=client)
+
+
+@client.event
+async def on_guild_join(guild: discord.Object) -> None:
+ """Declare the on guild join to sync it."""
+ await client.tree.sync(guild=guild)
+ logging.info("Joined and synced with \nname: %s\nid: %s", guild.name, guild.id)
+
+
+# activates the commands
+sync.main(client.tree)
+wikiguesser.main(client.tree)
+wikirandom.main(client.tree)
+leaderboard.main(client.tree)
+user_info.main(client.tree)
+wikisearch.main(client.tree)
+wikianimal.main(client.tree)
+reset_scores.main(client.tree)
+never.main(client.tree)
+help_bot.main(client.tree)
+rabbit_hole.main(client.tree)
+
+
+logging.getLogger("discord.gateway").setLevel(logging.CRITICAL)
+logging.getLogger("google").setLevel(logging.ERROR)
+
+
+# runs the client
+client.run(
+ os.environ["TOKEN"],
+ log_level=os.environ.get("LOG_LEVEL", "INFO").upper(),
+ root_logger=True,
+)
diff --git a/pure-pulsars/src/throttle.ctrl b/pure-pulsars/src/throttle.ctrl
new file mode 100644
index 0000000..e69de29
diff --git a/pure-pulsars/src/wikiutils.py b/pure-pulsars/src/wikiutils.py
new file mode 100644
index 0000000..894cbbb
--- /dev/null
+++ b/pure-pulsars/src/wikiutils.py
@@ -0,0 +1,530 @@
+"""Utilities for interacting with Wikipedia.
+
+This module contains some helper functions for interacitng with and fetching
+data from Wikipedia. It also contains a class that can be used to generate
+articles with a specific category or title
+"""
+
+import functools
+import logging
+import random
+import secrets
+from collections import defaultdict
+from collections.abc import AsyncGenerator, Sequence
+from datetime import UTC, date, datetime
+from typing import ClassVar
+
+import aiohttp
+import pywikibot
+import pywikibot.page
+from discord import Colour, Embed, User
+from pywikibot import Page
+
+from database.database_core import DATA, NullUserError
+from database.user import UserController, _User
+
+UA = "WikiWabbit/1.1.0 (https://pure-pulsars.web.app/; dannytheheretic@proton.me)"
+site = pywikibot.Site("en", "wikipedia", user=UA)
+
+
+def rand_date() -> date:
+ """Take the current time returning the timetuple.
+
+ Pulls a random date between 8 years ago and now.
+ """
+ now = int(datetime.now(tz=UTC).timestamp() // 1)
+
+ # Picking a random date between 8 years and now.
+ y = int((now - 252482400) - now % 31557600 // 1)
+ return datetime.fromtimestamp(timestamp=now - secrets.randbelow(now - y), tz=UTC)
+
+
+async def make_embed(article: Page) -> Embed:
+ """Return a Discord Embed.
+
+ Args:
+ ----
+ article (Page): The article to create the embed for.
+
+ Returns:
+ -------
+ Embed: The embed.
+
+ Raises:
+ ------
+ AttributeError: If the image url cannot be found.
+
+ """
+ embed = Embed(title=article.title())
+ embed.description = f"{article.extract(chars=400)}...([read more]({article.full_url()}))"
+ url = article.page_image()
+ try:
+ url = url.latest_file_info.url
+ except AttributeError:
+ url = None
+ embed.set_image(url=url)
+ return embed
+
+
+def make_img_embed(article: Page, error_message: str = "Sorry no image found") -> Embed:
+ """Return a Discord Image type Embed.
+
+ Args:
+ ----
+ article (Page): The article to create the embed for.
+ error_message (str): The error message if no picture is found.
+
+ Returns:
+ -------
+ Embed: The embed.
+
+ Raises:
+ ------
+ AttributeError: If the image url cannot be found.
+
+ """
+ embed = Embed(colour=Colour.blue(), type="image")
+ img_data = article.page_image()
+ try:
+ img_url = img_data.get_file_url()
+ except AttributeError:
+ img_url = "https://wikimedia.org/static/images/project-logos/enwiki-2x.png"
+ embed.description = error_message
+ embed.set_image(url=img_url)
+ return embed
+
+
+def get_best_result(results: tuple[Page], query: str) -> Page | None:
+ """Return the best result from a list of results.
+
+ Args:
+ ----
+ results (tuple[Page]): A list of results.
+ query (str): The query to search for.
+
+ Returns:
+ -------
+ Page: The best result. None if no results are found.
+
+ Notes:
+ -----
+ Does not implement an optimal search algorithm, but works fine for the
+ scope of the project.
+
+ """
+ # Check for exact match in the title
+ for result in results:
+ if query.casefold().strip() == result.title().casefold().strip():
+ return result
+
+ # Check for near match in the title
+ for result in results:
+ if query.casefold().strip() in result.title().casefold().strip():
+ return result
+
+ return None
+
+
+async def search_wikipedia(query: str) -> Page | None:
+ """Search wikipedia and return the first result.
+
+ Args:
+ ----
+ query (str): The query to search for.
+
+ Returns:
+ -------
+ Page: The first page found. None if no results are found.
+
+ """
+ result = Page(site, title=query)
+
+ if not result.exists() or result.isRedirectPage():
+ # Try performing a search.
+ results = tuple(site.search(query, total=10))
+
+ if not results:
+ return None
+
+ return get_best_result(results, query)
+
+ try:
+ return result
+
+ except IndexError:
+ logging.exception("No results found for query: %s", query)
+ return None
+
+
+async def search_wikipedia_generator(query: str, max_number: int = 10) -> AsyncGenerator[Page, None]:
+ """Search wikipedia and return results matching the query.
+
+ Args:
+ ----
+ query (str): The query to search for.
+ max_number (int): The maximum number of results to return.
+
+ Returns:
+ -------
+ Page: The first page found. None if no results are found.
+
+ """
+ results = tuple(site.search(query, total=max_number))
+
+ for result in results:
+ yield result
+
+
+async def rand_wiki() -> Page:
+ """Return a random popular wikipedia article."""
+ return await ArticleGenerator().fetch_article()
+
+
+async def loss_update(guild: int, user: User) -> None:
+ """Update the user in the database.
+
+ Args:
+ ----
+ guild (int): The guild id.
+ user (User): The user to update.
+ score (int): The score to update.
+
+ """
+ uid = user.id
+ try:
+ db_ref_user = await DATA.get_user(guild, uid)
+ await DATA.update_value_for_user(
+ guild_id=guild,
+ user_id=uid,
+ key="times_played",
+ value=db_ref_user["times_played"] + 1,
+ )
+
+ await DATA.update_value_for_user(
+ guild_id=guild,
+ user_id=uid,
+ key="failure",
+ value=db_ref_user["failure"] + 1,
+ )
+
+ await DATA.update_value_for_user(
+ guild_id=guild,
+ user_id=uid,
+ key="last_played",
+ value=datetime.now(UTC).timestamp(),
+ )
+
+ except NullUserError:
+ new_user = UserController(
+ info=_User(
+ name=user.global_name,
+ times_played=1,
+ failure=1,
+ score=0,
+ last_played=datetime.now(UTC).timestamp(),
+ ),
+ )
+ await DATA.add_user(uid, new_user, guild)
+
+
+async def win_update(guild: int, user: User, score: int) -> None:
+ """Update the user in the database.
+
+ Args:
+ ----
+ guild (int): The guild id.
+ user (User): The user to update.
+ score (int): The score to update.
+
+ Notes:
+ -----
+ This interfaces directly with the database connection provided by database_core.py.
+ For more information on the functions used here, see database_core.py.
+
+ """
+ uid = user.id
+ try:
+ db_ref_user = await DATA.get_user(guild, uid)
+ await DATA.update_value_for_user(
+ guild_id=guild,
+ user_id=uid,
+ key="times_played",
+ value=db_ref_user["times_played"] + 1,
+ )
+
+ await DATA.update_value_for_user(
+ guild_id=guild,
+ user_id=uid,
+ key="score",
+ value=db_ref_user["score"] + score,
+ )
+
+ await DATA.update_value_for_user(
+ guild_id=guild,
+ user_id=uid,
+ key="last_played",
+ value=datetime.now(UTC).timestamp(),
+ )
+
+ await DATA.update_value_for_user(
+ guild_id=guild,
+ user_id=uid,
+ key="wins",
+ value=db_ref_user["wins"] + 1,
+ )
+
+ except NullUserError:
+ new_user = UserController(
+ info=_User(
+ name=user.global_name,
+ times_played=1,
+ wins=1,
+ score=score,
+ last_played=datetime.now(UTC).timestamp(),
+ ),
+ )
+
+ await DATA.add_user(uid, new_user, guild)
+
+
+def get_all_categories_from_article(article: Page) -> list[str]:
+ """Return all categories from an article.
+
+ Args:
+ ----
+ article (Page): The article to get the categories from.
+
+ Returns:
+ -------
+ list[str]: A list of categories.
+
+ Notes:
+ -----
+ This is a convenience function for getting all categories from an article.
+ For implementation, see ``ArticleGenerator.get_all_categories_from_article``.
+
+ """
+ return ArticleGenerator.get_all_categories_from_article(article)
+
+
+async def get_articles_with_categories(categories: Sequence[str], number: int = 1) -> list[Page]:
+ """Get a list of articles with the given categories.
+
+ Args:
+ ----
+ categories (Sequence[str]): A list of categories to search for.
+ number (int): The number of articles to return.
+
+ Returns:
+ -------
+ list[Page]: A list of articles.
+
+ """
+ generator = ArticleGenerator(categories=categories)
+
+ return [await generator.fetch_article() for _ in range(number)]
+
+
+class ArticleGeneratorError(Exception):
+ """Base class for ArticleGenerator exceptions."""
+
+
+class ArticleGenerator:
+ """Generates articles from Wikipedia.
+
+ This class is a generator that will return articles based on the given
+ constraints. It can be used to generate articles based on categories or
+ titles. It can also be used to generate random articles.
+
+ Attributes
+ ----------
+ categories (tuple[str]): A tuple of categories to search for.
+ titles (list[str]): A list of titles to search for.
+ _current_article (Page): The current article.
+ _generated_articles (set[Page]): A set of generated articles.
+
+ """
+
+ _article_limit: ClassVar[int] = 1_000
+
+ _current_article: Page
+ _generated_articles: set[Page]
+ categories: tuple[str]
+ titles: list[str]
+
+ def __init__(
+ self,
+ titles: Sequence[str] | None = None,
+ categories: Sequence[str] | None = None,
+ ) -> None:
+ """Initialize the ArticleGenerator.
+
+ Args:
+ ----
+ titles (Sequence[str]): A list of titles to search for.
+ categories (Sequence[str]): A list of categories to search for.
+
+ """
+ self._current_article = None
+ self.titles = titles or []
+ self.categories = categories or ()
+ self._generated_articles = set()
+
+ # Make this class a generator that will iterate over subsequent calls to fetch_article.
+ def __aiter__(self) -> "ArticleGenerator":
+ """Return the generator."""
+ return self
+
+ async def __anext__(self) -> Page:
+ """Return the next article."""
+ try:
+ return await self.fetch_article()
+
+ except ArticleGeneratorError as err:
+ raise StopAsyncIteration from err
+
+ @property
+ def current_article(self) -> Page:
+ """Return the current article."""
+ return self._current_article
+
+ def clear_cache(self) -> None:
+ """Clear the cache."""
+ self._generated_articles.clear()
+
+ async def fetch_article(self) -> Page:
+ """Return an article. Functon that retrieves the next article from the generator."""
+ return await anext(self.next_article())
+
+ async def next_article(self) -> AsyncGenerator[Page, None]:
+ """Return a new article."""
+ while True:
+ self._current_article = await self.fetch_valid_article()
+ yield self._current_article
+
+ async def fetch_valid_article(self) -> Page:
+ """Return a valid Page based on the current constraints."""
+ articles = await self._articles_from_titles() if self.titles else []
+
+ # Filter articles that have already been generated.
+ articles = [article for article in articles if article not in self._generated_articles]
+
+ if self.categories:
+ if not articles:
+ articles = await self._articles_from_categories()
+
+ try:
+ article = secrets.choice([article for article in articles if self.article_has_categories(article)])
+
+ except IndexError as err:
+ message = f"No articles found in the categories: {self.categories}."
+ raise ArticleGeneratorError(message) from err
+
+ elif not articles and not self.titles:
+ article = await self.random_article()
+
+ else:
+ try:
+ article = articles[0]
+
+ except IndexError as err:
+ message = "No articles found for the given titles."
+ raise ArticleGeneratorError(message) from err
+
+ # Record this article as generated.
+ self._generated_articles.add(article)
+
+ return article
+
+ async def _articles_from_categories(self) -> list[Page]:
+ """Return an article from the list of categories."""
+ category_pages = {category: set(pywikibot.Category(site, category).articles()) for category in self.categories}
+
+ # If there are mutlitple categories, get the intersection of the articles.
+ articles = functools.reduce(set.union, category_pages.values())
+
+ # For each of these articles, check if they have all the categories.
+ articles = [article for article in articles if self.article_has_categories(article)]
+
+ try:
+ return articles
+
+ except IndexError as err:
+ message = "No articles found in the categories."
+ raise ArticleGeneratorError(message) from err
+
+ async def _articles_from_titles(self) -> list[Page]:
+ """Return an article from the list of titles."""
+ # If categories are provided, do a broader search.
+ if self.categories:
+ title_articles: dict[str, set[Page]] = defaultdict(set)
+
+ for title in self.titles:
+ async for article in search_wikipedia_generator(title):
+ title_articles[title].add(article)
+
+ articles = {a for articles in title_articles.values() for a in articles}
+
+ if any(article is None for article in title_articles.values()):
+ titles_not_found = [title for title, article in title_articles.items() if article is None]
+ message = f"No articles found for the following titles: {titles_not_found}"
+
+ raise ArticleGeneratorError(message)
+
+ # Ensure the title is at least a partial match.
+ articles = {
+ article
+ for article in articles
+ if any(title.casefold() in article.title().casefold() for title in self.titles)
+ }
+
+ else:
+ # Get top page for each of the titles.
+ articles = [await search_wikipedia(title) for title in self.titles]
+
+ if None in articles:
+ titles_not_found = [
+ title for title, article in zip(self.titles, articles, strict=False) if article is None
+ ]
+ message = f"No articles found for the following titles: {titles_not_found}"
+
+ raise ArticleGeneratorError(message)
+
+ if not articles:
+ message = "No articles found for the given titles"
+ raise ArticleGeneratorError(message)
+
+ return articles
+
+ def article_has_categories(self, article: Page) -> bool:
+ """Return True if the article has all the categories."""
+ article_categories = get_all_categories_from_article(article)
+ article_categories = {category.casefold() for category in article_categories}
+
+ return all(category.casefold() in article_categories for category in self.categories)
+
+ @staticmethod
+ async def random_article() -> Page:
+ """Return a random article."""
+ date = rand_date()
+ date = f"{date.year}/{date.month:02}/{date.day:02}"
+ url = f"https://wikimedia.org/api/rest_v1/metrics/pageviews/top/en.wikipedia/all-access/{date}"
+ json = None
+ async with aiohttp.ClientSession(headers={"UserAgent": UA}) as session, session.get(url) as response:
+ json = await response.json()
+ try:
+ articles = json["items"][0]["articles"]
+ random.shuffle(articles)
+ title = articles[0]["article"]
+ page = Page(site, title)
+ if page.isRedirectPage() or not page.exists():
+ return await rand_wiki()
+ except KeyError as e:
+ logging.info("Wikiutils:\nFunc: random_article\nException: %s", e)
+ return await rand_wiki()
+ return page
+
+ @staticmethod
+ def get_all_categories_from_article(article: Page) -> list[str]:
+ """Return all categories from an article."""
+ return [category.title().replace("Category:", "") for category in article.categories()]
diff --git a/pure-pulsars/tests/__init__.py b/pure-pulsars/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pure-pulsars/tests/unit/__init__.py b/pure-pulsars/tests/unit/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pure-pulsars/tests/unit/wikiutils/__init__.py b/pure-pulsars/tests/unit/wikiutils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pure-pulsars/tests/unit/wikiutils/test_articlegenerator.py b/pure-pulsars/tests/unit/wikiutils/test_articlegenerator.py
new file mode 100644
index 0000000..7cafe0d
--- /dev/null
+++ b/pure-pulsars/tests/unit/wikiutils/test_articlegenerator.py
@@ -0,0 +1,160 @@
+"""Test the ArticleGenerator class."""
+# ruff: noqa: SLF001, S101, D103
+
+import pytest
+from pywikibot import Page
+from src.wikiutils import ArticleGenerator, ArticleGeneratorError
+
+
+def get_all_categories_from_article(article: Page) -> list[str]:
+ """Return all categories from an article."""
+ return [category.title() for category in article.categories()]
+
+
+@pytest.mark.asyncio()
+async def test_plain_articlegenerator() -> None:
+ """Test the ArticleGenerator class."""
+ article_gen = ArticleGenerator()
+
+ result = article_gen.fetch_article()
+
+ assert result is not None
+
+
+@pytest.mark.asyncio()
+async def test_specific_page() -> None:
+ article_gen = ArticleGenerator(titles=["Python (programming language)"])
+
+ result = await article_gen.fetch_article()
+
+ assert result is not None
+ assert result.title() == "Python (programming language)"
+
+
+@pytest.mark.asyncio()
+async def test_category_fetch() -> None:
+ article_gen = ArticleGenerator(categories=("Astronomy",))
+
+ assert article_gen.categories == ("Astronomy",)
+
+ result = await article_gen.fetch_article()
+
+ assert result is not None
+ assert "Category:Astronomy" in get_all_categories_from_article(result)
+
+
+@pytest.mark.asyncio()
+async def test_multiple_categories() -> None:
+ article_gen = ArticleGenerator(categories=("Astronomy", "Physics"))
+
+ assert article_gen.categories == ("Astronomy", "Physics")
+
+ result = await article_gen.fetch_article()
+
+ assert result is not None
+ assert all(
+ f"Category:{category}" in get_all_categories_from_article(result) for category in article_gen.categories
+ )
+
+
+@pytest.mark.asyncio()
+async def test_multiple_titles() -> None:
+ article_gen = ArticleGenerator(titles=["Python (programming language)", "Java (programming language)"])
+
+ assert article_gen.titles == ["Python (programming language)", "Java (programming language)"]
+
+ result = await article_gen.fetch_article()
+
+ assert result is not None
+ assert result.title() in article_gen.titles
+
+ result1 = result
+
+ result = await article_gen.fetch_article()
+
+ assert result is not None
+ assert result.title() in article_gen.titles
+ assert result.title() != result1.title()
+
+
+@pytest.mark.asyncio()
+async def test_clear_cache() -> None:
+ article_gen = ArticleGenerator(titles=["Python (programming language)", "Java (programming language)"])
+
+ result = await article_gen.fetch_article()
+
+ assert result is not None
+ assert result.title() in article_gen.titles
+ assert article_gen._generated_articles == {result}
+
+ article_gen.clear_cache()
+
+ assert article_gen._generated_articles == set()
+
+
+@pytest.mark.asyncio()
+async def test_invalid_cateory() -> None:
+ article_gen = ArticleGenerator(categories=("Invalid category",))
+
+ with pytest.raises(ArticleGeneratorError):
+ _ = await article_gen.fetch_article()
+
+
+@pytest.mark.asyncio()
+async def test_invalid_title() -> None:
+ article_gen = ArticleGenerator(titles=["Invalid title!!!.!"])
+
+ with pytest.raises(ArticleGeneratorError):
+ _ = await article_gen.fetch_article()
+
+
+@pytest.mark.asyncio()
+async def test_invalid_title_and_category() -> None:
+ article_gen = ArticleGenerator(titles=["Invalid title!!!.!"], categories=("Invalid category",))
+
+ with pytest.raises(ArticleGeneratorError):
+ _ = await article_gen.fetch_article()
+
+
+@pytest.mark.asyncio()
+async def test_invalid_title_and_valid_category() -> None:
+ article_gen = ArticleGenerator(titles=["Invalid title!!!.!"], categories=("Astronomy",))
+
+ with pytest.raises(ArticleGeneratorError):
+ _ = await article_gen.fetch_article()
+
+
+@pytest.mark.asyncio()
+async def test_valid_title_and_invalid_category() -> None:
+ article_gen = ArticleGenerator(titles=["Python (programming language)"], categories=("Invalid category",))
+
+ with pytest.raises(ArticleGeneratorError):
+ _ = await article_gen.fetch_article()
+
+
+@pytest.mark.asyncio()
+async def test_title_and_category() -> None:
+ # Note: the category capitalization vs. the actual category name is different.
+ # This is intentional to test the case insensitivity of the category name.
+ article_gen = ArticleGenerator(titles=["Python"], categories=("Programming Languages",))
+
+ result = await article_gen.fetch_article()
+
+ assert result is not None
+ assert result.title() == "Python (programming language)"
+ assert "Category:Programming languages" in get_all_categories_from_article(result)
+
+
+@pytest.mark.asyncio()
+async def test_articlegenerator_as_iterator() -> None:
+ article_gen = ArticleGenerator(titles=["Python (programming language)", "Java (programming language)"])
+
+ recieved = []
+ async for article in article_gen:
+ assert article is not None
+ assert article.title() in article_gen.titles
+
+ recieved.append(article)
+
+ assert len(recieved) == len(article_gen.titles)
+ assert all(article in article_gen._generated_articles for article in recieved)
diff --git a/pure-pulsars/tests/unit/wikiutils/test_utility_functions.py b/pure-pulsars/tests/unit/wikiutils/test_utility_functions.py
new file mode 100644
index 0000000..2755d7d
--- /dev/null
+++ b/pure-pulsars/tests/unit/wikiutils/test_utility_functions.py
@@ -0,0 +1,43 @@
+"""Testing the rand_wiki function."""
+# ruff: noqa: SLF001, S101, D103
+
+import pytest
+import pywikibot
+from src.wikiutils import get_all_categories_from_article, rand_wiki, site
+
+N_RAND_WIKI_TESTS = 10
+
+
+@pytest.fixture()
+def sample_astronomy_articles() -> list[pywikibot.Page]:
+ """Return a list of astronomy articles."""
+ page_titles = [
+ "Astronomy",
+ "Baade-Wesselink method",
+ "Tidal downsizing",
+ ]
+
+ return [pywikibot.Page(site, title) for title in page_titles]
+
+
+@pytest.mark.asyncio()
+async def test_rand_wiki() -> None:
+ """Test the rand_wiki function."""
+ for _ in range(N_RAND_WIKI_TESTS):
+ result = await rand_wiki()
+
+ assert result is not None
+ assert result.title() is not None
+ assert result.text is not None
+ assert result.categories() is not None
+
+
+@pytest.mark.asyncio()
+async def test_get_all_categories_from_article(sample_astronomy_articles: list[pywikibot.Page]) -> None:
+ for article in sample_astronomy_articles:
+ categories = get_all_categories_from_article(article)
+
+ assert "Astronomy" in categories
+
+ # All example articles should alsohave any single other category.
+ assert len(categories) > 1