├── .github
├── FUNDING.yml
└── workflows
│ └── publish.yml
├── .gitignore
├── .readthedocs.yaml
├── LICENSE
├── README.md
├── docs
├── index.md
└── requirements.txt
├── images
└── 1337x.png
├── mkdocs.yml
├── py1337x
├── __init__.py
├── config.py
├── models.py
├── parser.py
├── py1337x.py
├── types
│ ├── __init__.py
│ ├── category.py
│ ├── order.py
│ └── sort.py
└── utils.py
├── pyproject.toml
└── uv.lock
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 | custom: ['https://buymeacoffee.com/hemantapkh']
14 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish Package
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | publish:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | id-token: write
12 | steps:
13 |
14 | - name: Clone the repo
15 | uses: actions/checkout@v4
16 |
17 | - name: Set up Python
18 | uses: actions/setup-python@v5
19 | with:
20 | python-version: '3.x'
21 |
22 | - name: Install dependencies
23 | run: |
24 | pip install build
25 |
26 | - name: Build distribution
27 | run: python -m build
28 |
29 | - name: Publish the package
30 | uses: pypa/gh-action-pypi-publish@v1.12.4
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # UV
98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | #uv.lock
102 |
103 | # poetry
104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105 | # This is especially recommended for binary packages to ensure reproducibility, and is more
106 | # commonly ignored for libraries.
107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108 | #poetry.lock
109 |
110 | # pdm
111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112 | #pdm.lock
113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114 | # in version control.
115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116 | .pdm.toml
117 | .pdm-python
118 | .pdm-build/
119 |
120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121 | __pypackages__/
122 |
123 | # Celery stuff
124 | celerybeat-schedule
125 | celerybeat.pid
126 |
127 | # SageMath parsed files
128 | *.sage.py
129 |
130 | # Environments
131 | .env
132 | .venv
133 | env/
134 | venv/
135 | ENV/
136 | env.bak/
137 | venv.bak/
138 |
139 | # Spyder project settings
140 | .spyderproject
141 | .spyproject
142 |
143 | # Rope project settings
144 | .ropeproject
145 |
146 | # mkdocs documentation
147 | /site
148 |
149 | # mypy
150 | .mypy_cache/
151 | .dmypy.json
152 | dmypy.json
153 |
154 | # Pyre type checker
155 | .pyre/
156 |
157 | # pytype static type analyzer
158 | .pytype/
159 |
160 | # Cython debug symbols
161 | cython_debug/
162 |
163 | # PyCharm
164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166 | # and can be added to the global gitignore or merged into this file. For a more nuclear
167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168 | #.idea/
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 | build:
3 | os: ubuntu-22.04
4 | tools:
5 | python: "3.11"
6 | # You can also specify other tool versions:
7 | # nodejs: "16"
8 |
9 | # Build documentation with Mkdocs
10 | mkdocs:
11 | configuration: mkdocs.yml
12 |
13 | # Dependencies required to build your docs
14 | python:
15 | install:
16 | - requirements: docs/requirements.txt
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Hemanta Pokharel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | The ultimate Python API wrapper for 1337x.
21 |
22 |
23 |
24 | ## Table of Contents
25 | - [Installation](#installation)
26 | - [Getting Started](#getting-started)
27 | - [Examples](#examples)
28 | - [Asynchronous Usage](#asynchronous-usage)
29 | - [Documentation](#documentation)
30 | - [Contributing](#contributing)
31 | - [License](#license)
32 |
33 | ## Installation
34 | Install via [PyPi](https://www.pypi.org/project/1337x):
35 | ```bash
36 | pip install 1337x
37 | ```
38 |
39 | Or install from the source:
40 | ```bash
41 | pip install git+https://github.com/hemantapkh/1337x
42 | ```
43 |
44 |
45 | ## Examples
46 |
47 | ### Searching Torrents
48 | ```python
49 | import py1337x
50 |
51 | torrents = py1337x.Py1337x()
52 |
53 | # Basic search
54 | results = torrents.search('ubuntu', page=1)
55 | for result in results.items:
56 | print(f"Title={result.name} Seeders={result.seeders}")
57 |
58 | # Search with sorting by seeders
59 | results = torrents.search('vlc', sort_by=py1337x.sort.SEEDERS, category=py1337x.category.APPS)
60 | print(results)
61 |
62 | # Get today's trending torrents
63 | results = torrents.trending()
64 | print(results)
65 | ```
66 |
67 | ### Getting Torrent Information
68 | To get magnetlink and other information of the torrent.
69 | ```python
70 | # Getting info the the first result of the above search
71 | torrent_id = results.items[0].torrent_id
72 | info = torrents.info(torrent_id=torrent_id)
73 | print(info)
74 |
75 | # Convert the result to dictionary
76 | info_dict = info.to_dict()
77 | print(info_dict)
78 | ```
79 |
80 | ## Asynchronous Usage
81 | For asynchronous usage, all functionalities are the same; use `AsyncPy1337x` instead of `Py1337x`:
82 |
83 | ```python
84 | import asyncio
85 | from py1337x import AsyncPy1337x
86 |
87 | async def main():
88 | torrents = AsyncPy1337x()
89 | results = await torrents.search('vlc media player')
90 | print(results)
91 |
92 | asyncio.run(main())
93 | ```
94 |
95 | ## Documentation
96 |
97 | The detailled documentation of the project is available [here](https://1337x.readthedocs.org/en/latest/).
98 |
99 | ## Contributing
100 |
101 | Any contributions you make are **greatly appreciated**.
102 |
103 | *Thanks to every [contributors](https://github.com/hemantapkh/1337x/graphs/contributors) who have contributed in this project.*
104 |
105 | ## License
106 |
107 | Distributed under the MIT License. See [LICENSE](https://github.com/hemantapkh/1337x/blob/main/LICENSE) for more information.
108 |
109 | -----
110 | Author/Maintainer: [Hemanta Pokharel](https://github.com/hemantapkh/)
111 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ## Installation
24 |
25 | Install with pip
26 |
27 | ```console
28 | pip install 1337x
29 | ```
30 |
31 | Install from source
32 |
33 | ```console
34 | pip install git+https://github.com/hemantapkh/1337x
35 | ```
36 |
37 | ## Detailled Documentation
38 |
39 | ### ::: py1337x.Py1337x
40 | ### ::: py1337x.AsyncPy1337x
41 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | babel==2.17.0
2 | certifi==2025.1.31
3 | charset-normalizer==3.4.1
4 | click==8.1.8
5 | colorama==0.4.6
6 | ghp-import==2.1.0
7 | griffe==1.5.7
8 | idna==3.10
9 | Jinja2==3.1.6
10 | Markdown==3.7
11 | MarkupSafe==3.0.2
12 | mergedeep==1.3.4
13 | mkdocs==1.6.1
14 | mkdocs-autorefs==1.3.1
15 | mkdocs-get-deps==0.2.0
16 | mkdocs-material==9.6.4
17 | mkdocs-material-extensions==1.3.1
18 | mkdocstrings==0.28.1
19 | mkdocstrings-python==1.15.0
20 | packaging==24.2
21 | paginate==0.5.7
22 | pathspec==0.12.1
23 | platformdirs==4.3.6
24 | Pygments==2.19.1
25 | pymdown-extensions==10.14.3
26 | python-dateutil==2.9.0.post0
27 | PyYAML==6.0.2
28 | pyyaml_env_tag==0.1
29 | regex==2024.11.6
30 | requests==2.32.3
31 | six==1.17.0
32 | urllib3==2.3.0
33 | watchdog==6.0.0
34 |
--------------------------------------------------------------------------------
/images/1337x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hemantapkh/1337x/3463a2fdbd308b27ed33d339010c2c9799784a23/images/1337x.png
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: PY1337X
2 | site_description: API wrapper of 1337x
3 | site_url: https://hemantapkh.com
4 | repo_name: hemantapkh/1337x
5 | repo_url: https://github.com/hemantapkh/1337x
6 | theme:
7 | name: "material"
8 | palette:
9 | - media: "(prefers-color-scheme)"
10 | toggle:
11 | icon: material/lightbulb-auto
12 | name: Switch to light mode
13 | - media: '(prefers-color-scheme: light)'
14 | scheme: default
15 | primary: teal
16 | accent: amber
17 | toggle:
18 | icon: material/lightbulb
19 | name: Switch to dark mode
20 | - media: '(prefers-color-scheme: dark)'
21 | scheme: slate
22 | primary: teal
23 | accent: amber
24 | toggle:
25 | icon: material/lightbulb-outline
26 | name: Switch to system preference
27 |
28 | icon:
29 | repo: fontawesome/brands/github-alt
30 | language: en
31 |
32 | features:
33 | - content.code.annotate
34 | - content.code.copy
35 | - content.code.select
36 | - content.footnote.tooltips
37 | - content.tabs.link
38 | - content.tooltips
39 | - navigation.footer
40 | - navigation.indexes
41 | - navigation.instant
42 | - navigation.instant.prefetch
43 | - navigation.instant.preview
44 | - navigation.instant.progress
45 | - navigation.path
46 | - navigation.tabs
47 | - navigation.tabs.sticky
48 | - navigation.top
49 | - navigation.tracking
50 | - search.highlight
51 | - search.share
52 | - search.suggest
53 | - toc.follow
54 |
55 | plugins:
56 | - search
57 | - mkdocstrings:
58 | handlers:
59 | python:
60 | options:
61 | members_order: source
62 | merge_init_into_class: true
63 | docstring_options:
64 | ignore_init_summary: true
65 |
--------------------------------------------------------------------------------
/py1337x/__init__.py:
--------------------------------------------------------------------------------
1 | from py1337x.py1337x import Py1337x # noqa
2 | from py1337x.py1337x import AsyncPy1337x # noqa
3 | from py1337x.types import category, sort, order # noqa
4 |
--------------------------------------------------------------------------------
/py1337x/config.py:
--------------------------------------------------------------------------------
1 | default_base_url: str = "https://www.1337x.to"
2 |
--------------------------------------------------------------------------------
/py1337x/models.py:
--------------------------------------------------------------------------------
1 | from dataclasses import asdict, dataclass
2 | from typing import List, Optional
3 |
4 |
5 | @dataclass
6 | class TorrentItem:
7 | """Represents a single torrent item in the search results."""
8 |
9 | name: str
10 | torrent_id: str
11 | url: str
12 | seeders: str
13 | leechers: str
14 | size: str
15 | time: str
16 | uploader: str
17 | uploader_link: str
18 |
19 | def to_dict(self):
20 | return asdict(self)
21 |
22 |
23 | @dataclass
24 | class TorrentResult:
25 | """Represents the result of a torrent search."""
26 |
27 | items: List[TorrentItem]
28 | current_page: int
29 | item_count: int
30 | page_count: int
31 |
32 | def to_dict(self):
33 | return asdict(self)
34 |
35 |
36 | @dataclass
37 | class TorrentInfo:
38 | """Represents information about a torrent."""
39 |
40 | name: Optional[str]
41 | short_name: Optional[str]
42 | description: Optional[str]
43 | category: Optional[str]
44 | type: Optional[str]
45 | genre: Optional[List[str]]
46 | language: Optional[str]
47 | size: Optional[str]
48 | thumbnail: Optional[str]
49 | images: Optional[List[str]]
50 | uploader: Optional[str]
51 | uploader_link: Optional[str]
52 | downloads: Optional[str]
53 | last_checked: Optional[str]
54 | date_uploaded: Optional[str]
55 | seeders: Optional[str]
56 | leechers: Optional[str]
57 | magnet_link: Optional[str]
58 | info_hash: Optional[str]
59 |
60 | def to_dict(self):
61 | return asdict(self)
62 |
--------------------------------------------------------------------------------
/py1337x/parser.py:
--------------------------------------------------------------------------------
1 | from bs4 import BeautifulSoup
2 | from requests import Response
3 |
4 | from py1337x import models
5 |
6 |
7 | def torrent_parser(response: Response, base_url: str, page: int = 1) -> models.TorrentResult:
8 | """
9 | Parse the response from a torrent result page.
10 |
11 | Args:
12 | response (Response): The HTTP response object.
13 | base_url (str): The base URL for the API.
14 | page (int): The current page number.
15 |
16 | Returns:
17 | The parsed search results.
18 | """
19 | soup = BeautifulSoup(response.content, "html.parser")
20 |
21 | torrent_list = soup.select('a[href*="/torrent/"]')
22 | seeders_list = soup.select("td.coll-2")
23 | leechers_list = soup.select("td.coll-3")
24 | size_list = soup.select("td.coll-4")
25 | time_list = soup.select("td.coll-date")
26 | uploader_list = soup.select("td.coll-5")
27 |
28 | last_page = soup.find("div", {"class": "pagination"})
29 |
30 | if not last_page:
31 | page_count = page
32 | else:
33 | try:
34 | page_count = int(last_page.findAll("a")[-1]["href"].split("/")[-2])
35 | except Exception:
36 | page_count = page
37 |
38 | items = []
39 |
40 | if torrent_list:
41 | for count, torrent in enumerate(torrent_list):
42 | name = torrent.getText().strip()
43 | torrent_id = torrent["href"].split("/")[2]
44 | link = base_url + torrent["href"]
45 | seeders = seeders_list[count].getText()
46 | leechers = leechers_list[count].getText()
47 | size = size_list[count].contents[0]
48 | time = time_list[count].getText()
49 | uploader = uploader_list[count].getText().strip()
50 | uploader_link = base_url + "/" + uploader + "/"
51 |
52 | items.append(
53 | models.TorrentItem(
54 | name=name,
55 | torrent_id=torrent_id,
56 | url=link,
57 | seeders=seeders,
58 | leechers=leechers,
59 | size=size,
60 | time=time,
61 | uploader=uploader,
62 | uploader_link=uploader_link,
63 | )
64 | )
65 |
66 | return models.TorrentResult(
67 | items=items, current_page=page or 1, item_count=len(torrent_list), page_count=page_count
68 | )
69 |
70 |
71 | def info_parser(response: Response, base_url: str) -> models.TorrentInfo:
72 | """
73 | Parse the response from a torrent information page.
74 |
75 | Args:
76 | response (Response): The HTTP response object.
77 | base_url (str): The base URL for the API.
78 |
79 | Returns:
80 | The parsed torrent information.
81 | """
82 | soup = BeautifulSoup(response.content, "html.parser")
83 |
84 | name = soup.find("div", {"class": "box-info-heading clearfix"})
85 | name = name.text.strip() if name else None
86 |
87 | short_name = soup.find("div", {"class": "torrent-detail-info"})
88 | short_name = short_name.find("h3").getText().strip() if short_name else None
89 |
90 | description = soup.find("div", {"class": "torrent-detail-info"})
91 | description = description.find("p").getText().strip() if description else None
92 |
93 | genre = soup.find("div", {"class": "torrent-category clearfix"})
94 | genre = [i.text.strip() for i in genre.find_all("span")] if genre else None
95 |
96 | thumbnail = soup.find("div", {"class": "torrent-image"})
97 | thumbnail = thumbnail.find("img")["src"] if thumbnail else None
98 |
99 | if thumbnail and not thumbnail.startswith("http"):
100 | if thumbnail.startswith("//"):
101 | thumbnail = "https:" + thumbnail
102 | else:
103 | thumbnail = base_url + thumbnail
104 |
105 | magnet_link = soup.select('a[href^="magnet"]')
106 | magnet_link = magnet_link[0]["href"] if magnet_link else None
107 |
108 | info_hash = soup.find("div", {"class": "infohash-box"})
109 | info_hash = info_hash.find("span").getText() if info_hash else None
110 |
111 | images = soup.find("div", {"class": "tab-pane active"})
112 | images = [i["src"] for i in images.find_all("img")] if images else None
113 |
114 | description_list = soup.find_all("ul", {"class": "list"})
115 |
116 | if len(description_list) > 2:
117 | first_list = description_list[1].find_all("li")
118 | second_list = description_list[2].find_all("li")
119 |
120 | category = first_list[0].find("span").getText()
121 | species = first_list[1].find("span").getText()
122 | language = first_list[2].find("span").getText()
123 | size = first_list[3].find("span").getText()
124 | uploader = first_list[4].find("span").getText().strip()
125 | uploader_link = base_url + "/" + uploader + "/"
126 |
127 | downloads = second_list[0].find("span").getText()
128 | last_checked = second_list[1].find("span").getText()
129 | date_uploaded = second_list[2].find("span").getText()
130 | seeders = second_list[3].find("span").getText()
131 | leechers = second_list[4].find("span").getText()
132 | else:
133 | category = species = language = size = uploader = uploader_link = downloads = last_checked = date_uploaded = (
134 | seeders
135 | ) = leechers = None
136 |
137 | return models.TorrentInfo(
138 | name=name,
139 | short_name=short_name,
140 | description=description,
141 | category=category,
142 | type=species,
143 | genre=genre,
144 | language=language,
145 | size=size,
146 | thumbnail=thumbnail,
147 | images=images if images else None,
148 | uploader=uploader,
149 | uploader_link=uploader_link,
150 | downloads=downloads,
151 | last_checked=last_checked,
152 | date_uploaded=date_uploaded,
153 | seeders=seeders,
154 | leechers=leechers,
155 | magnet_link=magnet_link,
156 | info_hash=info_hash.strip() if info_hash else None,
157 | )
158 |
--------------------------------------------------------------------------------
/py1337x/py1337x.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Literal, Optional
2 |
3 | import cloudscraper
4 | from asyncer import asyncify
5 |
6 | from py1337x import config, models, parser, utils
7 |
8 |
9 | class Py1337x:
10 | """
11 | A class to interact with the py1337x API for searching and retrieving torrent information.
12 |
13 | Example:
14 | ```python
15 | from py1337x import Py1337x
16 |
17 | torrents = Py1337x()
18 |
19 | vlc_media = torrents.search('vlc media player')
20 | print(vlc_media)
21 | ```
22 | """
23 |
24 | def __init__(
25 | self,
26 | base_url: str = config.default_base_url,
27 | cloudscraper_kwargs: Dict = {},
28 | ):
29 | """
30 | Initialize the Py1337x class with base URL, headers, and requests session.
31 |
32 | Args:
33 | base_url (Optional[str]): The base URL for the API.
34 | cloudscraper_kwargs (Optional[dict]): Kwargs to pass in cloudscraper.
35 | """
36 | self.base_url = base_url
37 | self.requests = cloudscraper.create_scraper(**cloudscraper_kwargs)
38 | self.url_builder = utils.URLBuilder(base_url)
39 |
40 | def search(
41 | self,
42 | query: str,
43 | page: int = 1,
44 | category: Optional[str] = None,
45 | sort_by: Optional[Literal["time", "size", "seeders", "leechers"]] = None,
46 | order: Literal["asc", "desc"] = "desc",
47 | ) -> models.TorrentResult:
48 | """
49 | Search for torrents based on a query.
50 |
51 | Args:
52 | query (str): The search query.
53 | page (int): The page number.
54 | category (Optional[str]): Category of the torrent.
55 | sort_by (Optional[str]): Sort by "time", "size", "seeders" or "leechers".
56 | order (str): The order string ('asc' or 'desc').
57 |
58 | Returns:
59 | Result from the query
60 |
61 | Example:
62 | Basic search
63 | ```python
64 | from py1337x import Py1337x
65 |
66 | results = torrents.search('ubuntu')
67 | print(results)
68 | ```
69 |
70 | Search with sorting by seeders
71 | ```python
72 | from py1337x.types import category, sort, order
73 |
74 | results = torrents.search('ubuntu', sort_by=sort.SEEDERS)
75 | print(results)
76 | ```
77 |
78 | Search within a category
79 | ```python
80 | results = torrents.search('ubuntu', category=category.APPS)
81 | print(results)
82 | ```
83 |
84 | Paginated search results, fetching page 2
85 | ```python
86 | results = torrents.search('ubuntu', page=2, order=order.ASC)
87 | print(results)
88 | ```
89 | """
90 | query = self.url_builder.sanitize_query(query)
91 | category = self.url_builder.sanitize_category(category)
92 | url = self.url_builder.build_search_url(query, page, category, sort_by, order)
93 | print(url)
94 |
95 | response = self.requests.get(url)
96 |
97 | return parser.torrent_parser(response, base_url=self.base_url, page=page)
98 |
99 | def trending(self, category: Optional[str] = None, weekly: bool = False) -> models.TorrentResult:
100 | """
101 | Retrieve trending torrents.
102 |
103 | Args:
104 | category (Optional[str]): Category of the torrent.
105 | weekly (bool): Whether to get weekly trending torrents.
106 |
107 | Returns:
108 | Trending torrents
109 |
110 | Example:
111 | Get today's trending torrents
112 | ```python
113 | trending_today = torrents.trending()
114 | print(trending_today)
115 | ```
116 |
117 | Weekly trending torrents in the applications category
118 | ```python
119 | trending_weekly = torrents.trending(category=category.APPS, weekly=True)
120 | print(trending_weekly)
121 | ```
122 | """
123 | url = self.url_builder.build_trending_url(category, weekly)
124 | response = self.requests.get(url)
125 |
126 | return parser.torrent_parser(response, base_url=self.base_url)
127 |
128 | def top(self, category: Optional[str] = None) -> models.TorrentResult:
129 | """
130 | Retrieve top 100 torrents.
131 |
132 | Args:
133 | category (Optional[str]): Category of the torrent.
134 |
135 | Returns:
136 | Top 100 torrents
137 |
138 | Example:
139 | Get top 100 torrents
140 | ```python
141 | top_torrents = torrents.top()
142 | print(top_torrents)
143 | ```
144 |
145 | Top torrents in specific category
146 | ```python
147 | top_movies = torrents.top(category=category.MOVIES)
148 | print(top_movies)
149 | ```
150 | """
151 | url = self.url_builder.build_top_url(category)
152 | response = self.requests.get(url)
153 |
154 | return parser.torrent_parser(response, base_url=self.base_url)
155 |
156 | def popular(self, category: str, weekly: bool = False) -> models.TorrentResult:
157 | """
158 | Retrieve popular torrents.
159 |
160 | Args:
161 | category (str): Category of the torrent.
162 | weekly (bool): Whether to get weekly popular torrents.
163 |
164 | Returns:
165 | Popular torrents
166 |
167 | Example:
168 | Get popular torrents
169 | ```python
170 | popular = torrents.popular(category=category.GAMES)
171 | print(popular)
172 | ```
173 |
174 | Weekly popular torrents
175 | ```python
176 | popular_weekly = torrents.popular(category=category.APPS, weekly=True)
177 | print(popular_weekly)
178 | ```
179 | """
180 | url = self.url_builder.build_popular_url(category, weekly)
181 | response = self.requests.get(url)
182 |
183 | return parser.torrent_parser(response, base_url=self.base_url)
184 |
185 | def browse(self, category: str, page: int = 1) -> models.TorrentResult:
186 | """
187 | Browse torrents by category.
188 |
189 | Args:
190 | category (str): Category of the torrent.
191 | page (int): The page number.
192 |
193 | Returns:
194 | Parsed browse results.
195 |
196 | Example:
197 | Browse torrents under the applications category
198 | ```python
199 | apps = torrents.browse(category=category.APPS, page=2)
200 | print(apps)
201 | ```
202 | """
203 | url = self.url_builder.build_browse_url(category, page)
204 | response = self.requests.get(url)
205 |
206 | return parser.torrent_parser(response, base_url=self.base_url, page=page)
207 |
208 | def info(self, link: Optional[str] = None, torrent_id: Optional[str] = None) -> models.TorrentInfo:
209 | """
210 | Retrieve information of a torrent.
211 |
212 | Args:
213 | link (Optional[str]): The URL to the torrent.
214 | torrent_id (Optional[str]): The torrent ID.
215 |
216 | Returns:
217 | Parsed torrent information.
218 |
219 | Raises:
220 | TypeError: If neither link nor torrent_id is provided, or if both are provided.
221 | """
222 | url = self.url_builder.build_info_url(link, torrent_id)
223 | response = self.requests.get(url)
224 |
225 | return parser.info_parser(response, base_url=self.base_url)
226 |
227 |
228 | class AsyncPy1337x(Py1337x):
229 | """
230 | An experimental asynchronous version of the Py1337x class to interact with the py1337x
231 | asynchronously by calling the original methods in a worker thread.
232 |
233 | Example:
234 | ```python
235 | import asyncio
236 |
237 | from py1337x import AsyncPy1337x
238 |
239 | async def main():
240 | torrents = AsyncPy1337x()
241 | vlc_media = await torrents.search('vlc media player')
242 | print(vlc_media)
243 |
244 | asyncio.run(main())
245 | ```
246 | """
247 |
248 | def __init__(self, *args, **kwargs):
249 | super().__init__(*args, **kwargs)
250 |
251 | def __getattribute__(self, name):
252 | # Get the attribute from the parent class
253 | attr = super().__getattribute__(name)
254 |
255 | if callable(attr):
256 | # If the attribute is callable, return an asyncified version of it
257 | return asyncify(attr)
258 |
259 | # Otherwise, return the attribute as is
260 | return attr
261 |
--------------------------------------------------------------------------------
/py1337x/types/__init__.py:
--------------------------------------------------------------------------------
1 | from py1337x.types import category # noqa
2 | from py1337x.types import order # noqa
3 | from py1337x.types import sort # noqa
4 |
--------------------------------------------------------------------------------
/py1337x/types/category.py:
--------------------------------------------------------------------------------
1 | MOVIES='movies'
2 | TV='tv'
3 | GAMES='games'
4 | MUSIC='music'
5 | APPS='apps'
6 | ANIME='anime'
7 | DOCUMENTARIES='documentaries'
8 | XXX='xxx'
9 | OTHER='other'
10 |
--------------------------------------------------------------------------------
/py1337x/types/order.py:
--------------------------------------------------------------------------------
1 | ASCENDING="asc"
2 | DESCENDING="desc"
3 |
--------------------------------------------------------------------------------
/py1337x/types/sort.py:
--------------------------------------------------------------------------------
1 | TIME="time"
2 | SIZE="size"
3 | SEEDERS="seeders"
4 | LEECHERS="leechers"
5 |
--------------------------------------------------------------------------------
/py1337x/utils.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 |
4 | class URLBuilder:
5 | """
6 | A class to build and sanitize URLs.
7 | """
8 |
9 | def __init__(self, base_url: str):
10 | """
11 | Initialize the URLBuilder with the base URL.
12 |
13 | Args:
14 | base_url (str): The base URL for the API.
15 | """
16 | self.base_url = base_url
17 |
18 | def sanitize_query(self, query: str) -> str:
19 | """
20 | Sanitize the search query.
21 |
22 | Args:
23 | query (str): The search query.
24 |
25 | Returns:
26 | str: The sanitized query.
27 | """
28 | return "+".join(query.split())
29 |
30 | def sanitize_category(self, category: Optional[str]) -> Optional[str]:
31 | """
32 | Sanitize the category string.
33 |
34 | Args:
35 | category (Optional[str]): The category string.
36 |
37 | Returns:
38 | Optional[str]: The sanitized category string or None if no category is provided.
39 | """
40 | if category:
41 | if category.lower() in ["xxx", "tv"]:
42 | return category.upper()
43 | return category.capitalize()
44 | return None
45 |
46 | def build_search_url(
47 | self, query: str, page: int, category: Optional[str], sort_by: Optional[str], order: str
48 | ) -> str:
49 | """
50 | Build the URL for searching torrents.
51 |
52 | Args:
53 | query (str): The search query.
54 | page (int): The page number.
55 | category (Optional[str]): The category string.
56 | sort_by (Optional[str]): The sort by string.
57 | order (str): The order string ('asc' or 'desc').
58 |
59 | Returns:
60 | str: The constructed search URL.
61 | """
62 | query = self.sanitize_query(query)
63 | category_part = ""
64 | category_type = ""
65 | sort_part = ""
66 | sort_type = ""
67 | order_part = ""
68 |
69 | if category:
70 | category_part = "category-"
71 | category_type = f"{self.sanitize_category(category)}/"
72 |
73 | if sort_by:
74 | sort_part = "sort-"
75 | sort_type = f"{sort_by.lower()}/"
76 | order_part = f"{order.lower()}/"
77 |
78 | url = f"{self.base_url}/{sort_part}{category_part}search/{query}/{category_type}{sort_type}{order_part}{page}/"
79 | return url
80 |
81 | def build_trending_url(self, category: Optional[str], weekly: bool) -> str:
82 | """
83 | Build the URL for trending torrents.
84 |
85 | Args:
86 | category (Optional[str]): The category string.
87 | week (bool): Whether to get weekly trending torrents.
88 |
89 | Returns:
90 | str: The constructed trending URL.
91 | """
92 | if weekly and category:
93 | return f"{self.base_url}/trending/w/{category.lower()}/"
94 | elif weekly:
95 | return f"{self.base_url}/trending-week/"
96 | elif category:
97 | return f"{self.base_url}/trending/d/{category.lower()}/"
98 | return f"{self.base_url}/trending"
99 |
100 | def build_top_url(self, category: Optional[str]) -> str:
101 | """
102 | Build the URL for top 100 torrents.
103 |
104 | Args:
105 | category (Optional[str]): The category string.
106 |
107 | Returns:
108 | str: The constructed top 100 URL.
109 | """
110 | if category:
111 | if category.lower() == "apps":
112 | category = "applications"
113 | elif category.lower() == "tv":
114 | category = "television"
115 | return f"{self.base_url}/top-100-{category.lower()}"
116 | return f"{self.base_url}/top-100"
117 |
118 | def build_popular_url(self, category: str, weekly: bool) -> str:
119 | """
120 | Build the URL for popular torrents.
121 |
122 | Args:
123 | category (str): The category string.
124 | weekly (bool): Whether to get weekly popular torrents.
125 |
126 | Returns:
127 | str: The constructed popular URL.
128 | """
129 | return f"{self.base_url}/popular-{category.lower()}{'-week' if weekly else ''}"
130 |
131 | def build_browse_url(self, category: str, page: int) -> str:
132 | """
133 | Build the URL for browsing torrents by category.
134 |
135 | Args:
136 | category (str): The category string.
137 | page (int): The page number.
138 |
139 | Returns:
140 | str: The constructed browse URL.
141 | """
142 | if category.lower() in ["xxx", "tv"]:
143 | category = category.upper()
144 | else:
145 | category = category.capitalize()
146 | return f"{self.base_url}/cat/{category}/{page}/"
147 |
148 | def build_info_url(self, link: Optional[str], torrent_id: Optional[str]) -> Optional[str]:
149 | """
150 | Build the URL for fetching torrent info.
151 |
152 | Args:
153 | link (Optional[str]): The direct link to the torrent.
154 | torrent_id (Optional[str]): The torrent ID.
155 |
156 | Returns:
157 | str: The constructed info URL.
158 |
159 | Raises:
160 | TypeError: If neither link nor torrent_id is provided, or if both are provided.
161 | """
162 | if not link and not torrent_id:
163 | raise TypeError("Missing 1 required positional argument: link or torrent_id")
164 | elif link and torrent_id:
165 | raise TypeError("Got an unexpected argument: Pass either link or torrent_id")
166 |
167 | return f"{self.base_url}/torrent/{torrent_id}/-/" if torrent_id else link
168 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "1337x"
3 | version = "2.0.1"
4 | description = "Unofficial API of 1337x.to"
5 | authors = [{ name = "Hemanta Pokharel", email = "yo@hemantapkh.com" }]
6 | requires-python = "~=3.8"
7 | readme = "README.md"
8 | license = "MIT"
9 | keywords = [
10 | "1337x",
11 | "torrents",
12 | ]
13 | classifiers = [
14 | "Development Status :: 5 - Production/Stable",
15 | "Intended Audience :: Developers",
16 | "License :: OSI Approved :: MIT License",
17 | "Operating System :: OS Independent",
18 | "Topic :: Utilities",
19 | ]
20 | dependencies = [
21 | "requests>=2.32.3,<3",
22 | "beautifulsoup4>=4.12.3,<5",
23 | "cloudscraper>=1.2.71,<2",
24 | "asyncer==0.0.8",
25 | ]
26 |
27 | [project.urls]
28 | Repository = "https://github.com/hemantapkh/1337x"
29 | Documentation = "https://github.com/hemantapkh/1337x/blob/main/README.md"
30 | "Issue tracker" = "https://github.com/hemantapkh/1337x/issues"
31 |
32 | [tool.hatch.build.targets.sdist]
33 | include = ["py1337x"]
34 |
35 | [tool.hatch.build.targets.wheel]
36 | include = ["py1337x"]
37 |
38 | [build-system]
39 | requires = ["hatchling"]
40 | build-backend = "hatchling.build"
41 |
--------------------------------------------------------------------------------
/uv.lock:
--------------------------------------------------------------------------------
1 | version = 1
2 | revision = 2
3 | requires-python = ">=3.8"
4 | resolution-markers = [
5 | "python_full_version >= '3.9'",
6 | "python_full_version < '3.9'",
7 | ]
8 |
9 | [[package]]
10 | name = "1337x"
11 | version = "2.0.0"
12 | source = { editable = "." }
13 | dependencies = [
14 | { name = "asyncer" },
15 | { name = "beautifulsoup4" },
16 | { name = "cloudscraper" },
17 | { name = "requests" },
18 | ]
19 |
20 | [package.metadata]
21 | requires-dist = [
22 | { name = "asyncer", specifier = ">=0.0.8" },
23 | { name = "beautifulsoup4", specifier = ">=4.12.3" },
24 | { name = "cloudscraper", specifier = ">=1.2.71" },
25 | { name = "requests", specifier = ">=2.32.3" },
26 | ]
27 |
28 | [[package]]
29 | name = "anyio"
30 | version = "4.5.2"
31 | source = { registry = "https://pypi.org/simple" }
32 | resolution-markers = [
33 | "python_full_version < '3.9'",
34 | ]
35 | dependencies = [
36 | { name = "exceptiongroup", marker = "python_full_version < '3.9'" },
37 | { name = "idna", marker = "python_full_version < '3.9'" },
38 | { name = "sniffio", marker = "python_full_version < '3.9'" },
39 | { name = "typing-extensions", marker = "python_full_version < '3.9'" },
40 | ]
41 | sdist = { url = "https://files.pythonhosted.org/packages/4d/f9/9a7ce600ebe7804daf90d4d48b1c0510a4561ddce43a596be46676f82343/anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b", size = 171293, upload-time = "2024-10-13T22:18:03.307Z" }
42 | wheels = [
43 | { url = "https://files.pythonhosted.org/packages/1b/b4/f7e396030e3b11394436358ca258a81d6010106582422f23443c16ca1873/anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f", size = 89766, upload-time = "2024-10-13T22:18:01.524Z" },
44 | ]
45 |
46 | [[package]]
47 | name = "anyio"
48 | version = "4.9.0"
49 | source = { registry = "https://pypi.org/simple" }
50 | resolution-markers = [
51 | "python_full_version >= '3.9'",
52 | ]
53 | dependencies = [
54 | { name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
55 | { name = "idna", marker = "python_full_version >= '3.9'" },
56 | { name = "sniffio", marker = "python_full_version >= '3.9'" },
57 | { name = "typing-extensions", marker = "python_full_version >= '3.9' and python_full_version < '3.13'" },
58 | ]
59 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
60 | wheels = [
61 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
62 | ]
63 |
64 | [[package]]
65 | name = "asyncer"
66 | version = "0.0.8"
67 | source = { registry = "https://pypi.org/simple" }
68 | dependencies = [
69 | { name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
70 | { name = "anyio", version = "4.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
71 | { name = "typing-extensions", marker = "python_full_version < '3.10'" },
72 | ]
73 | sdist = { url = "https://files.pythonhosted.org/packages/ff/67/7ea59c3e69eaeee42e7fc91a5be67ca5849c8979acac2b920249760c6af2/asyncer-0.0.8.tar.gz", hash = "sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c", size = 18217, upload-time = "2024-08-24T23:15:36.449Z" }
74 | wheels = [
75 | { url = "https://files.pythonhosted.org/packages/8a/04/15b6ca6b7842eda2748bda0a0af73f2d054e9344320f8bba01f994294bcb/asyncer-0.0.8-py3-none-any.whl", hash = "sha256:5920d48fc99c8f8f0f1576e1882f5022885589c5fcbc46ce4224ec3e53776eeb", size = 9209, upload-time = "2024-08-24T23:15:35.317Z" },
76 | ]
77 |
78 | [[package]]
79 | name = "beautifulsoup4"
80 | version = "4.13.4"
81 | source = { registry = "https://pypi.org/simple" }
82 | dependencies = [
83 | { name = "soupsieve" },
84 | { name = "typing-extensions" },
85 | ]
86 | sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" }
87 | wheels = [
88 | { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" },
89 | ]
90 |
91 | [[package]]
92 | name = "certifi"
93 | version = "2025.4.26"
94 | source = { registry = "https://pypi.org/simple" }
95 | sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
96 | wheels = [
97 | { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
98 | ]
99 |
100 | [[package]]
101 | name = "charset-normalizer"
102 | version = "3.4.2"
103 | source = { registry = "https://pypi.org/simple" }
104 | sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
105 | wheels = [
106 | { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" },
107 | { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" },
108 | { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" },
109 | { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" },
110 | { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" },
111 | { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" },
112 | { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" },
113 | { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" },
114 | { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" },
115 | { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" },
116 | { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" },
117 | { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" },
118 | { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" },
119 | { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" },
120 | { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" },
121 | { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" },
122 | { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" },
123 | { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" },
124 | { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" },
125 | { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" },
126 | { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" },
127 | { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" },
128 | { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" },
129 | { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" },
130 | { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" },
131 | { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" },
132 | { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" },
133 | { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" },
134 | { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" },
135 | { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" },
136 | { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" },
137 | { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" },
138 | { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" },
139 | { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" },
140 | { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" },
141 | { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" },
142 | { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" },
143 | { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" },
144 | { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" },
145 | { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
146 | { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
147 | { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
148 | { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
149 | { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
150 | { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
151 | { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
152 | { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
153 | { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
154 | { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
155 | { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
156 | { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
157 | { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
158 | { url = "https://files.pythonhosted.org/packages/4c/fd/f700cfd4ad876def96d2c769d8a32d808b12d1010b6003dc6639157f99ee/charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", size = 198257, upload-time = "2025-05-02T08:33:45.511Z" },
159 | { url = "https://files.pythonhosted.org/packages/3a/95/6eec4cbbbd119e6a402e3bfd16246785cc52ce64cf21af2ecdf7b3a08e91/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", size = 143453, upload-time = "2025-05-02T08:33:47.463Z" },
160 | { url = "https://files.pythonhosted.org/packages/b6/b3/d4f913660383b3d93dbe6f687a312ea9f7e89879ae883c4e8942048174d4/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", size = 153130, upload-time = "2025-05-02T08:33:50.568Z" },
161 | { url = "https://files.pythonhosted.org/packages/e5/69/7540141529eabc55bf19cc05cd9b61c2078bebfcdbd3e799af99b777fc28/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", size = 145688, upload-time = "2025-05-02T08:33:52.828Z" },
162 | { url = "https://files.pythonhosted.org/packages/2e/bb/d76d3d6e340fb0967c43c564101e28a78c9a363ea62f736a68af59ee3683/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", size = 147418, upload-time = "2025-05-02T08:33:54.718Z" },
163 | { url = "https://files.pythonhosted.org/packages/3e/ef/b7c1f39c0dc3808160c8b72e0209c2479393966313bfebc833533cfff9cc/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", size = 150066, upload-time = "2025-05-02T08:33:56.597Z" },
164 | { url = "https://files.pythonhosted.org/packages/20/26/4e47cc23d2a4a5eb6ed7d6f0f8cda87d753e2f8abc936d5cf5ad2aae8518/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", size = 144499, upload-time = "2025-05-02T08:33:58.637Z" },
165 | { url = "https://files.pythonhosted.org/packages/d7/9c/efdf59dd46593cecad0548d36a702683a0bdc056793398a9cd1e1546ad21/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", size = 152954, upload-time = "2025-05-02T08:34:00.552Z" },
166 | { url = "https://files.pythonhosted.org/packages/59/b3/4e8b73f7299d9aaabd7cd26db4a765f741b8e57df97b034bb8de15609002/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", size = 155876, upload-time = "2025-05-02T08:34:02.527Z" },
167 | { url = "https://files.pythonhosted.org/packages/53/cb/6fa0ccf941a069adce3edb8a1e430bc80e4929f4d43b5140fdf8628bdf7d/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", size = 153186, upload-time = "2025-05-02T08:34:04.481Z" },
168 | { url = "https://files.pythonhosted.org/packages/ac/c6/80b93fabc626b75b1665ffe405e28c3cef0aae9237c5c05f15955af4edd8/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", size = 148007, upload-time = "2025-05-02T08:34:06.888Z" },
169 | { url = "https://files.pythonhosted.org/packages/41/eb/c7367ac326a2628e4f05b5c737c86fe4a8eb3ecc597a4243fc65720b3eeb/charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", size = 97923, upload-time = "2025-05-02T08:34:08.792Z" },
170 | { url = "https://files.pythonhosted.org/packages/7c/02/1c82646582ccf2c757fa6af69b1a3ea88744b8d2b4ab93b7686b2533e023/charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", size = 105020, upload-time = "2025-05-02T08:34:10.6Z" },
171 | { url = "https://files.pythonhosted.org/packages/28/f8/dfb01ff6cc9af38552c69c9027501ff5a5117c4cc18dcd27cb5259fa1888/charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", size = 201671, upload-time = "2025-05-02T08:34:12.696Z" },
172 | { url = "https://files.pythonhosted.org/packages/32/fb/74e26ee556a9dbfe3bd264289b67be1e6d616329403036f6507bb9f3f29c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", size = 144744, upload-time = "2025-05-02T08:34:14.665Z" },
173 | { url = "https://files.pythonhosted.org/packages/ad/06/8499ee5aa7addc6f6d72e068691826ff093329fe59891e83b092ae4c851c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", size = 154993, upload-time = "2025-05-02T08:34:17.134Z" },
174 | { url = "https://files.pythonhosted.org/packages/f1/a2/5e4c187680728219254ef107a6949c60ee0e9a916a5dadb148c7ae82459c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", size = 147382, upload-time = "2025-05-02T08:34:19.081Z" },
175 | { url = "https://files.pythonhosted.org/packages/4c/fe/56aca740dda674f0cc1ba1418c4d84534be51f639b5f98f538b332dc9a95/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", size = 149536, upload-time = "2025-05-02T08:34:21.073Z" },
176 | { url = "https://files.pythonhosted.org/packages/53/13/db2e7779f892386b589173dd689c1b1e304621c5792046edd8a978cbf9e0/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", size = 151349, upload-time = "2025-05-02T08:34:23.193Z" },
177 | { url = "https://files.pythonhosted.org/packages/69/35/e52ab9a276186f729bce7a0638585d2982f50402046e4b0faa5d2c3ef2da/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", size = 146365, upload-time = "2025-05-02T08:34:25.187Z" },
178 | { url = "https://files.pythonhosted.org/packages/a6/d8/af7333f732fc2e7635867d56cb7c349c28c7094910c72267586947561b4b/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", size = 154499, upload-time = "2025-05-02T08:34:27.359Z" },
179 | { url = "https://files.pythonhosted.org/packages/7a/3d/a5b2e48acef264d71e036ff30bcc49e51bde80219bb628ba3e00cf59baac/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", size = 157735, upload-time = "2025-05-02T08:34:29.798Z" },
180 | { url = "https://files.pythonhosted.org/packages/85/d8/23e2c112532a29f3eef374375a8684a4f3b8e784f62b01da931186f43494/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", size = 154786, upload-time = "2025-05-02T08:34:31.858Z" },
181 | { url = "https://files.pythonhosted.org/packages/c7/57/93e0169f08ecc20fe82d12254a200dfaceddc1c12a4077bf454ecc597e33/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", size = 150203, upload-time = "2025-05-02T08:34:33.88Z" },
182 | { url = "https://files.pythonhosted.org/packages/2c/9d/9bf2b005138e7e060d7ebdec7503d0ef3240141587651f4b445bdf7286c2/charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", size = 98436, upload-time = "2025-05-02T08:34:35.907Z" },
183 | { url = "https://files.pythonhosted.org/packages/6d/24/5849d46cf4311bbf21b424c443b09b459f5b436b1558c04e45dbb7cc478b/charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", size = 105772, upload-time = "2025-05-02T08:34:37.935Z" },
184 | { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
185 | ]
186 |
187 | [[package]]
188 | name = "cloudscraper"
189 | version = "1.2.71"
190 | source = { registry = "https://pypi.org/simple" }
191 | dependencies = [
192 | { name = "pyparsing", version = "3.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
193 | { name = "pyparsing", version = "3.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
194 | { name = "requests" },
195 | { name = "requests-toolbelt" },
196 | ]
197 | sdist = { url = "https://files.pythonhosted.org/packages/ac/25/6d0481860583f44953bd791de0b7c4f6d7ead7223f8a17e776247b34a5b4/cloudscraper-1.2.71.tar.gz", hash = "sha256:429c6e8aa6916d5bad5c8a5eac50f3ea53c9ac22616f6cb21b18dcc71517d0d3", size = 93261, upload-time = "2023-04-25T23:20:19.467Z" }
198 | wheels = [
199 | { url = "https://files.pythonhosted.org/packages/81/97/fc88803a451029688dffd7eb446dc1b529657577aec13aceff1cc9628c5d/cloudscraper-1.2.71-py2.py3-none-any.whl", hash = "sha256:76f50ca529ed2279e220837befdec892626f9511708e200d48d5bb76ded679b0", size = 99652, upload-time = "2023-04-25T23:20:15.974Z" },
200 | ]
201 |
202 | [[package]]
203 | name = "exceptiongroup"
204 | version = "1.3.0"
205 | source = { registry = "https://pypi.org/simple" }
206 | dependencies = [
207 | { name = "typing-extensions", marker = "python_full_version < '3.13'" },
208 | ]
209 | sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
210 | wheels = [
211 | { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
212 | ]
213 |
214 | [[package]]
215 | name = "idna"
216 | version = "3.10"
217 | source = { registry = "https://pypi.org/simple" }
218 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
219 | wheels = [
220 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
221 | ]
222 |
223 | [[package]]
224 | name = "pyparsing"
225 | version = "3.1.4"
226 | source = { registry = "https://pypi.org/simple" }
227 | resolution-markers = [
228 | "python_full_version < '3.9'",
229 | ]
230 | sdist = { url = "https://files.pythonhosted.org/packages/83/08/13f3bce01b2061f2bbd582c9df82723de943784cf719a35ac886c652043a/pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032", size = 900231, upload-time = "2024-08-25T15:00:47.416Z" }
231 | wheels = [
232 | { url = "https://files.pythonhosted.org/packages/e5/0c/0e3c05b1c87bb6a1c76d281b0f35e78d2d80ac91b5f8f524cebf77f51049/pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c", size = 104100, upload-time = "2024-08-25T15:00:45.361Z" },
233 | ]
234 |
235 | [[package]]
236 | name = "pyparsing"
237 | version = "3.2.3"
238 | source = { registry = "https://pypi.org/simple" }
239 | resolution-markers = [
240 | "python_full_version >= '3.9'",
241 | ]
242 | sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" }
243 | wheels = [
244 | { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" },
245 | ]
246 |
247 | [[package]]
248 | name = "requests"
249 | version = "2.32.3"
250 | source = { registry = "https://pypi.org/simple" }
251 | dependencies = [
252 | { name = "certifi" },
253 | { name = "charset-normalizer" },
254 | { name = "idna" },
255 | { name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
256 | { name = "urllib3", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
257 | ]
258 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
259 | wheels = [
260 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
261 | ]
262 |
263 | [[package]]
264 | name = "requests-toolbelt"
265 | version = "1.0.0"
266 | source = { registry = "https://pypi.org/simple" }
267 | dependencies = [
268 | { name = "requests" },
269 | ]
270 | sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
271 | wheels = [
272 | { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
273 | ]
274 |
275 | [[package]]
276 | name = "sniffio"
277 | version = "1.3.1"
278 | source = { registry = "https://pypi.org/simple" }
279 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
280 | wheels = [
281 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
282 | ]
283 |
284 | [[package]]
285 | name = "soupsieve"
286 | version = "2.7"
287 | source = { registry = "https://pypi.org/simple" }
288 | sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" }
289 | wheels = [
290 | { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" },
291 | ]
292 |
293 | [[package]]
294 | name = "typing-extensions"
295 | version = "4.13.2"
296 | source = { registry = "https://pypi.org/simple" }
297 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
298 | wheels = [
299 | { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
300 | ]
301 |
302 | [[package]]
303 | name = "urllib3"
304 | version = "2.2.3"
305 | source = { registry = "https://pypi.org/simple" }
306 | resolution-markers = [
307 | "python_full_version < '3.9'",
308 | ]
309 | sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677, upload-time = "2024-09-12T10:52:18.401Z" }
310 | wheels = [
311 | { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338, upload-time = "2024-09-12T10:52:16.589Z" },
312 | ]
313 |
314 | [[package]]
315 | name = "urllib3"
316 | version = "2.4.0"
317 | source = { registry = "https://pypi.org/simple" }
318 | resolution-markers = [
319 | "python_full_version >= '3.9'",
320 | ]
321 | sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" }
322 | wheels = [
323 | { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
324 | ]
325 |
--------------------------------------------------------------------------------