diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..91b5759
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @caffeine-addictt
\ No newline at end of file
diff --git a/.github/CODESTYLE.md b/.github/CODESTYLE.md
new file mode 100644
index 0000000..d87f389
--- /dev/null
+++ b/.github/CODESTYLE.md
@@ -0,0 +1,111 @@
+# Code Style
+The following is a general guide on how to style your work so that the project
+remains consistent throughout all files. Please read this document in it's entirety
+and refer to it throughout the development of your contribution.
+
+1. [General Guidelines](#general-guidelines)
+2. [Commit Message Guidelines](#commit-message-guidelines)
+3. [Markdown Guidelines](#markdown-guidelines)
+
+
+
+## General Guidelines
+Listed is a example class used demonstrate general rules you should follow throughout the development of your contribution.
+
+- Docstrings are to follow reST (reStructuredText Docstring Format) as specified in [PEP 287](https://peps.python.org/pep-0287/)
+- Private attributes are to be prefixed with an underscore
+- Use of [typing](https://docs.python.org/3/library/typing.html) type hints
+- All files are to use 2 space indenting
+
+```python
+class ExampleClass:
+ """
+ ExampleClass
+ ------------
+ Example class for CODESTYLE.md
+ """
+ # ^^^ reST Docstring Format
+
+ _private_attribute : int # private attributes begin with a lowercase
+ public_attribute : int # type hint for integer is defined here
+
+ def __init__(
+ self,
+ public_attribute: int # type hint for parameters
+ ) -> None: # the expected return value of method
+ """
+ Initializes a ExampleClass
+
+ Parameters
+ ----------
+ :param public_attribute: example attribute
+ """
+ self.public_attribute = public_attribute
+ self.private_attribute = square(public_attribute)
+
+ def square(self, value: int) -> int:
+ """
+ Example method that square roots a value
+
+ Parameters
+ ----------
+ :param value: value that you want squared
+ """
+ return value**2
+```
+
+
+
+## Commit Message Guidelines
+When committing, commit messages are prefixed with a `+` or `-`. Depending on the type of change made
+influences which prefix is used.
+
+ - `+` when something is added.
+ - `-` when something is removed.
+ - none: when neither is applicable, like merge commits.
+
+Commit messages are also to begin with an uppercase character. Below list some example commit messages.
+
+```
+git commit -m "+ Added README.md"
+git commit -m "- Removed README.md"
+git commit -m "Moved README.md"
+```
+
+
+
+## Markdown Guidelines
+Currently, documentation for this project resides in markdown files.
+ - Headings are to be separated with 3 lines
+ - Use of HTML comments is appreciated
+ - Use of HTML is permitted
+ - [reference style links](https://www.markdownguide.org/basic-syntax/#reference-style-links) are not required by are appreciated
+ - Exceedingly long lines are to be broken
+ - The indents are to be two spaces
+
+```markdown
+
+# Section
+Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+sed do eiusmod tempor incididunt ut labore et dolore
+magna aliqua. Ut enim ad minim veniam, quis nostrud
+exercitation ullamco laboris nisi ut aliquip ex ea
+commodo consequat. Duis aute irure dolor in
+reprehenderit in voluptate velit esse cillum dolore eu
+fugiat nulla pariatur. Excepteur sint occaecat cupidatat
+non proident, sunt in culpa qui officia deserunt mollit
+anim id est laborum. found [Lorem Ipsum Generator]
+
+
+
+# Section 2
+
+ - Apple
+
- Orange
+
- Pineapple
+
+
+
+
+[Lorem Ipsum Generator]: https://loremipsum.io/generator/
+```
\ No newline at end of file
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..40124bd
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,27 @@
+# **Contributing**
+
+When contributing to this repository, please first discuss the change you wish to make via issue,
+email, or any other method with the owners of this repository before making a change.
+
+Please note we have a [code of conduct](CODE_OF_CONDUCT.md); please follow it in all your interactions with the project.
+
+
+
+## Pull Request Process
+
+1. Ensure any install or build dependencies are removed before the end of the layer when doing a
+ build.
+2. Update the README.md with details of changes to the interface; this includes new environment variables, exposed ports, valid file locations and container parameters.
+3. Increase the version numbers in any examples files and the README.md to the new version that this
+ Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
+4. You may merge the Pull Request once you have the sign-off of two other developers, or if you
+ do not have permission to do that, you may request the second reviewer to merge it for you.
+
+
+
+## Issue Report Process
+
+1. Go to the project's issues.
+2. Select the template that better fits your issue.
+3. Read the instructions carefully and write within the template guidelines.
+4. Submit it and wait for support.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md
new file mode 100644
index 0000000..40ef347
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/1-bug-report.md
@@ -0,0 +1,82 @@
+---
+name: "Bug Report"
+about: "Report an issue to help the project improve."
+title: "[Bug] "
+labels: "Type: Bug"
+assignees: caffeine-addictt
+
+---
+
+# Bug report
+Your issue may already be reported!
+Please check out our [active issues](https://github.com/python-thread/thread-bot/issues) before creating one.
+
+
+
+## Expected Behavior
+
+
+
+
+## Current Behavior
+
+
+
+
+## Is this a regression?
+
+
+
+
+## Possible Solution
+
+
+
+
+## Steps to Reproduce (for bugs)
+
+1.
+2.
+3.
+4.
+
+
+
+## Context
+
+
+
+
+## Your Environment
+
+* Version used:
+* Python version:
+* Link to your project:
+* Operating System and version (desktop or mobile):
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.md b/.github/ISSUE_TEMPLATE/2-feature-request.md
new file mode 100644
index 0000000..4b43a0a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/2-feature-request.md
@@ -0,0 +1,43 @@
+---
+name: "Feature Request"
+about: "Suggest an idea or possible new feature for this project."
+title: ""
+labels: "Type: Feature"
+assignees: caffeine-addictt
+
+---
+
+# Feature Request
+Your issue may already be reported!
+Please check out our [active issues](https://github.com/python-thread/thread-bot/issues) before creating one.
+
+
+
+## Is Your Feature Request Related to an Issue?
+
+
+
+
+## Describe the Solution You'd Like
+
+
+
+
+## Describe Alternatives You've Considered
+
+
+
+
+## Additional Context
+
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/3-seccurity-report.md b/.github/ISSUE_TEMPLATE/3-seccurity-report.md
new file mode 100644
index 0000000..1dc39b8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/3-seccurity-report.md
@@ -0,0 +1,97 @@
+---
+name: "Security Report"
+about: "Report an issue to help the project improve."
+title: ""
+labels: "Type: Security"
+assignees: caffeine-addictt
+
+---
+
+
+
+# Security Report
+Your issue may already be reported!
+Please check out our [active issues](https://github.com/python-thread/thread-bot/issues) before creating one.
+
+
+
+## Describe the Security Issue
+
+
+
+
+## Steps to Reproduce
+
+
+
+
+## Expected Behavior
+
+
+
+
+## Media Prove
+
+
+
+
+## Additional Context
+
+
+
+
+### Your Environment
+
+* Version used:
+* Python version:
+* Link to your project:
+* Operating System and version (desktop or mobile):
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/4-question-support.md b/.github/ISSUE_TEMPLATE/4-question-support.md
new file mode 100644
index 0000000..cc44220
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/4-question-support.md
@@ -0,0 +1,20 @@
+---
+name: "Question or Support Request"
+about: "Questions and requests for support."
+title: ""
+labels: "Type: Question"
+assignees: caffeine-addictt
+
+---
+
+# Question or Support Request
+
+
+
+
+## Describe your question or ask for support
+
+
+*
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..492cab1
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,78 @@
+
+
+## What type of PR is this? (check all applicable)
+
+- [ ] Refactor
+- [ ] Feature
+- [ ] Bug Fix
+- [ ] Optimization
+- [ ] Breaking Change
+- [ ] Documentation Update
+
+
+
+## Description
+
+
+
+
+## Related Tickets & Documents
+
+
+
+- Related Issue #
+- Closes #
+
+
+
+## QA Instructions, Screenshots, Recordings
+
+_Please replace this line with instructions on how to test your changes, a note
+on the devices and browsers this has been tested on, as well as any relevant
+images for UI changes._
+
+
+
+## Added/updated tests?
+_We encourage you to keep the code coverage percentage at 80% and above._
+
+- [ ] Yes
+- [ ] No, and this is why: _please replace this line with details on why tests
+ have not been included_
+- [ ] I need help with writing tests
+
+
+
+## [optional] Are there any post deployment tasks we need to perform?
+
+
+
+
+## Checklist
+
+- [ ] My code follows the code style of this project.
+- [ ] My change requires a change to the documentation.
+- [ ] I have updated the documentation accordingly.
\ No newline at end of file
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
new file mode 100644
index 0000000..84b319d
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,11 @@
+# **Reporting Security Issues**
+
+The project's team and community take security issues.
+
+We appreciate your efforts to disclose your findings responsibly and will make every effort to acknowledge your contributions.
+
+To report a security issue, go to the project's issues and create a new issue using the ā ļø Security Report 'issue template'.
+
+Read the instructions of this issue template carefully, and if your report could leak data or might expose how to gain access to a restricted area or break the system, please email [thread@ngjx.org](mailto:thread@ngjx.org) and include the word "SECURITY" in the subject line.
+
+We'll endeavour to respond quickly and keep you updated throughout the process.
\ No newline at end of file
diff --git a/.github/settings.yml b/.github/settings.yml
new file mode 100644
index 0000000..9aa6bf6
--- /dev/null
+++ b/.github/settings.yml
@@ -0,0 +1,80 @@
+labels:
+ - name: 'Type: Bug'
+ color: e80c0c
+ description: Something isn't working as expected.
+
+ - name: 'Type: Enhancement'
+ color: 54b2ff
+ description: Suggest an improvement for an existing feature.
+
+ - name: 'Type: Feature'
+ color: 54b2ff
+ description: Suggest a new feature.
+
+ - name: 'Type: Security'
+ color: fbff00
+ description: A problem or enhancement related to a security issue.
+
+ - name: 'Type: Question'
+ color: 9309ab
+ description: Request for information.
+
+ - name: 'Type: Test'
+ color: ce54e3
+ description: A problem or enhancement related to a test.
+
+ - name: 'Status: Awaiting Review'
+ color: 24d15d
+ description: Ready for review.
+
+ - name: 'Status: WIP'
+ color: 07b340
+ description: Currently being worked on.
+
+ - name: 'Status: Waiting'
+ color: 38C968
+ description: Waiting on something else to be ready.
+
+ - name: 'Status: Stale'
+ color: 66b38a
+ description: Has had no activity for some time.
+
+ - name: 'Duplicate'
+ color: EB862D
+ description: Duplicate of another issue.
+
+ - name: 'Invalid'
+ color: faef50
+ description: This issue doesn't seem right.
+
+ - name: 'Priority: High +'
+ color: ff008c
+ description: Task is considered higher-priority.
+
+ - name: 'Priority: Low -'
+ color: 690a34
+ description: Task is considered lower-priority.
+
+ - name: 'Documentation'
+ color: 2fbceb
+ description: An issue/change with the documentation.
+
+ - name: "Won't fix"
+ color: C8D9E6
+ description: Reported issue is working as intended.
+
+ - name: '3rd party issue'
+ color: e88707
+ description: This issue might be caused by a 3rd party script/package/other reasons
+
+ - name: 'Os: Windows'
+ color: AEB1C2
+ description: Is Windows-specific
+
+ - name: 'Os: Mac'
+ color: AEB1C2
+ description: Is Mac-specific
+
+ - name: 'Os: Linux'
+ color: AEB1C2
+ description: Is Linux-specific
\ No newline at end of file
diff --git a/.github/workflows/test-worker.yml b/.github/workflows/test-worker.yml
new file mode 100644
index 0000000..7d8bd74
--- /dev/null
+++ b/.github/workflows/test-worker.yml
@@ -0,0 +1,45 @@
+name: Run Python tests
+
+on: [ push ]
+
+jobs:
+ build:
+
+ name: Run tests
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ["3.11", "3.x"]
+
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install dependencies
+ run: |
+ python -m pip install -U pip
+ python -m pip install -U coverage pytest pytest-cov poetry
+ python -m poetry install
+ python -m poetry self add poetry-plugin-export
+ python -m poetry export -f requirements.txt --output requirements.txt
+ python -m pip install -r requirements.txt
+
+ - name: Lint with Ruff
+ run: |
+ python -m pip install -U ruff
+ ruff --per-file-ignores="__init__.py:F401" --per-file-ignores="__init__.py:E402" .
+ continue-on-error: true
+
+ - name: Test with pytest
+ run: |
+ coverage run -m pytest -v -s
+
+ - name: Generate Coverage Report
+ run: |
+ coverage report -m
+
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..23165df
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+# Python stuff
+venv*/
+*.pytest_cache
+__pycache__/
+
+*.py[cod]
+*$py.class
+
+*.ruff_cache
+
+# Env
+.env
+.vscode/
\ No newline at end of file
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..46fb426
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,516 @@
+# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
+
+[[package]]
+name = "aiohttp"
+version = "3.9.1"
+description = "Async http client/server framework (asyncio)"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"},
+ {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"},
+ {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"},
+ {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"},
+ {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"},
+ {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"},
+ {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"},
+ {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"},
+ {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"},
+ {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"},
+ {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"},
+ {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"},
+ {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"},
+ {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"},
+ {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"},
+ {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"},
+ {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"},
+ {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"},
+ {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"},
+ {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"},
+ {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"},
+ {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"},
+ {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"},
+ {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"},
+ {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"},
+ {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"},
+ {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"},
+ {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"},
+ {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"},
+ {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"},
+ {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"},
+ {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"},
+ {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"},
+ {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"},
+ {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"},
+ {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"},
+ {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"},
+ {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"},
+ {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"},
+ {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"},
+ {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"},
+ {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"},
+ {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"},
+ {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"},
+ {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"},
+ {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"},
+ {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"},
+ {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"},
+ {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"},
+ {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"},
+ {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"},
+ {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"},
+ {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"},
+ {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"},
+ {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"},
+ {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"},
+ {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"},
+ {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"},
+ {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"},
+ {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"},
+ {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"},
+ {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"},
+ {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"},
+ {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"},
+ {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"},
+ {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"},
+ {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"},
+ {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"},
+ {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"},
+ {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"},
+ {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"},
+ {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"},
+ {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"},
+ {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"},
+ {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"},
+ {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"},
+]
+
+[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 = "attrs"
+version = "23.1.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"},
+ {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"},
+]
+
+[package.extras]
+cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
+dev = ["attrs[docs,tests]", "pre-commit"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
+tests = ["attrs[tests-no-zope]", "zope-interface"]
+tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+
+[[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 = "discord-py"
+version = "2.3.2"
+description = "A Python wrapper for the Discord API"
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "discord.py-2.3.2-py3-none-any.whl", hash = "sha256:9da4679fc3cb10c64b388284700dc998663e0e57328283bbfcfc2525ec5960a6"},
+ {file = "discord.py-2.3.2.tar.gz", hash = "sha256:4560f70f2eddba7e83370ecebd237ac09fbb4980dc66507482b0c0e5b8f76b9c"},
+]
+
+[package.dependencies]
+aiohttp = ">=3.7.4,<4"
+
+[package.extras]
+docs = ["sphinx (==4.4.0)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport", "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)"]
+voice = ["PyNaCl (>=1.3.0,<1.6)"]
+
+[[package]]
+name = "emoji"
+version = "2.9.0"
+description = "Emoji for Python"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "emoji-2.9.0-py2.py3-none-any.whl", hash = "sha256:17b0d53e1d9f787307a4c65aa19badb0a1ffdbc89b3a3cd851fc77821cdaced2"},
+ {file = "emoji-2.9.0.tar.gz", hash = "sha256:5f4a15b7caa9c67fc11be9d90a822e3fa26aeb4e5b7bd2ded754b394d9c47869"},
+]
+
+[package.extras]
+dev = ["coverage", "coveralls", "pytest"]
+
+[[package]]
+name = "frozenlist"
+version = "1.4.0"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"},
+ {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"},
+ {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"},
+ {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"},
+ {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"},
+ {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"},
+ {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"},
+ {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"},
+ {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"},
+ {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"},
+ {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"},
+ {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"},
+ {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"},
+ {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"},
+ {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"},
+ {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"},
+ {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"},
+ {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"},
+ {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"},
+ {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"},
+ {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"},
+ {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"},
+ {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"},
+ {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"},
+ {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"},
+ {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"},
+ {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"},
+ {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"},
+ {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"},
+ {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"},
+ {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"},
+ {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"},
+ {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"},
+ {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"},
+ {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"},
+ {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"},
+ {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"},
+ {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"},
+ {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"},
+ {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"},
+ {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"},
+ {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"},
+ {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"},
+ {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"},
+ {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"},
+ {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"},
+ {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"},
+ {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"},
+ {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"},
+ {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"},
+ {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"},
+ {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"},
+ {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"},
+ {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"},
+ {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"},
+ {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"},
+ {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"},
+ {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"},
+ {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"},
+ {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"},
+ {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"},
+]
+
+[[package]]
+name = "idna"
+version = "3.6"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
+ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
+]
+
+[[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 = "multidict"
+version = "6.0.4"
+description = "multidict implementation"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"},
+ {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"},
+ {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"},
+ {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"},
+ {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"},
+ {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"},
+ {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"},
+ {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"},
+ {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"},
+ {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"},
+ {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"},
+ {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"},
+ {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"},
+ {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"},
+ {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"},
+ {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"},
+ {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"},
+ {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"},
+ {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"},
+ {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"},
+ {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"},
+ {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"},
+ {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"},
+ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
+]
+
+[[package]]
+name = "packaging"
+version = "23.2"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
+ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
+]
+
+[[package]]
+name = "pluggy"
+version = "1.3.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
+ {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pytest"
+version = "7.4.3"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
+ {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+
+[package.extras]
+testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "python-dotenv"
+version = "1.0.0"
+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.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
+ {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
+[[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.11"
+content-hash = "4a9a86c6b90e6c913b9cb6ef014303c23a982240666f513017485f0e43a79f0f"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..bf48e64
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,20 @@
+[tool.poetry]
+name = "thread-bot"
+version = "0.1.0"
+description = ""
+authors = ["AlexNg "]
+license = "BSD-3-Clause"
+readme = "README.md"
+
+[tool.poetry.dependencies]
+python = "^3.11"
+"discord.py" = "^2.3.2"
+python-dotenv = "^1.0.0"
+emoji = "^2.9.0"
+
+[tool.poetry.group.dev.dependencies]
+pytest = "^7.4.3"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/run.py b/run.py
new file mode 100644
index 0000000..482ad88
--- /dev/null
+++ b/run.py
@@ -0,0 +1,10 @@
+import asyncio
+from src import main
+
+if __name__ == '__main__':
+ try:
+ asyncio.run(main())
+ except KeyboardInterrupt:
+ pass
+ except Exception as e:
+ raise RuntimeError('Failed to start') from e
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..cfbaaea
--- /dev/null
+++ b/src/__init__.py
@@ -0,0 +1,41 @@
+import os
+import discord
+from discord.ext import commands
+
+from .config import Config
+
+
+client = commands.Bot(
+ command_prefix = Config.COMMAND_PREFIX,
+ intents = discord.Intents.all()
+)
+
+# Configuration
+@client.event
+async def on_ready():
+ print(f'Client [{client.user}] UP')
+
+@client.event
+async def on_message(message):
+ # Allow text to invoke comamnds
+ await client.process_commands(message)
+
+
+# Loading cogs
+async def load_cogs():
+ for filename in os.listdir(os.path.join(os.getcwd(), 'src', 'cogs')):
+ if filename.endswith('.py'):
+ await client.load_extension(
+ f'.cogs.{filename.split("/")[-1][:-3]}',
+ package = 'src'
+ )
+
+
+# Main runner
+async def main():
+ if not Config.BOT_TOKEN:
+ raise Exception('No bot token')
+
+ async with client:
+ await load_cogs()
+ await client.start(Config.BOT_TOKEN)
diff --git a/src/check.py b/src/check.py
new file mode 100644
index 0000000..caafa7e
--- /dev/null
+++ b/src/check.py
@@ -0,0 +1,225 @@
+import discord
+from discord import app_commands
+from discord.ext import commands
+
+from .config import Config
+from typing import (
+ Any, NoReturn, Literal, Union, Optional,
+ List, Sequence, TypedDict
+)
+
+
+class PermissionRequirement(TypedDict):
+ """
+ Permission Requirement
+
+ Parameters
+ ---------------
+ :param type: Wheter to whitelist or blacklist this condition
+ :param query: The permission scope
+ :param value: The permission value
+ """
+ type: Literal['wl', 'bl']
+ query: Literal['in_channel', 'in_guild', 'has_role', 'has_permission', 'is_developer', 'minimum_role']
+ value: Optional[Union[str, int]]
+
+class PermissionGate(TypedDict):
+ """
+ Permission Gate
+
+ Parameters
+ ----------
+ :param type: Whether the permission gate is required or optional
+ :param requirement: The permission requirement
+ """
+ type: Literal['required', 'optional']
+ requirement: PermissionRequirement
+
+class HybridContext:
+ """
+ Hybrid Context derrived from any command context
+ """
+
+ author: Union[discord.User, discord.Member]
+ is_developer: bool
+ guild: Optional[discord.Guild]
+ channel: Any
+
+ def __init__(self, __ctx: Union[discord.Interaction, commands.Context]) -> None:
+ """
+ Converts a command context or app command context into a HybridContext
+
+ Parameters
+ ----------
+ :param __ctx: The command context
+ """
+ self.author = __ctx.user if isinstance(__ctx, discord.Interaction) else __ctx.author
+ self.is_developer = (self.author.id == Config.DEVELOPER_USER_ID)
+ self.guild = __ctx.guild
+ self.channel = __ctx.channel
+
+
+
+def _validate_group(ctx: HybridContext, group: List[PermissionGate]) -> bool:
+ required: List[bool] = []
+ optional: List[bool] = []
+
+ for gate in group:
+ passFlag: bool = False
+
+ match gate['requirement']['query']:
+ case 'is_developer':
+ passFlag = ((gate['requirement']['type'] == 'wl') and ctx.is_developer)
+
+ case 'has_role':
+ if ctx.guild and isinstance(ctx.author, discord.Member):
+ passFlag = (gate['requirement']['type'] == 'wl') and any(
+ gate['requirement']['value'] in [role.id, role.name]
+ for role in ctx.author.roles
+ )
+
+ case 'has_permission':
+ if ctx.guild and isinstance(ctx.author, discord.Member):
+ passFlag = (gate['requirement']['type'] == 'wl') and getattr(
+ ctx.author.guild_permissions,
+ str(gate['requirement']['value'])
+ )
+
+ case 'in_channel':
+ if ctx.guild and isinstance(ctx.author, discord.Member):
+ passFlag = (gate['requirement']['type'] == 'wl') and (
+ gate['requirement']['value'] == ctx.channel.id
+ )
+
+ case 'in_guild':
+ if ctx.guild and isinstance(ctx.author, discord.Member):
+ passFlag = (gate['requirement']['type'] == 'wl') and (
+ gate['requirement']['value'] in
+ [ctx.guild.id, ctx.guild.name]
+ )
+
+ case 'minimum_role':
+ if ctx.guild and gate['requirement']['value'] and isinstance(ctx.author, discord.Member):
+ role = ctx.guild.get_role(int(gate['requirement']['value']))
+ passFlag = bool((gate['requirement']['type'] == 'wl') and role and (
+ ctx.author.top_role >= role
+ ))
+
+ if gate['type'] == 'required':
+ required.append(passFlag)
+ else:
+ optional.append(passFlag)
+
+ return (
+ (((len(required) > 0) and all(required)) or True)
+ and
+ (((len(optional) > 0) and any(optional)) or True)
+ )
+
+
+
+def validate(__ctx: Union[discord.Interaction, commands.Context], *gates: Union[PermissionGate, Sequence[PermissionGate]]) -> bool:
+ """
+ Validates a members access to a command
+
+ Parameters
+ ----------
+ :param __ctx: Command Context [legacy and app command supported]
+ :param gates: List of permission gates
+ """
+ try:
+ ctx = HybridContext(__ctx)
+ grouped: List[List[PermissionGate]] = [[]]
+
+ # Separate grouped and ungrouped
+ for gate in gates:
+ if isinstance(gate, Sequence):
+ grouped.append(list(gate))
+ else:
+ grouped[0].append(gate)
+
+ # Validate
+ for gate in grouped:
+ if len(gate) > 0:
+ passFlag = _validate_group(ctx, gate)
+ if not passFlag:
+ return False
+
+ return True
+ except Exception as e:
+ raise commands.CheckFailure(f'{e}') from e
+
+
+class Protected:
+ @staticmethod
+ def app(*clauses: Union[PermissionGate, Sequence[PermissionGate]]):
+ """
+ Protect app command usage
+
+ Unclaused permission gates are treated as one clause
+ All clauses must pass for the user to be allowed access to the command
+
+ Parameters
+ ----------
+ :param *: Permission gates or clausees of permission gates
+ """
+ async def predicate(interaction):
+ return validate(interaction, *clauses)
+ return app_commands.check(predicate)
+
+ @staticmethod
+ def legacy(*clauses: Union[PermissionGate, Sequence[PermissionGate]]):
+ """
+ Protect legacy command usage
+
+ Unclaused permission gates are treated as one clause
+ All clauses must pass for the user to be allowed access to the command
+
+ Parameters
+ ----------
+ :param *: Permission gates or clausees of permission gates
+ """
+ def predicate(ctx):
+ return validate(ctx, *clauses)
+ return commands.check(predicate)
+
+
+
+class PermissionPreset:
+ """Permission Presets for repeatedly used permissions"""
+
+ Developer: PermissionGate = {
+ 'type': 'required',
+ 'requirement': {
+ 'type': 'wl',
+ 'query': 'is_developer',
+ 'value': None
+ }
+ }
+ Admin: PermissionGate = {
+ 'type': 'required',
+ 'requirement': PermissionRequirement(
+ type = 'wl',
+ query = 'has_permission',
+ value = 'administrator'
+ )
+ }
+ WithinServer: PermissionGate = {
+ 'type': 'required',
+ 'requirement': PermissionRequirement(
+ type = 'wl',
+ query = 'in_guild',
+ value = Config.GUILD_ID
+ )
+ }
+ Is_Member: PermissionGate = {
+ 'type': 'required',
+ 'requirement': PermissionRequirement(
+ type = 'wl',
+ query = 'minimum_role',
+ value = 'Community'
+ )
+ }
+
+ def __init__(self, *args, **kwargs) -> NoReturn:
+ raise NotImplementedError('Non Instantiable')
diff --git a/src/cogs/dev_command.py b/src/cogs/dev_command.py
new file mode 100644
index 0000000..32ae351
--- /dev/null
+++ b/src/cogs/dev_command.py
@@ -0,0 +1,51 @@
+from discord.ext import commands
+from src.check import Protected, PermissionPreset
+
+class DevCommands(commands.Cog, command_attrs = dict(hidden = True), group_extras = dict(hidden = True)):
+ """hidden"""
+
+ client: commands.Bot
+
+
+ def __init__(self, client: commands.Bot):
+ self.client = client
+
+
+ @commands.Cog.listener()
+ async def on_ready(self):
+ print('Developer Commands UP')
+
+
+ @commands.command()
+ @Protected.legacy(PermissionPreset.Developer)
+ async def syncSlashCommands(self, ctx: commands.Context):
+ try:
+ Sync = await self.client.tree.sync()
+ print(f'Synced {len(Sync)} command(s)')
+ await ctx.reply(f'{ctx.author.mention} Synced {len(Sync)} command(s)')
+ except Exception as e:
+ print(e)
+
+ @commands.command()
+ @Protected.legacy(PermissionPreset.Developer)
+ async def load(self, ctx: commands.Context, extension):
+ await self.client.load_extension(f'.cogs.{extension}', package = 'src')
+ await ctx.message.add_reaction('ā
')
+
+ @commands.command()
+ @Protected.legacy(PermissionPreset.Developer)
+ async def unload(self, ctx: commands.Context, extension):
+ await self.client.unload_extension(f'.cogs.{extension}', package = 'src')
+ await ctx.message.add_reaction('ā
')
+
+ @commands.command()
+ @Protected.legacy(PermissionPreset.Developer)
+ async def reload(self, ctx: commands.Context, extension):
+ await self.client.unload_extension(f'.cogs.{extension}', package = 'src')
+ await self.client.load_extension(f'.cogs.{extension}', package = 'src')
+ await ctx.message.add_reaction('ā
')
+
+
+
+async def setup(client: commands.Bot):
+ await client.add_cog(DevCommands(client))
\ No newline at end of file
diff --git a/src/cogs/error_manager.py b/src/cogs/error_manager.py
new file mode 100644
index 0000000..cb2a3c4
--- /dev/null
+++ b/src/cogs/error_manager.py
@@ -0,0 +1,88 @@
+import math
+import traceback
+import discord
+from discord.ext import commands
+
+from src.utils import Error, Embeds
+from src.config import Config
+
+
+class ErrorManager(commands.Cog):
+ """hidden"""
+
+ client: commands.Bot
+
+
+ def __init__(self, client: commands.Bot):
+ self.client = client
+
+
+ @commands.Cog.listener()
+ async def on_ready(self):
+ print('Error Manager UP')
+
+ @commands.Cog.listener()
+ async def on_command_error(self, ctx: commands.Context, error):
+ prefix = Config.COMMAND_PREFIX
+ embed = Embeds()
+ embed.color = discord.Color.dark_red()
+
+ if isinstance(error, Error):
+ embed.description = error.description
+ for field in error.fields.values():
+ field['inline'] = ('inline' in field) and field['inline'] or False
+ embed.add_field(**field)
+
+ elif isinstance(error, commands.CommandNotFound):
+ embed.description= f'Command not found! `{prefix}help` for a list of commands!'
+
+ elif isinstance(error, commands.BotMissingPermissions):
+ missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in error.missing_permissions]
+ if len(missing) > 2:
+ fmt = '{}, and {}'.format("**, **".join(missing[:-1]), missing[-1])
+ else:
+ fmt = ' and '.join(missing)
+ embed.description = 'I need the **{}** permission(s) to run this command.'.format(fmt)
+
+ elif isinstance(error, commands.DisabledCommand):
+ embed.description = 'The command has been disabled!'
+
+ elif isinstance(error, commands.CommandOnCooldown):
+ embed.description = 'This command is on cooldown, please retry in {}s.'.format(math.ceil(error.retry_after))
+
+ elif isinstance(error, commands.MissingPermissions):
+ missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in error.missing_permissions]
+ if len(missing) > 2:
+ fmt = '{}, and {}'.format("**, **".join(missing[:-1]), missing[-1])
+ else:
+ fmt = ' and '.join(missing)
+ embed.description = 'You need the **{}** permission(s) to use this command.'.format(fmt)
+
+ elif isinstance(error, commands.NoPrivateMessage):
+ try:
+ await ctx.author.send('This command cannot be used in direct messages.')
+ except discord.Forbidden:
+ pass
+
+ elif isinstance(error, commands.CheckFailure):
+ embed.description = 'You do not have permission to use this command!'
+
+ elif isinstance(error, commands.UserInputError):
+ embed.description = f'Invalid arguments! `{prefix}help` for a list of commands!'
+ embed.add_field(
+ name = 'Invalid Arguments',
+ value = error,
+ inline = False
+ )
+
+ elif isinstance(error, commands.CommandError): # Custom Error
+ embed.description = str(error)
+
+ await ctx.reply(ctx.author.mention, embed = embed, ephemeral = True)
+ print(error)
+ traceback.print_exc()
+
+
+
+async def setup(client: commands.Bot):
+ await client.add_cog(ErrorManager(client))
diff --git a/src/cogs/help_command.py b/src/cogs/help_command.py
new file mode 100644
index 0000000..b98e481
--- /dev/null
+++ b/src/cogs/help_command.py
@@ -0,0 +1,156 @@
+import discord
+from discord import app_commands
+from discord.ext import commands
+
+from src.utils import Embeds, Error, ViewPageScroller
+from typing import Optional, List
+
+
+class Help(commands.Cog):
+ """
+ Sends this help message
+ """
+
+ client: commands.Bot
+
+
+ def __init__(self, client: commands.Bot):
+ self.client = client
+ client.remove_command('help')
+
+
+ @commands.Cog.listener()
+ async def on_ready(self):
+ print('Help command UP')
+
+
+ @commands.hybrid_command(
+ name = 'help',
+ description = 'Sends the Help Command',
+ with_app_command = True,
+ required = True
+ )
+ @app_commands.describe(
+ query = 'Module\'s or Command\'s name'
+ )
+ @commands.cooldown(1, 15, commands.BucketType.user)
+ async def help(self, ctx: commands.Context, *, query: Optional[str] = None):
+ """
+ **Help command of the bot**
+
+ > **Arguments**
+ ```
+ query | string | default=None
+ ```
+
+ > **Example**
+ ```
+ /help
+ /help ping
+ ```
+ """
+ embed_data: dict[str, list[list]] = {}
+ page_limit = 1
+
+ async def load_cog_commands(cog, cmds):
+ if str(self.client.cogs[cog].__doc__).startswith('hidden'): return
+ if not cog in embed_data:
+ embed_data[cog] = [[]]
+
+ for i in cmds:
+ if i.hidden or str(i.help).startswith('hidden') or not await i.can_run(ctx): continue
+ if len(embed_data[cog][-1]) >= page_limit:
+ embed_data[cog].append([i])
+ else:
+ embed_data[cog][-1].append(i)
+
+
+ if not query:
+ for cogname in self.client.cogs:
+ if str(self.client.cogs[cogname].__doc__).startswith('hidden'): continue # Skip hidden cogs
+
+ cog = self.client.get_cog(cogname)
+ if cog:
+ await load_cog_commands(cogname, cog.walk_commands())
+
+ else:
+ query = ''.join(query)
+ for cogname in self.client.cogs:
+ if query.lower() == cogname.lower() and not str(self.client.cogs[cogname].__doc__).startswith('hidden'):
+ cog = self.client.get_cog(cogname)
+ if not cog: continue
+
+ await load_cog_commands(cogname, cog.walk_commands())
+ break
+
+ elif not str(self.client.cogs[cogname].__doc__).startswith('hidden'):
+ cog = self.client.get_cog(cogname)
+ if not cog: continue
+
+ for cmd in cog.walk_commands():
+ if query.lower().strip() in cmd.qualified_name.lower().strip():
+ await load_cog_commands(cogname, [cmd])
+
+
+ if not embed_data:
+ raise Error(
+ description = f'Unable to locate module or command' + ((query and f'[{query}]') or '')
+ )
+
+ else: #clean up
+ cleaned = []
+ for cogName, pages in embed_data.items():
+ for page in pages:
+ cleaned.append([cogName, list(page)])
+
+
+ #Setup embed screen navigation
+ def load_page(data: tuple):
+ if not self.client.user:
+ raise Error('Client not logged in!')
+
+ embed = Embeds(
+ title = 'Commands',
+ description = f'Use `/help ` or `/help ` for more information about that module or command\n\n**{data[0]}\'s commands**\n\u200d'
+ )
+ embed.color = discord.Color.blurple()
+ embed.set_author(name = self.client.user.name, icon_url = self.client.user.display_avatar)
+
+ for i in data[1]:
+ embed.add_field(
+ name = f'/{i.qualified_name}',
+ value = f'{str(i.help).lstrip().rstrip()}\nā',
+ inline = False
+ )
+
+ return embed
+
+ scroll_embed = ViewPageScroller(
+ ownerid = ctx.author.id,
+ load_page = load_page,
+ pages = cleaned,
+ timeout = 180
+ )
+
+ await scroll_embed.send_message(ctx)
+
+
+ @help.autocomplete('query')
+ async def help_autocomplete(self, ctx, current: str) -> List[app_commands.Choice[str]]:
+ data = []
+ command_names = []
+ for i in self.client.commands:
+ if i.hidden or str(i.help).startswith('hidden'): continue
+ command_names += list(i.aliases) + [i.name]
+
+ for name in [cog for cog in self.client.cogs if not str(self.client.cogs[cog].__doc__).startswith('hidden')] + command_names:
+ if current.lower() in name.lower():
+ data.append(app_commands.Choice(
+ name = name,
+ value = name
+ ))
+ return data[0:24]
+
+
+async def setup(client):
+ await client.add_cog(Help(client))
diff --git a/src/cogs/invite_manager.py b/src/cogs/invite_manager.py
new file mode 100644
index 0000000..a7d6fc6
--- /dev/null
+++ b/src/cogs/invite_manager.py
@@ -0,0 +1,40 @@
+import discord
+from discord.ext import commands
+from typing import Optional
+
+from src.utils import Embeds, Error
+from src.config import Config
+
+
+class Invite(commands.Cog):
+ """hidden
+
+ Handles welcoming new members
+ """
+
+ client: commands.Bot
+
+
+ def __init__(self, client: commands.Bot) -> None:
+ self.client = client
+
+
+ @commands.Cog.listener()
+ async def on_ready(self):
+ print('Invite Manager UP')
+
+
+ @commands.Cog.listener()
+ async def on_member_join(self, member: discord.Member):
+ guild = self.client.get_guild(Config.GUILD_ID)
+ welcome_channel = guild and guild.get_channel(Config.WELCOME_CHANNEL_ID)
+
+ if welcome_channel and isinstance(welcome_channel, discord.TextChannel):
+ await welcome_channel.send(member.mention, embed = Embeds(
+ title = member.global_name,
+ description = f'**Welcome to the server!** š\n\nDon\'t forget to `git checkout `<#{Config.RULE_CHANNEL_ID}>'
+ ))
+
+
+async def setup(client: commands.Bot):
+ await client.add_cog(Invite(client))
diff --git a/src/cogs/misc_command.py b/src/cogs/misc_command.py
new file mode 100644
index 0000000..93ad041
--- /dev/null
+++ b/src/cogs/misc_command.py
@@ -0,0 +1,161 @@
+import emoji
+import discord
+from discord import app_commands
+from discord.ext import commands
+from typing import Optional
+
+from src.utils import Embeds, Error
+from src.config import Config
+
+
+class Misc(commands.Cog):
+ """Miscellaneous commands"""
+
+ client: commands.Bot
+
+
+ def __init__(self, client):
+ self.client = client
+
+
+ @commands.Cog.listener()
+ async def on_ready(self):
+ print('Miscellaneous Commands UP')
+
+
+ @commands.hybrid_command(
+ name = 'ping',
+ description = 'Print out the current ping',
+ with_app_command = True,
+ required = True
+ )
+ @commands.cooldown(1, 15)
+ async def ping(self, ctx: commands.Context):
+ """
+ **Calculates the bot's latency**
+
+ > **Example**
+ ```
+ /ping
+ ```
+ """
+ replication_rate = 60
+ latency = sum([self.client.latency for _ in range(replication_rate)])/replication_rate
+ await ctx.reply(f'{ctx.author.mention}', embed = Embeds(
+ title = 'Pong! :ping_pong:',
+ description = f'{round(latency*1000, 2)}ms',
+ color = discord.Color.purple()
+ ))
+
+
+ @commands.hybrid_command(
+ name = 'links',
+ description = 'Returns the project links',
+ with_app_command = True,
+ )
+ @commands.cooldown(1, 10, commands.BucketType.channel)
+ async def links(self, ctx: commands.Context):
+ """
+ **Returns the project links**
+
+ > **Example**
+ ```
+ /links
+ ```
+ """
+ await ctx.reply(ctx.author.mention, embed = Embeds(
+ title = 'Project Links',
+ description = f'[**Github Organization**](https://github.com/python-thread)\n[**Documentation**](https://thread.ngjx.org)'
+ ))
+
+
+ @commands.hybrid_command(
+ name = 'getprefix',
+ description = 'Returns the current in-use command prefix',
+ with_app_command = True,
+ required = True,
+ aliases = ['prefix']
+ )
+ @commands.cooldown(1, 15, commands.BucketType.channel)
+ async def getprefix(self, ctx: commands.Context):
+ """
+ **Returns the current in-use command prefix**
+
+ > **Aliases**
+ ```
+ prefix, getprefix
+ ```
+
+ > **Example**
+ ```
+ /prefix
+ /getprefix
+ ```
+ """
+ await ctx.reply(ctx.author.mention, embed = Embeds(
+ title = 'Prefix',
+ description = str(Config.COMMAND_PREFIX)
+ ))
+
+
+ @commands.hybrid_command(
+ name = 'react',
+ description = 'Reacts to the message with specified reaction!',
+ with_app_command = True,
+ aliases = ['addreaction'],
+ required = True
+ )
+ @app_commands.describe(
+ reaction = 'What reaction you would like to add',
+ messageid = 'Which message you would like to add to'
+ )
+ @commands.cooldown(1, 5, commands.BucketType.user)
+ async def addreaction(self, ctx: commands.Context, reaction: str, messageid: Optional[str] = None):
+ """
+ **Reacts to the message with specified reaction!**
+
+ > **Arguments**
+ ```
+ reaction | string | required
+ messageid | string | default = Latest message in channel
+ ```
+
+ > **Example**
+ ```
+ /react [str] (str)
+ /react :ping_pong: 12345
+ /react :ping_pong: https://.../12345
+ ```
+ """
+ if not ctx.guild or not ctx.message.guild: return
+
+ if not emoji.is_emoji(reaction):
+ for emojiinfo in ctx.message.guild.emojis:
+ if emojiinfo.name == reaction:
+ reaction = emojiinfo.animated and f'' or f'<:{emojiinfo.name}:{emojiinfo.id}>'
+ break
+
+ if messageid:
+ id = messageid.startswith('https:') and messageid.split('/')[len(messageid.split('/')) - 1] or messageid
+ elif isinstance(ctx.message.channel, discord.TextChannel):
+ id = str(ctx.message.channel.last_message_id)
+ else:
+ raise Error('Unsupported channel type')
+
+ message = await ctx.message.channel.fetch_message(int(id))
+ await message.add_reaction(reaction)
+
+ embed = Embeds(title = 'Success')
+ embed.set_author(name = ctx.author.name, icon_url = ctx.author.display_avatar)
+ embed.add_field(name = "Emoji", value = f'`{reaction}`', inline = False)
+ embed.add_field(
+ name = "Message ID",
+ value = f'https://discord.com/channels/{ctx.guild.id}/{ctx.message.channel.id}/{id}',
+ inline = False
+ )
+
+ await ctx.reply(ctx.author.mention, embed = embed, ephemeral = True)
+
+
+async def setup(client: commands.Bot):
+ await client.add_cog(Misc(client))
diff --git a/src/config.py b/src/config.py
new file mode 100644
index 0000000..87da669
--- /dev/null
+++ b/src/config.py
@@ -0,0 +1,21 @@
+import os
+from dotenv import load_dotenv
+from typing import NoReturn, Optional
+
+load_dotenv()
+
+class Config:
+ """Configurations"""
+
+ BOT_TOKEN: Optional[str] = os.getenv('BOT_TOKEN')
+
+ GUILD_ID: int = 1184345962412507157
+ RULE_CHANNEL_ID: int = 1184494394682916954
+ WELCOME_CHANNEL_ID: int = 1184345962861314050
+
+ COMMAND_PREFIX: str = '$'
+ DEVELOPER_USER_ID: int = 470966329931857921
+
+ def __init__(self, *args, **kwargs) -> NoReturn:
+ raise NotImplementedError('Not instantiable')
+
\ No newline at end of file
diff --git a/src/utils.py b/src/utils.py
new file mode 100644
index 0000000..98f864d
--- /dev/null
+++ b/src/utils.py
@@ -0,0 +1,133 @@
+import discord
+from discord.ext import commands
+from datetime import datetime
+
+
+class Embeds(discord.Embed):
+ def __init__(self, color = None, **kwargs):
+ super().__init__(**kwargs)
+ self.timestamp = datetime.utcnow()
+ self.color = color or discord.Color.from_rgb(0,191,255) #Deepskyblue
+
+
+class Error(commands.CommandError):
+ def __init__(self, title: str = 'Error', description: str = '-', fields: dict = {}, **kwargs):
+ self.title = title
+ self.description = description
+ self.fields = fields
+ super().__init__(**kwargs)
+
+
+class ViewPageScroller(discord.ui.View):
+ def __init__(self, *, ownerid: int, load_page, pages: list = [], timeout = None):
+ self.ownerid = ownerid
+ self.pages = pages
+ self.current_page = 1
+ self.load_page = load_page
+
+ super().__init__(
+ timeout = timeout
+ )
+
+ async def send_message(self, ctx, *args, **kwargs):
+ self.message = await ctx.send(view = self, *args, **kwargs)
+ await self.update_message()
+
+ async def update_message(self):
+ self.update_buttons()
+ await self.message.edit(embed = self.create_embed(), view = self)
+
+ def update_buttons(self):
+ if (self.current_page == 1) and (self.current_page != len(self.pages)):
+ self.first_page_button.disabled = True
+ self.next_button.disabled = False
+ self.prev_button.disabled = True
+ self.last_page_button.disabled = False
+
+ self.first_page_button.style = discord.ButtonStyle.gray
+ self.next_button.style = discord.ButtonStyle.primary
+ self.prev_button.style = discord.ButtonStyle.gray
+ self.last_page_button.style = discord.ButtonStyle.green
+
+ elif (self.current_page > 1) and (self.current_page == len(self.pages)):
+ self.first_page_button.disabled = False
+ self.next_button.disabled = True
+ self.prev_button.disabled = False
+ self.last_page_button.disabled = True
+
+ self.first_page_button.style = discord.ButtonStyle.green
+ self.next_button.style = discord.ButtonStyle.gray
+ self.prev_button.style = discord.ButtonStyle.primary
+ self.last_page_button.style = discord.ButtonStyle.gray
+
+ elif (self.current_page == 1) and (self.current_page == len(self.pages)):
+ self.first_page_button.disabled = True
+ self.next_button.disabled = True
+ self.prev_button.disabled = True
+ self.last_page_button.disabled = True
+
+ self.first_page_button.style = discord.ButtonStyle.gray
+ self.next_button.style = discord.ButtonStyle.gray
+ self.prev_button.style = discord.ButtonStyle.gray
+ self.last_page_button.style = discord.ButtonStyle.gray
+
+ else:
+ self.first_page_button.disabled = False
+ self.next_button.disabled = False
+ self.prev_button.disabled = False
+ self.last_page_button.disabled = False
+
+ self.first_page_button.style = discord.ButtonStyle.green
+ self.next_button.style = discord.ButtonStyle.primary
+ self.prev_button.style = discord.ButtonStyle.primary
+ self.last_page_button.style = discord.ButtonStyle.green
+
+ def create_embed(self) -> discord.Embed:
+ data = self.load_page(self.pages[self.current_page - 1])
+ data.set_footer(text=f'Page [{self.current_page}/{len(self.pages)}]\nā')
+ return data
+
+
+ @discord.ui.button(label="|<", style=discord.ButtonStyle.green)
+ async def first_page_button(self, interaction: discord.Interaction, button: discord.ui.Button):
+ if interaction.user.id != self.ownerid: return
+ await interaction.response.defer()
+ self.current_page = 1
+
+ await self.update_message()
+
+ @discord.ui.button(label="<", style=discord.ButtonStyle.primary)
+ async def prev_button(self, interaction:discord.Interaction, button: discord.ui.Button):
+ if interaction.user.id != self.ownerid: return
+ await interaction.response.defer()
+ self.current_page -= 1
+
+ await self.update_message()
+
+ @discord.ui.button(label=">", style=discord.ButtonStyle.primary)
+ async def next_button(self, interaction:discord.Interaction, button: discord.ui.Button):
+ if interaction.user.id != self.ownerid: return
+ await interaction.response.defer()
+ self.current_page += 1
+
+ await self.update_message()
+
+ @discord.ui.button(label=">|", style=discord.ButtonStyle.green)
+ async def last_page_button(self, interaction:discord.Interaction, button: discord.ui.Button):
+ if interaction.user.id != self.ownerid: return
+ await interaction.response.defer()
+ self.current_page = len(self.pages)
+
+ await self.update_message()
+
+ @discord.ui.button(label="Done", style=discord.ButtonStyle.blurple)
+ async def done(self, interaction:discord.Interaction, button: discord.ui.Button):
+ last = self.create_embed()
+ last.set_footer(text=f'Page [{self.current_page}/{len(self.pages)}]\nDisabled due to user\nā')
+ await self.message.edit(embed = last, view = None)
+ self.stop()
+
+ async def on_timeout(self) -> None:
+ last = self.create_embed()
+ last.set_footer(text=f'Page [{self.current_page}/{len(self.pages)}]\nDisabled due to timeout\nā')
+ await self.message.edit(embed = last, view = None)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_placeholder.py b/tests/test_placeholder.py
new file mode 100644
index 0000000..d62af70
--- /dev/null
+++ b/tests/test_placeholder.py
@@ -0,0 +1,3 @@
+def test_palceholder():
+ assert True
+
\ No newline at end of file