mirror of
https://github.com/zhaarey/AppleMusicDecrypt.git
synced 2025-10-23 15:11:06 +00:00
Initial commit
This commit is contained in:
162
.gitignore
vendored
Normal file
162
.gitignore
vendored
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
### Python template
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
8
.idea/AppleMusicDecrypt.iml
generated
Normal file
8
.idea/AppleMusicDecrypt.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/encodings.xml
generated
Normal file
6
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/downloads/MyGO!!!!!/迷跡波/1-08 春日影 (MyGO!!!!! ver.lrc" charset="GBK" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
59
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
59
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<Languages>
|
||||||
|
<language minSize="54" name="Python" />
|
||||||
|
</Languages>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ourVersions">
|
||||||
|
<value>
|
||||||
|
<list size="5">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="3.10" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="3.11" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="3.12" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="3.9" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="3.8" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredPackages">
|
||||||
|
<value>
|
||||||
|
<list size="13">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="dnspython" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="pydantic" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="graia-broadcast" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="pydantic-core" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="markupsafe" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="jinja2" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="fonttools" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="seaborn" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="aiohttp" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="pytz" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="beanie" />
|
||||||
|
<item index="11" class="java.lang.String" itemvalue="uvicorn" />
|
||||||
|
<item index="12" class="java.lang.String" itemvalue="flask" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredErrors">
|
||||||
|
<list>
|
||||||
|
<option value="E501" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredErrors">
|
||||||
|
<list>
|
||||||
|
<option value="N801" />
|
||||||
|
<option value="N806" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="PROJECT_PROFILE" value="Default" />
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Poetry (AppleMusicDecrypt)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Poetry (AppleMusicDecrypt)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/AppleMusicDecrypt.iml" filepath="$PROJECT_DIR$/.idea/AppleMusicDecrypt.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
661
LICENSE.txt
Normal file
661
LICENSE.txt
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
113
agent.js
Normal file
113
agent.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
'use strict';
|
||||||
|
setTimeout(() => {
|
||||||
|
const fairplayCert = "MIIEzjCCA7agAwIBAgIIAXAVjHFZDjgwDQYJKoZIhvcNAQEFBQAwfzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMwMQYDVQQDDCpBcHBsZSBLZXkgU2VydmljZXMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTIwNzI1MTgwMjU4WhcNMTQwNzI2MTgwMjU4WjAwMQswCQYDVQQGEwJVUzESMBAGA1UECgwJQXBwbGUgSW5jMQ0wCwYDVQQDDARGUFMxMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqZ9IbMt0J0dTKQN4cUlfeQRY9bcnbnP95HFv9A16Yayh4xQzRLAQqVSmisZtBK2/nawZcDmcs+XapBojRb+jDM4Dzk6/Ygdqo8LoA+BE1zipVyalGLj8Y86hTC9QHX8i05oWNCDIlmabjjWvFBoEOk+ezOAPg8c0SET38x5u+TwIDAQABo4ICHzCCAhswHQYDVR0OBBYEFPP6sfTWpOQ5Sguf5W3Y0oibbEc3MAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUY+RHVMuFcVlGLIOszEQxZGcDLL4wgeIGA1UdIASB2jCB1zCB1AYJKoZIhvdjZAUBMIHGMIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9jcmwuYXBwbGUuY29tL2tleXNlcnZpY2VzLmNybDAOBgNVHQ8BAf8EBAMCBSAwFAYLKoZIhvdjZAYNAQUBAf8EAgUAMBsGCyqGSIb3Y2QGDQEGAQH/BAkBAAAAAQAAAAEwKQYLKoZIhvdjZAYNAQMBAf8EFwF+bjsY57ASVFmeehD2bdu6HLGBxeC2MEEGCyqGSIb3Y2QGDQEEAQH/BC8BHrKviHJf/Se/ibc7T0/55Bt1GePzaYBVfgF3ZiNuV93z8P3qsawAqAXzzh9o5DANBgkqhkiG9w0BAQUFAAOCAQEAVGyCtuLYcYb/aPijBCtaemxuV0IokXJn3EgmwYHZynaR6HZmeGRUp9p3f8EXu6XPSekKCCQi+a86hXX9RfnGEjRdvtP+jts5MDSKuUIoaqce8cLX2dpUOZXdf3lR0IQM0kXHb5boNGBsmbTLVifqeMsexfZryGw2hE/4WDOJdGQm1gMJZU4jP1b/HSLNIUhHWAaMeWtcJTPRBucR4urAtvvtOWD88mriZNHG+veYw55b+qA36PSqDPMbku9xTY7fsMa6mxIRmwULQgi8nOk1wNhw3ZO0qUKtaCO3gSqWdloecxpxUQSZCSW7tWPkpXXwDZqegUkij9xMFS1pr37RIg==";
|
||||||
|
const port = 2147483647
|
||||||
|
|
||||||
|
function newStdStringFromBuffer(content) {
|
||||||
|
const size = content.byteLength;
|
||||||
|
const cap = 2 ** Math.ceil(Math.log2(size + 1));
|
||||||
|
const buffer = Memory.alloc(cap);
|
||||||
|
Memory.copy(buffer, content.unwrap(), size);
|
||||||
|
|
||||||
|
const addr = Memory.alloc(Process.pointerSize * 3);
|
||||||
|
addr.writeULong(cap | 0x1);
|
||||||
|
addr.add(Process.pointerSize).writeULong(size);
|
||||||
|
addr.add(Process.pointerSize * 2).writePointer(buffer);
|
||||||
|
|
||||||
|
return { buffer: buffer, str: addr };
|
||||||
|
}
|
||||||
|
|
||||||
|
function newStdString(content) {
|
||||||
|
const size = content.length;
|
||||||
|
const cap = 2 ** Math.ceil(Math.log2(size + 1));
|
||||||
|
const buffer = Memory.alloc(cap);
|
||||||
|
buffer.writeUtf8String(content);
|
||||||
|
|
||||||
|
const addr = Memory.alloc(Process.pointerSize * 3);
|
||||||
|
addr.writeULong(cap | 0x1);
|
||||||
|
addr.add(Process.pointerSize).writeULong(size);
|
||||||
|
addr.add(Process.pointerSize * 2).writePointer(buffer);
|
||||||
|
|
||||||
|
return { buffer: buffer, str: addr };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const androidappmusic = Process.getModuleByName("libandroidappmusic.so");
|
||||||
|
|
||||||
|
const sessionCtrlPtr = androidappmusic.getExportByName("_ZN21SVFootHillSessionCtrl8instanceEv");
|
||||||
|
const sessionCtrlInstanceFunc = new NativeFunction(sessionCtrlPtr, "pointer", []);
|
||||||
|
const sessionCtrlInstance = sessionCtrlInstanceFunc();
|
||||||
|
|
||||||
|
const getPersistentKeyAddr = androidappmusic.getExportByName("_ZN21SVFootHillSessionCtrl16getPersistentKeyERKNSt6__ndk112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_S8_S8_S8_S8_S8_");
|
||||||
|
const getPersistentKey = new NativeFunction(getPersistentKeyAddr, "void", Array(9).fill("pointer"));
|
||||||
|
|
||||||
|
const decryptContextAddr = androidappmusic.getExportByName("_ZN21SVFootHillSessionCtrl14decryptContextERKNSt6__ndk112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEERKN11SVDecryptor15SVDecryptorTypeERKb");
|
||||||
|
const decryptContext = new NativeFunction(decryptContextAddr, "void", Array(3).fill("pointer"));
|
||||||
|
|
||||||
|
const NfcRKVnxuKZy04KWbdFu71Ou = androidappmusic.getExportByName("NfcRKVnxuKZy04KWbdFu71Ou");
|
||||||
|
const decryptSample = new NativeFunction(NfcRKVnxuKZy04KWbdFu71Ou, 'ulong', ['pointer', 'uint', 'pointer', 'pointer', 'size_t']);
|
||||||
|
|
||||||
|
const kdContextMap = new Map();
|
||||||
|
|
||||||
|
function getkdContext(adam, uri) {
|
||||||
|
const uriStr = String.fromCharCode(...new Uint8Array(uri))
|
||||||
|
if (kdContextMap.has(uriStr)) {
|
||||||
|
return kdContextMap.get(uriStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultId = newStdStringFromBuffer(adam);
|
||||||
|
const keyUri = newStdStringFromBuffer(uri);
|
||||||
|
const keyFormat = newStdString("com.apple.streamingkeydelivery");
|
||||||
|
const keyFormatVer = newStdString("1");
|
||||||
|
const serverUri = newStdString("https://play.itunes.apple.com/WebObjects/MZPlay.woa/music/fps");
|
||||||
|
const protocolType = newStdString("simplified");
|
||||||
|
const fpsCert = newStdString(fairplayCert);
|
||||||
|
const persistentKey = Memory.alloc(Process.pointerSize * 2);
|
||||||
|
getPersistentKey(persistentKey, sessionCtrlInstance, defaultId.str, keyUri.str, keyFormat.str, keyFormatVer.str, serverUri.str, protocolType.str, fpsCert.str);
|
||||||
|
|
||||||
|
const ptr = persistentKey.readPointer();
|
||||||
|
if (ptr.isNull()) return null;
|
||||||
|
|
||||||
|
const svfootHillPKey = Memory.alloc(Process.pointerSize * 2);
|
||||||
|
decryptContext(svfootHillPKey, sessionCtrlInstance, ptr);
|
||||||
|
|
||||||
|
const ptr2 = svfootHillPKey.readPointer();
|
||||||
|
if (ptr2.isNull()) return null;
|
||||||
|
|
||||||
|
const ap = ptr2.add(0x18).readPointer();
|
||||||
|
if (!ap.isNull()) kdContextMap.set(uriStr, ap);
|
||||||
|
return ap;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleConnection(s) {
|
||||||
|
// console.log("new connection!");
|
||||||
|
while (true) {
|
||||||
|
const adamSize = (await s.input.readAll(1)).unwrap().readU8();
|
||||||
|
if (adamSize === 0)
|
||||||
|
break;
|
||||||
|
const adam = await s.input.readAll(adamSize);
|
||||||
|
const uriSize = (await s.input.readAll(1)).unwrap().readU8();
|
||||||
|
const uri = await s.input.readAll(uriSize);
|
||||||
|
const kdContext = getkdContext(adam, uri);
|
||||||
|
// console.log(adam, uri, kdContext)
|
||||||
|
while (true) {
|
||||||
|
const size = (await s.input.readAll(4)).unwrap().readU32();
|
||||||
|
if (size === 0)
|
||||||
|
break;
|
||||||
|
const sample = await s.input.readAll(size);
|
||||||
|
decryptSample(kdContext.readPointer(), 5, sample.unwrap(), sample.unwrap(), sample.byteLength);
|
||||||
|
await s.output.writeAll(sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await s.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket.listen({
|
||||||
|
family: "ipv4",
|
||||||
|
port: port,
|
||||||
|
}).then(async function (listener) {
|
||||||
|
while (true) {
|
||||||
|
handleConnection(await listener.accept());
|
||||||
|
}
|
||||||
|
}).catch(console.log);
|
||||||
|
}, 4000);
|
||||||
652
assets/storefront_ids.json
Normal file
652
assets/storefront_ids.json
Normal file
@@ -0,0 +1,652 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Algeria",
|
||||||
|
"code": "DZ",
|
||||||
|
"storefrontId": 143563
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Angola",
|
||||||
|
"code": "AO",
|
||||||
|
"storefrontId": 143564
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anguilla",
|
||||||
|
"code": "AI",
|
||||||
|
"storefrontId": 143538
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Antigua & Barbuda",
|
||||||
|
"code": "AG",
|
||||||
|
"storefrontId": 143540
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Argentina",
|
||||||
|
"code": "AR",
|
||||||
|
"storefrontId": 143505
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Armenia",
|
||||||
|
"code": "AM",
|
||||||
|
"storefrontId": 143524
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Australia",
|
||||||
|
"code": "AU",
|
||||||
|
"storefrontId": 143460
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Austria",
|
||||||
|
"code": "AT",
|
||||||
|
"storefrontId": 143445
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Azerbaijan",
|
||||||
|
"code": "AZ",
|
||||||
|
"storefrontId": 143568
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bahrain",
|
||||||
|
"code": "BH",
|
||||||
|
"storefrontId": 143559
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bangladesh",
|
||||||
|
"code": "BD",
|
||||||
|
"storefrontId": 143490
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Barbados",
|
||||||
|
"code": "BB",
|
||||||
|
"storefrontId": 143541
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Belarus",
|
||||||
|
"code": "BY",
|
||||||
|
"storefrontId": 143565
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Belgium",
|
||||||
|
"code": "BE",
|
||||||
|
"storefrontId": 143446
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Belize",
|
||||||
|
"code": "BZ",
|
||||||
|
"storefrontId": 143555
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bermuda",
|
||||||
|
"code": "BM",
|
||||||
|
"storefrontId": 143542
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bolivia",
|
||||||
|
"code": "BO",
|
||||||
|
"storefrontId": 143556
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Botswana",
|
||||||
|
"code": "BW",
|
||||||
|
"storefrontId": 143525
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Brazil",
|
||||||
|
"code": "BR",
|
||||||
|
"storefrontId": 143503
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "British Virgin Islands",
|
||||||
|
"code": "VG",
|
||||||
|
"storefrontId": 143543
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Brunei",
|
||||||
|
"code": "BN",
|
||||||
|
"storefrontId": 143560
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bulgaria",
|
||||||
|
"code": "BG",
|
||||||
|
"storefrontId": 143526
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Canada",
|
||||||
|
"code": "CA",
|
||||||
|
"storefrontId": 143455
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cayman Islands",
|
||||||
|
"code": "KY",
|
||||||
|
"storefrontId": 143544
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chile",
|
||||||
|
"code": "CL",
|
||||||
|
"storefrontId": 143483
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "China",
|
||||||
|
"code": "CN",
|
||||||
|
"storefrontId": 143465
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Colombia",
|
||||||
|
"code": "CO",
|
||||||
|
"storefrontId": 143501
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Costa Rica",
|
||||||
|
"code": "CR",
|
||||||
|
"storefrontId": 143495
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cote D’Ivoire",
|
||||||
|
"code": "CI",
|
||||||
|
"storefrontId": 143527
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Croatia",
|
||||||
|
"code": "HR",
|
||||||
|
"storefrontId": 143494
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cyprus",
|
||||||
|
"code": "CY",
|
||||||
|
"storefrontId": 143557
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Czech Republic",
|
||||||
|
"code": "CZ",
|
||||||
|
"storefrontId": 143489
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Denmark",
|
||||||
|
"code": "DK",
|
||||||
|
"storefrontId": 143458
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dominica",
|
||||||
|
"code": "DM",
|
||||||
|
"storefrontId": 143545
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dominican Rep.",
|
||||||
|
"code": "DO",
|
||||||
|
"storefrontId": 143508
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ecuador",
|
||||||
|
"code": "EC",
|
||||||
|
"storefrontId": 143509
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Egypt",
|
||||||
|
"code": "EG",
|
||||||
|
"storefrontId": 143516
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "El Salvador",
|
||||||
|
"code": "SV",
|
||||||
|
"storefrontId": 143506
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Estonia",
|
||||||
|
"code": "EE",
|
||||||
|
"storefrontId": 143518
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Finland",
|
||||||
|
"code": "FI",
|
||||||
|
"storefrontId": 143447
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "France",
|
||||||
|
"code": "FR",
|
||||||
|
"storefrontId": 143442
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Germany",
|
||||||
|
"code": "DE",
|
||||||
|
"storefrontId": 143443
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ghana",
|
||||||
|
"code": "GH",
|
||||||
|
"storefrontId": 143573
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Greece",
|
||||||
|
"code": "GR",
|
||||||
|
"storefrontId": 143448
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Grenada",
|
||||||
|
"code": "GD",
|
||||||
|
"storefrontId": 143546
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Guatemala",
|
||||||
|
"code": "GT",
|
||||||
|
"storefrontId": 143504
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Guyana",
|
||||||
|
"code": "GY",
|
||||||
|
"storefrontId": 143553
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Honduras",
|
||||||
|
"code": "HN",
|
||||||
|
"storefrontId": 143510
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hong Kong",
|
||||||
|
"code": "HK",
|
||||||
|
"storefrontId": 143463
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hungary",
|
||||||
|
"code": "HU",
|
||||||
|
"storefrontId": 143482
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Iceland",
|
||||||
|
"code": "IS",
|
||||||
|
"storefrontId": 143558
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "India",
|
||||||
|
"code": "IN",
|
||||||
|
"storefrontId": 143467
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Indonesia",
|
||||||
|
"code": "ID",
|
||||||
|
"storefrontId": 143476
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ireland",
|
||||||
|
"code": "IE",
|
||||||
|
"storefrontId": 143449
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Israel",
|
||||||
|
"code": "IL",
|
||||||
|
"storefrontId": 143491
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Italy",
|
||||||
|
"code": "IT",
|
||||||
|
"storefrontId": 143450
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jamaica",
|
||||||
|
"code": "JM",
|
||||||
|
"storefrontId": 143511
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Japan",
|
||||||
|
"code": "JP",
|
||||||
|
"storefrontId": 143462
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jordan",
|
||||||
|
"code": "JO",
|
||||||
|
"storefrontId": 143528
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Kazakstan",
|
||||||
|
"code": "KZ",
|
||||||
|
"storefrontId": 143517
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Kenya",
|
||||||
|
"code": "KE",
|
||||||
|
"storefrontId": 143529
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Korea, Republic Of",
|
||||||
|
"code": "KR",
|
||||||
|
"storefrontId": 143466
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Kuwait",
|
||||||
|
"code": "KW",
|
||||||
|
"storefrontId": 143493
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Latvia",
|
||||||
|
"code": "LV",
|
||||||
|
"storefrontId": 143519
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lebanon",
|
||||||
|
"code": "LB",
|
||||||
|
"storefrontId": 143497
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Liechtenstein",
|
||||||
|
"code": "LI",
|
||||||
|
"storefrontId": 143522
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lithuania",
|
||||||
|
"code": "LT",
|
||||||
|
"storefrontId": 143520
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Luxembourg",
|
||||||
|
"code": "LU",
|
||||||
|
"storefrontId": 143451
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Macau",
|
||||||
|
"code": "MO",
|
||||||
|
"storefrontId": 143515
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Macedonia",
|
||||||
|
"code": "MK",
|
||||||
|
"storefrontId": 143530
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Madagascar",
|
||||||
|
"code": "MG",
|
||||||
|
"storefrontId": 143531
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Malaysia",
|
||||||
|
"code": "MY",
|
||||||
|
"storefrontId": 143473
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Maldives",
|
||||||
|
"code": "MV",
|
||||||
|
"storefrontId": 143488
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mali",
|
||||||
|
"code": "ML",
|
||||||
|
"storefrontId": 143532
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Malta",
|
||||||
|
"code": "MT",
|
||||||
|
"storefrontId": 143521
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mauritius",
|
||||||
|
"code": "MU",
|
||||||
|
"storefrontId": 143533
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mexico",
|
||||||
|
"code": "MX",
|
||||||
|
"storefrontId": 143468
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Moldova, Republic Of",
|
||||||
|
"code": "MD",
|
||||||
|
"storefrontId": 143523
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Montserrat",
|
||||||
|
"code": "MS",
|
||||||
|
"storefrontId": 143547
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nepal",
|
||||||
|
"code": "NP",
|
||||||
|
"storefrontId": 143484
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Netherlands",
|
||||||
|
"code": "NL",
|
||||||
|
"storefrontId": 143452
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "New Zealand",
|
||||||
|
"code": "NZ",
|
||||||
|
"storefrontId": 143461
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nicaragua",
|
||||||
|
"code": "NI",
|
||||||
|
"storefrontId": 143512
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Niger",
|
||||||
|
"code": "NE",
|
||||||
|
"storefrontId": 143534
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nigeria",
|
||||||
|
"code": "NG",
|
||||||
|
"storefrontId": 143561
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Norway",
|
||||||
|
"code": "NO",
|
||||||
|
"storefrontId": 143457
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Oman",
|
||||||
|
"code": "OM",
|
||||||
|
"storefrontId": 143562
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pakistan",
|
||||||
|
"code": "PK",
|
||||||
|
"storefrontId": 143477
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Panama",
|
||||||
|
"code": "PA",
|
||||||
|
"storefrontId": 143485
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Paraguay",
|
||||||
|
"code": "PY",
|
||||||
|
"storefrontId": 143513
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Peru",
|
||||||
|
"code": "PE",
|
||||||
|
"storefrontId": 143507
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Philippines",
|
||||||
|
"code": "PH",
|
||||||
|
"storefrontId": 143474
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Poland",
|
||||||
|
"code": "PL",
|
||||||
|
"storefrontId": 143478
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Portugal",
|
||||||
|
"code": "PT",
|
||||||
|
"storefrontId": 143453
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qatar",
|
||||||
|
"code": "QA",
|
||||||
|
"storefrontId": 143498
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Romania",
|
||||||
|
"code": "RO",
|
||||||
|
"storefrontId": 143487
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Russia",
|
||||||
|
"code": "RU",
|
||||||
|
"storefrontId": 143469
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Saudi Arabia",
|
||||||
|
"code": "SA",
|
||||||
|
"storefrontId": 143479
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Senegal",
|
||||||
|
"code": "SN",
|
||||||
|
"storefrontId": 143535
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Serbia",
|
||||||
|
"code": "RS",
|
||||||
|
"storefrontId": 143500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Singapore",
|
||||||
|
"code": "SG",
|
||||||
|
"storefrontId": 143464
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Slovakia",
|
||||||
|
"code": "SK",
|
||||||
|
"storefrontId": 143496
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Slovenia",
|
||||||
|
"code": "SI",
|
||||||
|
"storefrontId": 143499
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "South Africa",
|
||||||
|
"code": "ZA",
|
||||||
|
"storefrontId": 143472
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Spain",
|
||||||
|
"code": "ES",
|
||||||
|
"storefrontId": 143454
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sri Lanka",
|
||||||
|
"code": "LK",
|
||||||
|
"storefrontId": 143486
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "St. Kitts & Nevis",
|
||||||
|
"code": "KN",
|
||||||
|
"storefrontId": 143548
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "St. Lucia",
|
||||||
|
"code": "LC",
|
||||||
|
"storefrontId": 143549
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "St. Vincent & The Grenadines",
|
||||||
|
"code": "VC",
|
||||||
|
"storefrontId": 143550
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Suriname",
|
||||||
|
"code": "SR",
|
||||||
|
"storefrontId": 143554
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sweden",
|
||||||
|
"code": "SE",
|
||||||
|
"storefrontId": 143456
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Switzerland",
|
||||||
|
"code": "CH",
|
||||||
|
"storefrontId": 143459
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Taiwan",
|
||||||
|
"code": "TW",
|
||||||
|
"storefrontId": 143470
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tanzania",
|
||||||
|
"code": "TZ",
|
||||||
|
"storefrontId": 143572
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Thailand",
|
||||||
|
"code": "TH",
|
||||||
|
"storefrontId": 143475
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "The Bahamas",
|
||||||
|
"code": "BS",
|
||||||
|
"storefrontId": 143539
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Trinidad & Tobago",
|
||||||
|
"code": "TT",
|
||||||
|
"storefrontId": 143551
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tunisia",
|
||||||
|
"code": "TN",
|
||||||
|
"storefrontId": 143536
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Turkey",
|
||||||
|
"code": "TR",
|
||||||
|
"storefrontId": 143480
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Turks & Caicos",
|
||||||
|
"code": "TC",
|
||||||
|
"storefrontId": 143552
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Uganda",
|
||||||
|
"code": "UG",
|
||||||
|
"storefrontId": 143537
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "UK",
|
||||||
|
"code": "GB",
|
||||||
|
"storefrontId": 143444
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ukraine",
|
||||||
|
"code": "UA",
|
||||||
|
"storefrontId": 143492
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "United Arab Emirates",
|
||||||
|
"code": "AE",
|
||||||
|
"storefrontId": 143481
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Uruguay",
|
||||||
|
"code": "UY",
|
||||||
|
"storefrontId": 143514
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "USA",
|
||||||
|
"code": "US",
|
||||||
|
"storefrontId": 143441
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Uzbekistan",
|
||||||
|
"code": "UZ",
|
||||||
|
"storefrontId": 143566
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Venezuela",
|
||||||
|
"code": "VE",
|
||||||
|
"storefrontId": 143502
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vietnam",
|
||||||
|
"code": "VN",
|
||||||
|
"storefrontId": 143471
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Yemen",
|
||||||
|
"code": "YE",
|
||||||
|
"storefrontId": 143571
|
||||||
|
}
|
||||||
|
]
|
||||||
24
config.toml
Normal file
24
config.toml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[language]
|
||||||
|
language = "zh-CN"
|
||||||
|
languageForGenre = "en_US"
|
||||||
|
|
||||||
|
[[devices]]
|
||||||
|
host = "127.0.0.1"
|
||||||
|
port = 58526
|
||||||
|
agentPort = 10020
|
||||||
|
fridaPath = "/system/bin/frida-server"
|
||||||
|
suMethod = "su -c"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
atmosConventToM4a = false
|
||||||
|
songNameFormat = "{disk}-{tracknum:02d} {title}"
|
||||||
|
dirPathFormat = "downloads/{artist}/{album}"
|
||||||
|
saveLyrics = true
|
||||||
|
saveCover = true
|
||||||
|
coverFormat = "jpg"
|
||||||
|
afterDownloaded = ""
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
embedMetadata = ["title", "artist", "album", "album_artist", "composer",
|
||||||
|
"genre", "created", "track", "tracknum", "disk", "lyrics", "cover", "copyright",
|
||||||
|
"record_company", "upc", "isrc"]
|
||||||
12
main.py
Normal file
12
main.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from src.cmd import NewInteractiveShell
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
cmd = NewInteractiveShell(loop)
|
||||||
|
try:
|
||||||
|
loop.run_until_complete(cmd.start())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
loop.stop()
|
||||||
718
poetry.lock
generated
Normal file
718
poetry.lock
generated
Normal file
@@ -0,0 +1,718 @@
|
|||||||
|
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.6.0"
|
||||||
|
description = "Reusable constraint types to use with typing.Annotated"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
|
||||||
|
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.3.0"
|
||||||
|
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
|
||||||
|
{file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
idna = ">=2.8"
|
||||||
|
sniffio = ">=1.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||||
|
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||||
|
trio = ["trio (>=0.23)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "beautifulsoup4"
|
||||||
|
version = "4.12.3"
|
||||||
|
description = "Screen-scraping library"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.0"
|
||||||
|
files = [
|
||||||
|
{file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"},
|
||||||
|
{file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
soupsieve = ">1.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
cchardet = ["cchardet"]
|
||||||
|
chardet = ["chardet"]
|
||||||
|
charset-normalizer = ["charset-normalizer"]
|
||||||
|
html5lib = ["html5lib"]
|
||||||
|
lxml = ["lxml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2024.2.2"
|
||||||
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
|
||||||
|
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "frida"
|
||||||
|
version = "16.2.1"
|
||||||
|
description = "Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "frida-16.2.1-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:df1cc56a494aa045bf3b48a6609658b992801b41b929e9013c7319b8301c6450"},
|
||||||
|
{file = "frida-16.2.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:2bf69733d56d6d15260f94a4b68551d758c77e3dd36ad5a71df18ba9852e44ce"},
|
||||||
|
{file = "frida-16.2.1-cp37-abi3-manylinux_2_17_aarch64.whl", hash = "sha256:4b4db71b9317086ad188f91de8b00aceb9fd8a4d89c353bd06587eaf6bb410a2"},
|
||||||
|
{file = "frida-16.2.1-cp37-abi3-manylinux_2_17_armv7l.whl", hash = "sha256:e15d612767493b29795522ee3e763b2dfcf8610dab8a1e337b936adc55991c55"},
|
||||||
|
{file = "frida-16.2.1-cp37-abi3-manylinux_2_5_i686.whl", hash = "sha256:51ca64ffd29c6df70429e2d96a6651f3d0a752223f55a0187181370391a28cba"},
|
||||||
|
{file = "frida-16.2.1-cp37-abi3-manylinux_2_5_x86_64.whl", hash = "sha256:2b549a18bfd09e5b67168bc890cfdc53b3ca01eb9fab99b0d06c3ffc530788ec"},
|
||||||
|
{file = "frida-16.2.1-cp37-abi3-win32.whl", hash = "sha256:ee3e63fa16bf494f840bcacaa955a6a2e793a25f84dc3ea5bd92885f8526aa7a"},
|
||||||
|
{file = "frida-16.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:0363340ab678b75045426529b4a7061b32f8095c01ae0e196f8764e9ee404e26"},
|
||||||
|
{file = "frida-16.2.1.tar.gz", hash = "sha256:64a011825ea21a5ed3e3d7589f04c1dec473e1a083beb4c57895dddf32caa7c9"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "frida-tools"
|
||||||
|
version = "12.3.0"
|
||||||
|
description = "Frida CLI tools"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "frida-tools-12.3.0.tar.gz", hash = "sha256:8edc67d1ae3792ff5b2dc63508cde4d247f92b7d0d7bf153d74a21a6d58dc045"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = ">=0.2.7,<1.0.0"
|
||||||
|
frida = ">=16.0.9,<17.0.0"
|
||||||
|
prompt-toolkit = ">=2.0.0,<4.0.0"
|
||||||
|
pygments = ">=2.0.2,<3.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.14.0"
|
||||||
|
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||||
|
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpcore"
|
||||||
|
version = "1.0.5"
|
||||||
|
description = "A minimal low-level HTTP client."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
|
||||||
|
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = "*"
|
||||||
|
h11 = ">=0.13,<0.15"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
asyncio = ["anyio (>=4.0,<5.0)"]
|
||||||
|
http2 = ["h2 (>=3,<5)"]
|
||||||
|
socks = ["socksio (==1.*)"]
|
||||||
|
trio = ["trio (>=0.22.0,<0.26.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpx"
|
||||||
|
version = "0.27.0"
|
||||||
|
description = "The next generation HTTP client."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
|
||||||
|
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
anyio = "*"
|
||||||
|
certifi = "*"
|
||||||
|
httpcore = "==1.*"
|
||||||
|
idna = "*"
|
||||||
|
sniffio = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotli", "brotlicffi"]
|
||||||
|
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||||
|
http2 = ["h2 (>=3,<5)"]
|
||||||
|
socks = ["socksio (==1.*)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.7"
|
||||||
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||||
|
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loguru"
|
||||||
|
version = "0.7.2"
|
||||||
|
description = "Python logging made (stupidly) simple"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
|
||||||
|
{file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
|
||||||
|
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lxml"
|
||||||
|
version = "5.2.1"
|
||||||
|
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1f7785f4f789fdb522729ae465adcaa099e2a3441519df750ebdccc481d961a1"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cc6ee342fb7fa2471bd9b6d6fdfc78925a697bf5c2bcd0a302e98b0d35bfad3"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:794f04eec78f1d0e35d9e0c36cbbb22e42d370dda1609fb03bcd7aeb458c6377"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817d420c60a5183953c783b0547d9eb43b7b344a2c46f69513d5952a78cddf3"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2213afee476546a7f37c7a9b4ad4d74b1e112a6fafffc9185d6d21f043128c81"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b070bbe8d3f0f6147689bed981d19bbb33070225373338df755a46893528104a"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e02c5175f63effbd7c5e590399c118d5db6183bbfe8e0d118bdb5c2d1b48d937"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3dc773b2861b37b41a6136e0b72a1a44689a9c4c101e0cddb6b854016acc0aa8"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:d7520db34088c96cc0e0a3ad51a4fd5b401f279ee112aa2b7f8f976d8582606d"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:bcbf4af004f98793a95355980764b3d80d47117678118a44a80b721c9913436a"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2b44bec7adf3e9305ce6cbfa47a4395667e744097faed97abb4728748ba7d47"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1c5bb205e9212d0ebddf946bc07e73fa245c864a5f90f341d11ce7b0b854475d"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2c9d147f754b1b0e723e6afb7ba1566ecb162fe4ea657f53d2139bbf894d050a"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3545039fa4779be2df51d6395e91a810f57122290864918b172d5dc7ca5bb433"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a91481dbcddf1736c98a80b122afa0f7296eeb80b72344d7f45dc9f781551f56"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2ddfe41ddc81f29a4c44c8ce239eda5ade4e7fc305fb7311759dd6229a080052"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a7baf9ffc238e4bf401299f50e971a45bfcc10a785522541a6e3179c83eabf0a"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:31e9a882013c2f6bd2f2c974241bf4ba68c85eba943648ce88936d23209a2e01"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0a15438253b34e6362b2dc41475e7f80de76320f335e70c5528b7148cac253a1"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-win32.whl", hash = "sha256:6992030d43b916407c9aa52e9673612ff39a575523c5f4cf72cdef75365709a5"},
|
||||||
|
{file = "lxml-5.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:da052e7962ea2d5e5ef5bc0355d55007407087392cf465b7ad84ce5f3e25fe0f"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:70ac664a48aa64e5e635ae5566f5227f2ab7f66a3990d67566d9907edcbbf867"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1ae67b4e737cddc96c99461d2f75d218bdf7a0c3d3ad5604d1f5e7464a2f9ffe"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f18a5a84e16886898e51ab4b1d43acb3083c39b14c8caeb3589aabff0ee0b270"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6f2c8372b98208ce609c9e1d707f6918cc118fea4e2c754c9f0812c04ca116d"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:394ed3924d7a01b5bd9a0d9d946136e1c2f7b3dc337196d99e61740ed4bc6fe1"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d077bc40a1fe984e1a9931e801e42959a1e6598edc8a3223b061d30fbd26bbc"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764b521b75701f60683500d8621841bec41a65eb739b8466000c6fdbc256c240"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3a6b45da02336895da82b9d472cd274b22dc27a5cea1d4b793874eead23dd14f"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:5ea7b6766ac2dfe4bcac8b8595107665a18ef01f8c8343f00710b85096d1b53a"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:e196a4ff48310ba62e53a8e0f97ca2bca83cdd2fe2934d8b5cb0df0a841b193a"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:200e63525948e325d6a13a76ba2911f927ad399ef64f57898cf7c74e69b71095"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dae0ed02f6b075426accbf6b2863c3d0a7eacc1b41fb40f2251d931e50188dad"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:ab31a88a651039a07a3ae327d68ebdd8bc589b16938c09ef3f32a4b809dc96ef"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:df2e6f546c4df14bc81f9498bbc007fbb87669f1bb707c6138878c46b06f6510"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5dd1537e7cc06efd81371f5d1a992bd5ab156b2b4f88834ca852de4a8ea523fa"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9b9ec9c9978b708d488bec36b9e4c94d88fd12ccac3e62134a9d17ddba910ea9"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8e77c69d5892cb5ba71703c4057091e31ccf534bd7f129307a4d084d90d014b8"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d5c70e04aac1eda5c829a26d1f75c6e5286c74743133d9f742cda8e53b9c2f"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c94e75445b00319c1fad60f3c98b09cd63fe1134a8a953dcd48989ef42318534"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-win32.whl", hash = "sha256:4951e4f7a5680a2db62f7f4ab2f84617674d36d2d76a729b9a8be4b59b3659be"},
|
||||||
|
{file = "lxml-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:5c670c0406bdc845b474b680b9a5456c561c65cf366f8db5a60154088c92d102"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:abc25c3cab9ec7fcd299b9bcb3b8d4a1231877e425c650fa1c7576c5107ab851"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6935bbf153f9a965f1e07c2649c0849d29832487c52bb4a5c5066031d8b44fd5"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d793bebb202a6000390a5390078e945bbb49855c29c7e4d56a85901326c3b5d9"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd5562927cdef7c4f5550374acbc117fd4ecc05b5007bdfa57cc5355864e0a4"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e7259016bc4345a31af861fdce942b77c99049d6c2107ca07dc2bba2435c1d9"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:530e7c04f72002d2f334d5257c8a51bf409db0316feee7c87e4385043be136af"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59689a75ba8d7ffca577aefd017d08d659d86ad4585ccc73e43edbfc7476781a"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f9737bf36262046213a28e789cc82d82c6ef19c85a0cf05e75c670a33342ac2c"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:3a74c4f27167cb95c1d4af1c0b59e88b7f3e0182138db2501c353555f7ec57f4"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:68a2610dbe138fa8c5826b3f6d98a7cfc29707b850ddcc3e21910a6fe51f6ca0"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f0a1bc63a465b6d72569a9bba9f2ef0334c4e03958e043da1920299100bc7c08"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c2d35a1d047efd68027817b32ab1586c1169e60ca02c65d428ae815b593e65d4"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:79bd05260359170f78b181b59ce871673ed01ba048deef4bf49a36ab3e72e80b"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:865bad62df277c04beed9478fe665b9ef63eb28fe026d5dedcb89b537d2e2ea6"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:44f6c7caff88d988db017b9b0e4ab04934f11e3e72d478031efc7edcac6c622f"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71e97313406ccf55d32cc98a533ee05c61e15d11b99215b237346171c179c0b0"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:057cdc6b86ab732cf361f8b4d8af87cf195a1f6dc5b0ff3de2dced242c2015e0"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f3bbbc998d42f8e561f347e798b85513ba4da324c2b3f9b7969e9c45b10f6169"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491755202eb21a5e350dae00c6d9a17247769c64dcf62d8c788b5c135e179dc4"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-win32.whl", hash = "sha256:8de8f9d6caa7f25b204fc861718815d41cbcf27ee8f028c89c882a0cf4ae4134"},
|
||||||
|
{file = "lxml-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:f2a9efc53d5b714b8df2b4b3e992accf8ce5bbdfe544d74d5c6766c9e1146a3a"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:70a9768e1b9d79edca17890175ba915654ee1725975d69ab64813dd785a2bd5c"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c38d7b9a690b090de999835f0443d8aa93ce5f2064035dfc48f27f02b4afc3d0"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5670fb70a828663cc37552a2a85bf2ac38475572b0e9b91283dc09efb52c41d1"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:958244ad566c3ffc385f47dddde4145088a0ab893504b54b52c041987a8c1863"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6241d4eee5f89453307c2f2bfa03b50362052ca0af1efecf9fef9a41a22bb4f"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2a66bf12fbd4666dd023b6f51223aed3d9f3b40fef06ce404cb75bafd3d89536"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:9123716666e25b7b71c4e1789ec829ed18663152008b58544d95b008ed9e21e9"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:0c3f67e2aeda739d1cc0b1102c9a9129f7dc83901226cc24dd72ba275ced4218"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d5792e9b3fb8d16a19f46aa8208987cfeafe082363ee2745ea8b643d9cc5b45"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:88e22fc0a6684337d25c994381ed8a1580a6f5ebebd5ad41f89f663ff4ec2885"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:21c2e6b09565ba5b45ae161b438e033a86ad1736b8c838c766146eff8ceffff9"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:afbbdb120d1e78d2ba8064a68058001b871154cc57787031b645c9142b937a62"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:627402ad8dea044dde2eccde4370560a2b750ef894c9578e1d4f8ffd54000461"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:e89580a581bf478d8dcb97d9cd011d567768e8bc4095f8557b21c4d4c5fea7d0"},
|
||||||
|
{file = "lxml-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:59565f10607c244bc4c05c0c5fa0c190c990996e0c719d05deec7030c2aa8289"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:857500f88b17a6479202ff5fe5f580fc3404922cd02ab3716197adf1ef628029"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56c22432809085b3f3ae04e6e7bdd36883d7258fcd90e53ba7b2e463efc7a6af"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a55ee573116ba208932e2d1a037cc4b10d2c1cb264ced2184d00b18ce585b2c0"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:6cf58416653c5901e12624e4013708b6e11142956e7f35e7a83f1ab02f3fe456"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:64c2baa7774bc22dd4474248ba16fe1a7f611c13ac6123408694d4cc93d66dbd"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:74b28c6334cca4dd704e8004cba1955af0b778cf449142e581e404bd211fb619"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7221d49259aa1e5a8f00d3d28b1e0b76031655ca74bb287123ef56c3db92f213"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3dbe858ee582cbb2c6294dc85f55b5f19c918c2597855e950f34b660f1a5ede6"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:04ab5415bf6c86e0518d57240a96c4d1fcfc3cb370bb2ac2a732b67f579e5a04"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:6ab833e4735a7e5533711a6ea2df26459b96f9eec36d23f74cafe03631647c41"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f443cdef978430887ed55112b491f670bba6462cea7a7742ff8f14b7abb98d75"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:9e2addd2d1866fe112bc6f80117bcc6bc25191c5ed1bfbcf9f1386a884252ae8"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:f51969bac61441fd31f028d7b3b45962f3ecebf691a510495e5d2cd8c8092dbd"},
|
||||||
|
{file = "lxml-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b0b58fbfa1bf7367dde8a557994e3b1637294be6cf2169810375caf8571a085c"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3e183c6e3298a2ed5af9d7a356ea823bccaab4ec2349dc9ed83999fd289d14d5"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:804f74efe22b6a227306dd890eecc4f8c59ff25ca35f1f14e7482bbce96ef10b"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08802f0c56ed150cc6885ae0788a321b73505d2263ee56dad84d200cab11c07a"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f8c09ed18ecb4ebf23e02b8e7a22a05d6411911e6fabef3a36e4f371f4f2585"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3d30321949861404323c50aebeb1943461a67cd51d4200ab02babc58bd06a86"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:b560e3aa4b1d49e0e6c847d72665384db35b2f5d45f8e6a5c0072e0283430533"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:058a1308914f20784c9f4674036527e7c04f7be6fb60f5d61353545aa7fcb739"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:adfb84ca6b87e06bc6b146dc7da7623395db1e31621c4785ad0658c5028b37d7"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:417d14450f06d51f363e41cace6488519038f940676ce9664b34ebf5653433a5"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a2dfe7e2473f9b59496247aad6e23b405ddf2e12ef0765677b0081c02d6c2c0b"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bf2e2458345d9bffb0d9ec16557d8858c9c88d2d11fed53998512504cd9df49b"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:58278b29cb89f3e43ff3e0c756abbd1518f3ee6adad9e35b51fb101c1c1daaec"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:64641a6068a16201366476731301441ce93457eb8452056f570133a6ceb15fca"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:78bfa756eab503673991bdcf464917ef7845a964903d3302c5f68417ecdc948c"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:11a04306fcba10cd9637e669fd73aa274c1c09ca64af79c041aa820ea992b637"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-win32.whl", hash = "sha256:66bc5eb8a323ed9894f8fa0ee6cb3e3fb2403d99aee635078fd19a8bc7a5a5da"},
|
||||||
|
{file = "lxml-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:9676bfc686fa6a3fa10cd4ae6b76cae8be26eb5ec6811d2a325636c460da1806"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cf22b41fdae514ee2f1691b6c3cdeae666d8b7fa9434de445f12bbeee0cf48dd"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec42088248c596dbd61d4ae8a5b004f97a4d91a9fd286f632e42e60b706718d7"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd53553ddad4a9c2f1f022756ae64abe16da1feb497edf4d9f87f99ec7cf86bd"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feaa45c0eae424d3e90d78823f3828e7dc42a42f21ed420db98da2c4ecf0a2cb"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddc678fb4c7e30cf830a2b5a8d869538bc55b28d6c68544d09c7d0d8f17694dc"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:853e074d4931dbcba7480d4dcab23d5c56bd9607f92825ab80ee2bd916edea53"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc4691d60512798304acb9207987e7b2b7c44627ea88b9d77489bbe3e6cc3bd4"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:beb72935a941965c52990f3a32d7f07ce869fe21c6af8b34bf6a277b33a345d3"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:6588c459c5627fefa30139be4d2e28a2c2a1d0d1c265aad2ba1935a7863a4913"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:588008b8497667f1ddca7c99f2f85ce8511f8f7871b4a06ceede68ab62dff64b"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6787b643356111dfd4032b5bffe26d2f8331556ecb79e15dacb9275da02866e"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7c17b64b0a6ef4e5affae6a3724010a7a66bda48a62cfe0674dabd46642e8b54"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:27aa20d45c2e0b8cd05da6d4759649170e8dfc4f4e5ef33a34d06f2d79075d57"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d4f2cc7060dc3646632d7f15fe68e2fa98f58e35dd5666cd525f3b35d3fed7f8"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff46d772d5f6f73564979cd77a4fffe55c916a05f3cb70e7c9c0590059fb29ef"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96323338e6c14e958d775700ec8a88346014a85e5de73ac7967db0367582049b"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:52421b41ac99e9d91934e4d0d0fe7da9f02bfa7536bb4431b4c05c906c8c6919"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7a7efd5b6d3e30d81ec68ab8a88252d7c7c6f13aaa875009fe3097eb4e30b84c"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ed777c1e8c99b63037b91f9d73a6aad20fd035d77ac84afcc205225f8f41188"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-win32.whl", hash = "sha256:644df54d729ef810dcd0f7732e50e5ad1bd0a135278ed8d6bcb06f33b6b6f708"},
|
||||||
|
{file = "lxml-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:9ca66b8e90daca431b7ca1408cae085d025326570e57749695d6a01454790e95"},
|
||||||
|
{file = "lxml-5.2.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b0ff53900566bc6325ecde9181d89afadc59c5ffa39bddf084aaedfe3b06a11"},
|
||||||
|
{file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd6037392f2d57793ab98d9e26798f44b8b4da2f2464388588f48ac52c489ea1"},
|
||||||
|
{file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9c07e7a45bb64e21df4b6aa623cb8ba214dfb47d2027d90eac197329bb5e94"},
|
||||||
|
{file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3249cc2989d9090eeac5467e50e9ec2d40704fea9ab72f36b034ea34ee65ca98"},
|
||||||
|
{file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f42038016852ae51b4088b2862126535cc4fc85802bfe30dea3500fdfaf1864e"},
|
||||||
|
{file = "lxml-5.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:533658f8fbf056b70e434dff7e7aa611bcacb33e01f75de7f821810e48d1bb66"},
|
||||||
|
{file = "lxml-5.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:622020d4521e22fb371e15f580d153134bfb68d6a429d1342a25f051ec72df1c"},
|
||||||
|
{file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efa7b51824aa0ee957ccd5a741c73e6851de55f40d807f08069eb4c5a26b2baa"},
|
||||||
|
{file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c6ad0fbf105f6bcc9300c00010a2ffa44ea6f555df1a2ad95c88f5656104817"},
|
||||||
|
{file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e233db59c8f76630c512ab4a4daf5a5986da5c3d5b44b8e9fc742f2a24dbd460"},
|
||||||
|
{file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a014510830df1475176466b6087fc0c08b47a36714823e58d8b8d7709132a96"},
|
||||||
|
{file = "lxml-5.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d38c8f50ecf57f0463399569aa388b232cf1a2ffb8f0a9a5412d0db57e054860"},
|
||||||
|
{file = "lxml-5.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5aea8212fb823e006b995c4dda533edcf98a893d941f173f6c9506126188860d"},
|
||||||
|
{file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff097ae562e637409b429a7ac958a20aab237a0378c42dabaa1e3abf2f896e5f"},
|
||||||
|
{file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f5d65c39f16717a47c36c756af0fb36144069c4718824b7533f803ecdf91138"},
|
||||||
|
{file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3d0c3dd24bb4605439bf91068598d00c6370684f8de4a67c2992683f6c309d6b"},
|
||||||
|
{file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e32be23d538753a8adb6c85bd539f5fd3b15cb987404327c569dfc5fd8366e85"},
|
||||||
|
{file = "lxml-5.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cc518cea79fd1e2f6c90baafa28906d4309d24f3a63e801d855e7424c5b34144"},
|
||||||
|
{file = "lxml-5.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a0af35bd8ebf84888373630f73f24e86bf016642fb8576fba49d3d6b560b7cbc"},
|
||||||
|
{file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8aca2e3a72f37bfc7b14ba96d4056244001ddcc18382bd0daa087fd2e68a354"},
|
||||||
|
{file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ca1e8188b26a819387b29c3895c47a5e618708fe6f787f3b1a471de2c4a94d9"},
|
||||||
|
{file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c8ba129e6d3b0136a0f50345b2cb3db53f6bda5dd8c7f5d83fbccba97fb5dcb5"},
|
||||||
|
{file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e998e304036198b4f6914e6a1e2b6f925208a20e2042563d9734881150c6c246"},
|
||||||
|
{file = "lxml-5.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d3be9b2076112e51b323bdf6d5a7f8a798de55fb8d95fcb64bd179460cdc0704"},
|
||||||
|
{file = "lxml-5.2.1.tar.gz", hash = "sha256:3f7765e69bbce0906a7c74d5fe46d2c7a7596147318dbc08e4a2431f3060e306"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
cssselect = ["cssselect (>=0.7)"]
|
||||||
|
html-clean = ["lxml-html-clean"]
|
||||||
|
html5 = ["html5lib"]
|
||||||
|
htmlsoup = ["BeautifulSoup4"]
|
||||||
|
source = ["Cython (>=3.0.10)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "m3u8"
|
||||||
|
version = "4.1.0"
|
||||||
|
description = "Python m3u8 parser"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "m3u8-4.1.0-py3-none-any.whl", hash = "sha256:981daed09f57b7590721b6437278e49f2c36c1bceaa8fbe48f585e1745571d17"},
|
||||||
|
{file = "m3u8-4.1.0.tar.gz", hash = "sha256:3b9d7e5bafbaae89f2464cb16f397887d8decf6b1b48d8de58711414dc1c7b45"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prompt-toolkit"
|
||||||
|
version = "3.0.43"
|
||||||
|
description = "Library for building powerful interactive command lines in Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7.0"
|
||||||
|
files = [
|
||||||
|
{file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"},
|
||||||
|
{file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
wcwidth = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pure-python-adb"
|
||||||
|
version = "0.3.0.dev0"
|
||||||
|
description = "Pure python implementation of the adb client"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "pure-python-adb-0.3.0.dev0.tar.gz", hash = "sha256:0ecc89d780160cfe03260ba26df2c471a05263b2cad0318363573ee8043fb94d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
async = ["aiofiles (>=0.4.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.7.1"
|
||||||
|
description = "Data validation using Python type hints"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"},
|
||||||
|
{file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
annotated-types = ">=0.4.0"
|
||||||
|
pydantic-core = "2.18.2"
|
||||||
|
typing-extensions = ">=4.6.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
email = ["email-validator (>=2.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.18.2"
|
||||||
|
description = "Core functionality for Pydantic validation and serialization"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"},
|
||||||
|
{file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"},
|
||||||
|
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"},
|
||||||
|
{file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.17.2"
|
||||||
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
|
||||||
|
{file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
plugins = ["importlib-metadata"]
|
||||||
|
windows-terminal = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "2023.12.25"
|
||||||
|
description = "Alternative regular expression module, to replace re."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"},
|
||||||
|
{file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"},
|
||||||
|
{file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"},
|
||||||
|
{file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"},
|
||||||
|
{file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"},
|
||||||
|
{file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"},
|
||||||
|
{file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"},
|
||||||
|
{file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.16.0"
|
||||||
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
files = [
|
||||||
|
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||||
|
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.3.1"
|
||||||
|
description = "Sniff out which async library your code is running under"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
||||||
|
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "soupsieve"
|
||||||
|
version = "2.5"
|
||||||
|
description = "A modern CSS selector implementation for Beautiful Soup."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"},
|
||||||
|
{file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tenacity"
|
||||||
|
version = "8.2.3"
|
||||||
|
description = "Retry code until it succeeds"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"},
|
||||||
|
{file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
doc = ["reno", "sphinx", "tornado (>=4.5)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.11.0"
|
||||||
|
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
|
||||||
|
{file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wcwidth"
|
||||||
|
version = "0.2.13"
|
||||||
|
description = "Measures the displayed width of unicode strings in a terminal"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
|
||||||
|
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "win32-setctime"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "A small Python utility to set file creation time on Windows"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
|
||||||
|
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.0"
|
||||||
|
python-versions = "^3.11"
|
||||||
|
content-hash = "d1e738dcf8fd6798c036097d82214e2ad9a2a3e881a33721792a490a8e4850dc"
|
||||||
26
pyproject.toml
Normal file
26
pyproject.toml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "applemusicdecrypt"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["WorldObservationLog <wolc@duck.com>"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.11"
|
||||||
|
httpx = "^0.27.0"
|
||||||
|
regex = "^2023.12.25"
|
||||||
|
pydantic = "^2.7.0"
|
||||||
|
loguru = "^0.7.2"
|
||||||
|
six = "^1.16.0"
|
||||||
|
lxml = "^5.2.1"
|
||||||
|
beautifulsoup4 = "^4.12.3"
|
||||||
|
m3u8 = "^4.1.0"
|
||||||
|
frida-tools = "^12.3.0"
|
||||||
|
pure-python-adb = "^0.3.0.dev0"
|
||||||
|
frida = "^16.2.1"
|
||||||
|
tenacity = "^8.2.3"
|
||||||
|
prompt-toolkit = "^3.0.43"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
150
src/adb.py
Normal file
150
src/adb.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import frida
|
||||||
|
import regex
|
||||||
|
from loguru import logger
|
||||||
|
from ppadb.client import Client as AdbClient
|
||||||
|
from ppadb.device import Device as AdbDevice
|
||||||
|
|
||||||
|
from src.exceptions import FridaNotExistException, ADBConnectException, FailedGetAuthParamException
|
||||||
|
from src.types import AuthParams
|
||||||
|
|
||||||
|
|
||||||
|
class Device:
|
||||||
|
host: str
|
||||||
|
client: AdbClient
|
||||||
|
device: AdbDevice
|
||||||
|
fridaPath: str
|
||||||
|
fridaPort: int
|
||||||
|
fridaDevice: frida.core.Device = None
|
||||||
|
fridaSession: frida.core.Session = None
|
||||||
|
pid: int
|
||||||
|
authParams: AuthParams = None
|
||||||
|
suMethod: str
|
||||||
|
decryptLock: asyncio.Lock
|
||||||
|
|
||||||
|
def __init__(self, host="127.0.0.1", port=5037,
|
||||||
|
frida_path="/data/local/tmp/frida-server-16.2.1-android-x86_64", su_method: str = "su -c"):
|
||||||
|
self.client = AdbClient(host, port)
|
||||||
|
self.fridaPath = frida_path
|
||||||
|
self.suMethod = su_method
|
||||||
|
self.host = host
|
||||||
|
self.decryptLock = asyncio.Lock()
|
||||||
|
|
||||||
|
def connect(self, host: str, port: int):
|
||||||
|
try:
|
||||||
|
status = self.client.remote_connect(host, port)
|
||||||
|
except RuntimeError:
|
||||||
|
subprocess.run("adb devices", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
status = self.client.remote_connect(host, port)
|
||||||
|
if not status:
|
||||||
|
raise ADBConnectException
|
||||||
|
self.device = self.client.device(f"{host}:{port}")
|
||||||
|
|
||||||
|
def _execute_command(self, cmd: str, su: bool = False) -> Optional[str]:
|
||||||
|
if su:
|
||||||
|
cmd = cmd.replace("\"", "\\\"")
|
||||||
|
output = self.device.shell(f"{self.suMethod} \"{cmd}\"")
|
||||||
|
else:
|
||||||
|
output = self.device.shell(cmd, timeout=30)
|
||||||
|
if not output:
|
||||||
|
return ""
|
||||||
|
return output
|
||||||
|
|
||||||
|
def _if_frida_running(self) -> bool:
|
||||||
|
logger.debug("checking if frida-server running")
|
||||||
|
output = self._execute_command("ps -e | grep frida")
|
||||||
|
if not output or "frida" not in output:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _start_remote_frida(self):
|
||||||
|
logger.debug("starting remote frida")
|
||||||
|
output = f"(ls {self.fridaPath} && echo True) || echo False"
|
||||||
|
if not output or "True" not in output:
|
||||||
|
raise FridaNotExistException
|
||||||
|
permission = self._execute_command(f"ls -l {self.fridaPath}")
|
||||||
|
if not permission or "x" not in permission[:10]:
|
||||||
|
self._execute_command(f"chmod +x {self.fridaPath}", True)
|
||||||
|
self._execute_command(f"{self.fridaPath} &", True)
|
||||||
|
|
||||||
|
def _start_forward(self, local_port: int, remote_port: int):
|
||||||
|
self.device.forward(f"tcp:{local_port}", f"tcp:{remote_port}")
|
||||||
|
|
||||||
|
def _inject_frida(self, frida_port):
|
||||||
|
logger.debug("injecting agent script")
|
||||||
|
self.fridaPort = frida_port
|
||||||
|
with open("agent.js", "r") as f:
|
||||||
|
agent = f.read().replace("2147483647", str(frida_port))
|
||||||
|
if not self.fridaDevice:
|
||||||
|
frida.get_device_manager().add_remote_device(self.device.serial)
|
||||||
|
self.fridaDevice = frida.get_device_manager().get_device(self.device.serial)
|
||||||
|
self.pid = self.fridaDevice.spawn("com.apple.android.music")
|
||||||
|
self.fridaSession = self.fridaDevice.attach(self.pid)
|
||||||
|
script: frida.core.Script = self.fridaSession.create_script(agent)
|
||||||
|
script.load()
|
||||||
|
self.fridaDevice.resume(self.pid)
|
||||||
|
|
||||||
|
def restart_inject_frida(self):
|
||||||
|
self.fridaSession.detach()
|
||||||
|
self._kill_apple_music()
|
||||||
|
self._inject_frida(self.fridaPort)
|
||||||
|
|
||||||
|
def _kill_apple_music(self):
|
||||||
|
self._execute_command(f"kill -9 {self.pid}", su=True)
|
||||||
|
|
||||||
|
def start_inject_frida(self, frida_port):
|
||||||
|
if not self._if_frida_running():
|
||||||
|
self._start_remote_frida()
|
||||||
|
self._start_forward(frida_port, frida_port)
|
||||||
|
self._inject_frida(frida_port)
|
||||||
|
|
||||||
|
def _get_dsid(self) -> str:
|
||||||
|
logger.debug("getting dsid")
|
||||||
|
dsid = self._execute_command(
|
||||||
|
"sqlite3 /data/data/com.apple.android.music/files/mpl_db/cookies.sqlitedb \"select value from cookies where name='X-Dsid';\"", True)
|
||||||
|
if not dsid:
|
||||||
|
raise FailedGetAuthParamException
|
||||||
|
return dsid.strip()
|
||||||
|
|
||||||
|
def _get_account_token(self, dsid: str) -> str:
|
||||||
|
logger.debug("getting account token")
|
||||||
|
account_token = self._execute_command(
|
||||||
|
f"sqlite3 /data/data/com.apple.android.music/files/mpl_db/cookies.sqlitedb \"select value from cookies where name='mz_at_ssl-{dsid}';\"", True)
|
||||||
|
if not account_token:
|
||||||
|
raise FailedGetAuthParamException
|
||||||
|
return account_token.strip()
|
||||||
|
|
||||||
|
def _get_access_token(self) -> str:
|
||||||
|
logger.debug("getting access token")
|
||||||
|
prefs = self._execute_command("cat /data/data/com.apple.android.music/shared_prefs/preferences.xml", True)
|
||||||
|
match = regex.search(r"eyJr[^<]*", prefs)
|
||||||
|
if not match:
|
||||||
|
raise FailedGetAuthParamException
|
||||||
|
return match[0]
|
||||||
|
|
||||||
|
def _get_storefront(self) -> str | None:
|
||||||
|
logger.debug("getting storefront")
|
||||||
|
storefront_id = self._execute_command(
|
||||||
|
"sqlite3 /data/data/com.apple.android.music/files/mpl_db/accounts.sqlitedb \"select storeFront from account;\"", True)
|
||||||
|
if not storefront_id:
|
||||||
|
raise FailedGetAuthParamException
|
||||||
|
with open("assets/storefront_ids.json") as f:
|
||||||
|
storefront_ids = json.load(f)
|
||||||
|
for storefront_mapping in storefront_ids:
|
||||||
|
if storefront_mapping["storefrontId"] == int(storefront_id.split("-")[0]):
|
||||||
|
return storefront_mapping["code"]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_auth_params(self):
|
||||||
|
if not self.authParams:
|
||||||
|
dsid = self._get_dsid()
|
||||||
|
token = self._get_account_token(dsid)
|
||||||
|
access_token = self._get_access_token()
|
||||||
|
storefront = self._get_storefront()
|
||||||
|
self.authParams = AuthParams(dsid=dsid, accountToken=token,
|
||||||
|
accountAccessToken=access_token, storefront=storefront)
|
||||||
|
return self.authParams
|
||||||
103
src/api.py
Normal file
103
src/api.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from ssl import SSLError
|
||||||
|
|
||||||
|
import httpcore
|
||||||
|
import httpx
|
||||||
|
import regex
|
||||||
|
|
||||||
|
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from src.models import *
|
||||||
|
|
||||||
|
client = httpx.AsyncClient()
|
||||||
|
lock = asyncio.Semaphore(1)
|
||||||
|
user_agent_browser = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||||
|
user_agent_itunes = "iTunes/12.11.3 (Windows; Microsoft Windows 10 x64 Professional Edition (Build 19041); x64) AppleWebKit/7611.1022.4001.1 (dt:2)"
|
||||||
|
user_agent_app = "Music/5.7 Android/10 model/Pixel6GR1YH build/1234 (dt:66)"
|
||||||
|
|
||||||
|
|
||||||
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
||||||
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
|
async def get_token():
|
||||||
|
req = await client.get("https://beta.music.apple.com")
|
||||||
|
index_js_uri = regex.findall(r"/assets/index-legacy-[^/]+\.js", req.text)[0]
|
||||||
|
js_req = await client.get("https://beta.music.apple.com" + index_js_uri)
|
||||||
|
token = regex.search(r'eyJh([^"]*)', js_req.text)[0]
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
||||||
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
|
async def download_song(url: str) -> bytes:
|
||||||
|
async with lock:
|
||||||
|
return (await client.get(url)).content
|
||||||
|
|
||||||
|
|
||||||
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
||||||
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
|
async def get_meta(album_id: str, token: str, storefront: str):
|
||||||
|
if "pl." in album_id:
|
||||||
|
mtype = "playlists"
|
||||||
|
else:
|
||||||
|
mtype = "albums"
|
||||||
|
req = await client.get(f"https://amp-api.music.apple.com/v1/catalog/{storefront}/{mtype}/{album_id}",
|
||||||
|
params={"omit[resource]": "autos", "include": "tracks,artists,record-labels",
|
||||||
|
"include[songs]": "artists", "fields[artists]": "name",
|
||||||
|
"fields[albums:albums]": "artistName,artwork,name,releaseDate,url",
|
||||||
|
"fields[record-labels]": "name"},
|
||||||
|
headers={"Authorization": f"Bearer {token}", "User-Agent": user_agent_browser,
|
||||||
|
"Origin": "https://music.apple.com"})
|
||||||
|
if mtype == "albums":
|
||||||
|
return AlbumMeta.model_validate(req.json())
|
||||||
|
else:
|
||||||
|
result = PlaylistMeta.model_validate(req.json())
|
||||||
|
result.data[0].attributes.artistName = "Apple Music"
|
||||||
|
if result.data[0].relationships.tracks.next:
|
||||||
|
page = 0
|
||||||
|
while True:
|
||||||
|
page += 100
|
||||||
|
page_req = await client.get(
|
||||||
|
f"https://amp-api.music.apple.com/v1/catalog/{storefront}/{mtype}/{album_id}/tracks?offset={page}",
|
||||||
|
headers={"Authorization": f"Bearer {token}", "User-Agent": user_agent_browser,
|
||||||
|
"Origin": "https://music.apple.com"})
|
||||||
|
page_result = TracksMeta.model_validate(page_req.json())
|
||||||
|
result.data[0].relationships.tracks.data.extend(page_result.data)
|
||||||
|
if not page_result.next:
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
||||||
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
|
async def get_cover(url: str, cover_format: str):
|
||||||
|
formatted_url = regex.sub('bb.jpg', f'bb.{cover_format}', url)
|
||||||
|
req = await client.get(formatted_url.replace("{w}x{h}", "10000x10000"),
|
||||||
|
headers={"User-Agent": user_agent_browser})
|
||||||
|
return req.content
|
||||||
|
|
||||||
|
|
||||||
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
||||||
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
|
async def get_info_from_adam(adam_id: str, token: str, storefront: str):
|
||||||
|
req = await client.get(f"https://amp-api.music.apple.com/v1/catalog/{storefront}/songs/{adam_id}",
|
||||||
|
params={"extend": "extendedAssetUrls", "include": "albums"},
|
||||||
|
headers={"Authorization": f"Bearer {token}", "User-Agent": user_agent_itunes,
|
||||||
|
"Origin": "https://music.apple.com"})
|
||||||
|
song_data_obj = SongData.model_validate(req.json())
|
||||||
|
for data in song_data_obj.data:
|
||||||
|
if data.id == adam_id:
|
||||||
|
return data
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
||||||
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
|
async def get_song_lyrics(song_id: str, storefront: str, token: str, dsid: str, account_token: str) -> str:
|
||||||
|
req = await client.get(f"https://amp-api.music.apple.com/v1/catalog/{storefront}/songs/{song_id}/lyrics",
|
||||||
|
headers={"Authorization": f"Bearer {token}", "User-Agent": user_agent_app,
|
||||||
|
"X-Dsid": dsid},
|
||||||
|
cookies={f"mz_at_ssl-{dsid}": account_token})
|
||||||
|
result = SongLyrics.model_validate(req.json())
|
||||||
|
return result.data[0].attributes.ttml
|
||||||
104
src/cmd.py
Normal file
104
src/cmd.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
from asyncio import Task
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
from prompt_toolkit import PromptSession, print_formatted_text, ANSI
|
||||||
|
from prompt_toolkit.patch_stdout import patch_stdout
|
||||||
|
|
||||||
|
from src.adb import Device
|
||||||
|
from src.api import get_token
|
||||||
|
from src.config import Config
|
||||||
|
from src.rip import rip_song, rip_album
|
||||||
|
from src.types import GlobalAuthParams
|
||||||
|
from src.url import AppleMusicURL, URLType
|
||||||
|
|
||||||
|
|
||||||
|
class NewInteractiveShell:
|
||||||
|
loop: asyncio.AbstractEventLoop
|
||||||
|
config: Config
|
||||||
|
tasks: list[Task] = []
|
||||||
|
devices: list[Device] = []
|
||||||
|
storefront_device_mapping: dict[str, list[Device]] = {}
|
||||||
|
anonymous_access_token: str
|
||||||
|
parser: argparse.ArgumentParser
|
||||||
|
|
||||||
|
def __init__(self, loop: asyncio.AbstractEventLoop):
|
||||||
|
self.loop = loop
|
||||||
|
self.config = Config.load_from_config()
|
||||||
|
self.anonymous_access_token = loop.run_until_complete(get_token())
|
||||||
|
|
||||||
|
self.parser = argparse.ArgumentParser(exit_on_error=False)
|
||||||
|
subparser = self.parser.add_subparsers()
|
||||||
|
download_parser = subparser.add_parser("download")
|
||||||
|
download_parser.add_argument("url", type=str)
|
||||||
|
download_parser.add_argument("-c", "--codec",
|
||||||
|
choices=["alac", "ec3", "aac", "aac-binaural", "aac-downmix"], default="alac")
|
||||||
|
download_parser.add_argument("-f", "--force", type=bool, default=False)
|
||||||
|
subparser.add_parser("exit")
|
||||||
|
|
||||||
|
logger.remove()
|
||||||
|
logger.add(lambda msg: print_formatted_text(ANSI(msg), end=""), colorize=True, level="INFO")
|
||||||
|
|
||||||
|
for device_info in self.config.devices:
|
||||||
|
device = Device(frida_path=device_info.fridaPath)
|
||||||
|
device.connect(device_info.host, device_info.port)
|
||||||
|
logger.info(f"Device {device_info.host}:{device_info.port} has connected")
|
||||||
|
self.devices.append(device)
|
||||||
|
auth_params = device.get_auth_params()
|
||||||
|
if not self.storefront_device_mapping.get(auth_params.storefront.lower()):
|
||||||
|
self.storefront_device_mapping.update({auth_params.storefront.lower(): []})
|
||||||
|
self.storefront_device_mapping[auth_params.storefront.lower()].append(device)
|
||||||
|
device.start_inject_frida(device_info.agentPort)
|
||||||
|
|
||||||
|
async def command_parser(self, cmd: str):
|
||||||
|
if not cmd.strip():
|
||||||
|
return
|
||||||
|
cmds = cmd.split(" ")
|
||||||
|
try:
|
||||||
|
args = self.parser.parse_args(cmds)
|
||||||
|
except argparse.ArgumentError:
|
||||||
|
logger.warning(f"Unknown command: {cmd}")
|
||||||
|
return
|
||||||
|
match cmds[0]:
|
||||||
|
case "download":
|
||||||
|
await self.do_download(args.url, args.codec, args.force)
|
||||||
|
case "exit":
|
||||||
|
self.loop.stop()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
async def do_download(self, raw_url: str, codec: str, force_download: bool):
|
||||||
|
url = AppleMusicURL.parse_url(raw_url)
|
||||||
|
devices = self.storefront_device_mapping.get(url.storefront)
|
||||||
|
if not devices:
|
||||||
|
logger.error(f"No device is available to decrypt the specified region: {url.storefront}")
|
||||||
|
available_devices = [device for device in devices if not device.decryptLock.locked()]
|
||||||
|
if not available_devices:
|
||||||
|
available_device: Device = random.choice(devices)
|
||||||
|
else:
|
||||||
|
available_device: Device = random.choice(available_devices)
|
||||||
|
global_auth_param = GlobalAuthParams.from_auth_params_and_token(available_device.get_auth_params(), self.anonymous_access_token)
|
||||||
|
match url.type:
|
||||||
|
case URLType.Song:
|
||||||
|
self.loop.create_task(rip_song(url, global_auth_param, codec, self.config, available_device, force_download))
|
||||||
|
case URLType.Album:
|
||||||
|
self.loop.create_task(rip_album(url, global_auth_param, codec, self.config, available_device))
|
||||||
|
|
||||||
|
async def handle_command(self):
|
||||||
|
session = PromptSession("> ")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
command = await session.prompt_async()
|
||||||
|
await self.command_parser(command)
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
return
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
with patch_stdout():
|
||||||
|
try:
|
||||||
|
await self.handle_command()
|
||||||
|
finally:
|
||||||
|
logger.info("Existing shell")
|
||||||
42
src/config.py
Normal file
42
src/config.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import tomllib
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Language(BaseModel):
|
||||||
|
language: str
|
||||||
|
languageForGenre: str
|
||||||
|
|
||||||
|
|
||||||
|
class Device(BaseModel):
|
||||||
|
host: str
|
||||||
|
port: int
|
||||||
|
agentPort: int
|
||||||
|
fridaPath: str
|
||||||
|
|
||||||
|
|
||||||
|
class Download(BaseModel):
|
||||||
|
atmosConventToM4a: bool
|
||||||
|
songNameFormat: str
|
||||||
|
dirPathFormat: str
|
||||||
|
saveLyrics: bool
|
||||||
|
saveCover: bool
|
||||||
|
coverFormat: str
|
||||||
|
afterDownloaded: str
|
||||||
|
|
||||||
|
|
||||||
|
class Metadata(BaseModel):
|
||||||
|
embedMetadata: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
class Config(BaseModel):
|
||||||
|
language: Language
|
||||||
|
devices: list[Device]
|
||||||
|
download: Download
|
||||||
|
metadata: Metadata
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_from_config(cls, config_file: str = "config.toml"):
|
||||||
|
with open(config_file, "r") as f:
|
||||||
|
config = tomllib.loads(f.read())
|
||||||
|
return cls.parse_obj(config)
|
||||||
48
src/decrypt.py
Normal file
48
src/decrypt.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from prompt_toolkit.shortcuts import ProgressBar
|
||||||
|
from loguru import logger
|
||||||
|
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log
|
||||||
|
|
||||||
|
from src.adb import Device
|
||||||
|
from src.exceptions import DecryptException
|
||||||
|
from src.models.song_data import Datum
|
||||||
|
from src.mp4 import SongInfo, SampleInfo
|
||||||
|
from src.types import defaultId, prefetchKey
|
||||||
|
|
||||||
|
|
||||||
|
async def decrypt(info: SongInfo, keys: list[str], manifest: Datum, device: Device) -> bytes:
|
||||||
|
async with device.decryptLock:
|
||||||
|
logger.info(f"Decrypting song: {manifest.attributes.artistName} - {manifest.attributes.name}")
|
||||||
|
reader, writer = await asyncio.open_connection(device.host, device.fridaPort)
|
||||||
|
decrypted = bytes()
|
||||||
|
last_index = 255
|
||||||
|
for sample in info.samples:
|
||||||
|
if last_index != sample.descIndex:
|
||||||
|
if len(decrypted) != 0:
|
||||||
|
writer.write(bytes([0, 0, 0, 0]))
|
||||||
|
key_uri = keys[sample.descIndex]
|
||||||
|
track_id = manifest.id
|
||||||
|
if key_uri == prefetchKey:
|
||||||
|
track_id = defaultId
|
||||||
|
writer.write(bytes([len(track_id)]))
|
||||||
|
writer.write(track_id.encode("utf-8"))
|
||||||
|
writer.write(bytes([len(key_uri)]))
|
||||||
|
writer.write(key_uri.encode("utf-8"))
|
||||||
|
last_index = sample.descIndex
|
||||||
|
result = await decrypt_sample(writer, reader, sample)
|
||||||
|
decrypted += result
|
||||||
|
writer.write(bytes([0, 0, 0, 0]))
|
||||||
|
writer.close()
|
||||||
|
return decrypted
|
||||||
|
|
||||||
|
|
||||||
|
async def decrypt_sample(writer: asyncio.StreamWriter, reader: asyncio.StreamReader, sample: SampleInfo) -> bytes:
|
||||||
|
writer.write(len(sample.data).to_bytes(4, byteorder="little", signed=False))
|
||||||
|
writer.write(sample.data)
|
||||||
|
result = await reader.read(len(sample.data))
|
||||||
|
if not result:
|
||||||
|
raise DecryptException
|
||||||
|
return result
|
||||||
18
src/exceptions.py
Normal file
18
src/exceptions.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
class FridaNotExistException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class ADBConnectException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class FailedGetAuthParamException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class DecryptException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class NotTimeSyncedLyricsException(Exception):
|
||||||
|
...
|
||||||
58
src/metadata.py
Normal file
58
src/metadata.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.api import get_cover
|
||||||
|
from src.models.song_data import Datum
|
||||||
|
from src.utils import ttml_convent_to_lrc
|
||||||
|
|
||||||
|
|
||||||
|
class SongMetadata(BaseModel):
|
||||||
|
title: str
|
||||||
|
artist: str
|
||||||
|
album_artist: str
|
||||||
|
album: str
|
||||||
|
composer: str
|
||||||
|
genre: str
|
||||||
|
created: str
|
||||||
|
track: str
|
||||||
|
tracknum: int
|
||||||
|
disk: int
|
||||||
|
lyrics: str
|
||||||
|
cover: bytes = None
|
||||||
|
cover_url: str
|
||||||
|
copyright: str
|
||||||
|
record_company: str
|
||||||
|
upc: str
|
||||||
|
isrc: str
|
||||||
|
|
||||||
|
def to_itags_params(self, embed_metadata: list[str], cover_format: str):
|
||||||
|
tags = []
|
||||||
|
for key, value in self.model_dump().items():
|
||||||
|
if key in embed_metadata and value:
|
||||||
|
if key == "cover":
|
||||||
|
continue
|
||||||
|
if key == "lyrics":
|
||||||
|
lrc = ttml_convent_to_lrc(value)
|
||||||
|
tags.append(f"{key}={lrc}")
|
||||||
|
continue
|
||||||
|
tags.append(f"{key}={value}")
|
||||||
|
return ":".join(tags)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_from_song_data(cls, song_data: Datum):
|
||||||
|
return cls(title=song_data.attributes.name, artist=song_data.attributes.artistName,
|
||||||
|
album_artist=song_data.relationships.albums.data[0].attributes.artistName,
|
||||||
|
album=song_data.attributes.albumName, composer=song_data.attributes.composerName,
|
||||||
|
genre=song_data.attributes.genreNames[0], created=song_data.attributes.releaseDate,
|
||||||
|
track=song_data.attributes.name, tracknum=song_data.attributes.trackNumber,
|
||||||
|
disk=song_data.attributes.discNumber, lyrics="", cover_url=song_data.attributes.artwork.url,
|
||||||
|
copyright=song_data.relationships.albums.data[0].attributes.copyright,
|
||||||
|
record_company=song_data.relationships.albums.data[0].attributes.recordLabel,
|
||||||
|
upc=song_data.relationships.albums.data[0].attributes.upc,
|
||||||
|
isrc=song_data.attributes.isrc
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_lyrics(self, lyrics: str):
|
||||||
|
self.lyrics = lyrics
|
||||||
|
|
||||||
|
async def get_cover(self, cover_format: str):
|
||||||
|
self.cover = await get_cover(self.cover_url, cover_format)
|
||||||
5
src/models/__init__.py
Normal file
5
src/models/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from src.models.album_meta import AlbumMeta
|
||||||
|
from src.models.playlist_meta import PlaylistMeta
|
||||||
|
from src.models.tracks_meta import TracksMeta
|
||||||
|
from src.models.song_data import SongData
|
||||||
|
from src.models.song_lyrics import SongLyrics
|
||||||
160
src/models/album_meta.py
Normal file
160
src/models/album_meta.py
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class Artwork(BaseModel):
|
||||||
|
width: int
|
||||||
|
url: str
|
||||||
|
height: int
|
||||||
|
textColor3: str
|
||||||
|
textColor2: str
|
||||||
|
textColor4: str
|
||||||
|
textColor1: str
|
||||||
|
bgColor: str
|
||||||
|
hasP3: bool
|
||||||
|
|
||||||
|
|
||||||
|
class PlayParams(BaseModel):
|
||||||
|
id: str
|
||||||
|
kind: str
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes(BaseModel):
|
||||||
|
copyright: str
|
||||||
|
genreNames: List[str]
|
||||||
|
releaseDate: str
|
||||||
|
upc: str
|
||||||
|
isMasteredForItunes: bool
|
||||||
|
artwork: Artwork
|
||||||
|
url: str
|
||||||
|
playParams: PlayParams
|
||||||
|
recordLabel: str
|
||||||
|
isCompilation: bool
|
||||||
|
trackCount: int
|
||||||
|
isPrerelease: bool
|
||||||
|
audioTraits: List[str]
|
||||||
|
isSingle: bool
|
||||||
|
name: str
|
||||||
|
artistName: str
|
||||||
|
isComplete: bool
|
||||||
|
|
||||||
|
|
||||||
|
class Artwork1(BaseModel):
|
||||||
|
width: int
|
||||||
|
url: str
|
||||||
|
height: int
|
||||||
|
textColor3: str
|
||||||
|
textColor2: str
|
||||||
|
textColor4: str
|
||||||
|
textColor1: str
|
||||||
|
bgColor: str
|
||||||
|
hasP3: bool
|
||||||
|
|
||||||
|
|
||||||
|
class PlayParams1(BaseModel):
|
||||||
|
id: str
|
||||||
|
kind: str
|
||||||
|
|
||||||
|
|
||||||
|
class Preview(BaseModel):
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes1(BaseModel):
|
||||||
|
hasTimeSyncedLyrics: bool
|
||||||
|
albumName: str
|
||||||
|
genreNames: List[str]
|
||||||
|
trackNumber: int
|
||||||
|
durationInMillis: int
|
||||||
|
releaseDate: str
|
||||||
|
isVocalAttenuationAllowed: bool
|
||||||
|
isMasteredForItunes: bool
|
||||||
|
isrc: str
|
||||||
|
artwork: Artwork1
|
||||||
|
composerName: str
|
||||||
|
audioLocale: str
|
||||||
|
playParams: PlayParams1
|
||||||
|
url: str
|
||||||
|
discNumber: int
|
||||||
|
hasCredits: bool
|
||||||
|
isAppleDigitalMaster: bool
|
||||||
|
hasLyrics: bool
|
||||||
|
audioTraits: List[str]
|
||||||
|
name: str
|
||||||
|
previews: List[Preview]
|
||||||
|
artistName: str
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes2(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class Datum2(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
attributes: Attributes2
|
||||||
|
|
||||||
|
|
||||||
|
class Artists(BaseModel):
|
||||||
|
href: str
|
||||||
|
data: List[Datum2]
|
||||||
|
|
||||||
|
|
||||||
|
class Relationships1(BaseModel):
|
||||||
|
artists: Artists
|
||||||
|
|
||||||
|
|
||||||
|
class Datum1(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
attributes: Attributes1
|
||||||
|
relationships: Relationships1
|
||||||
|
|
||||||
|
|
||||||
|
class Tracks(BaseModel):
|
||||||
|
href: str
|
||||||
|
data: List[Datum1]
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes3(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class Datum3(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
attributes: Attributes3
|
||||||
|
|
||||||
|
|
||||||
|
class Artists1(BaseModel):
|
||||||
|
href: str
|
||||||
|
data: List[Datum3]
|
||||||
|
|
||||||
|
|
||||||
|
class RecordLabels(BaseModel):
|
||||||
|
href: str
|
||||||
|
data: List
|
||||||
|
|
||||||
|
|
||||||
|
class Relationships(BaseModel):
|
||||||
|
tracks: Tracks
|
||||||
|
artists: Artists1
|
||||||
|
record_labels: RecordLabels = Field(..., alias='record-labels')
|
||||||
|
|
||||||
|
|
||||||
|
class Datum(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
attributes: Attributes
|
||||||
|
relationships: Relationships
|
||||||
|
|
||||||
|
|
||||||
|
class AlbumMeta(BaseModel):
|
||||||
|
data: List[Datum]
|
||||||
147
src/models/playlist_meta.py
Normal file
147
src/models/playlist_meta.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Description(BaseModel):
|
||||||
|
standard: str
|
||||||
|
short: str
|
||||||
|
|
||||||
|
|
||||||
|
class Artwork(BaseModel):
|
||||||
|
width: int
|
||||||
|
url: str
|
||||||
|
height: int
|
||||||
|
textColor3: str
|
||||||
|
textColor2: str
|
||||||
|
textColor4: str
|
||||||
|
textColor1: str
|
||||||
|
bgColor: str
|
||||||
|
hasP3: bool
|
||||||
|
|
||||||
|
|
||||||
|
class PlayParams(BaseModel):
|
||||||
|
id: str
|
||||||
|
kind: str
|
||||||
|
versionHash: str
|
||||||
|
|
||||||
|
|
||||||
|
class EditorialNotes(BaseModel):
|
||||||
|
name: str
|
||||||
|
standard: str
|
||||||
|
short: str
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes(BaseModel):
|
||||||
|
lastModifiedDate: str
|
||||||
|
supportsSing: bool
|
||||||
|
description: Description
|
||||||
|
artwork: Artwork
|
||||||
|
playParams: PlayParams
|
||||||
|
url: str
|
||||||
|
hasCollaboration: bool
|
||||||
|
curatorName: str
|
||||||
|
audioTraits: List
|
||||||
|
name: str
|
||||||
|
isChart: bool
|
||||||
|
playlistType: str
|
||||||
|
editorialNotes: EditorialNotes
|
||||||
|
artistName: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Artwork1(BaseModel):
|
||||||
|
width: int
|
||||||
|
url: str
|
||||||
|
height: int
|
||||||
|
textColor3: str
|
||||||
|
textColor2: str
|
||||||
|
textColor4: str
|
||||||
|
textColor1: str
|
||||||
|
bgColor: str
|
||||||
|
hasP3: bool
|
||||||
|
|
||||||
|
|
||||||
|
class PlayParams1(BaseModel):
|
||||||
|
id: str
|
||||||
|
kind: str
|
||||||
|
|
||||||
|
|
||||||
|
class Preview(BaseModel):
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes1(BaseModel):
|
||||||
|
albumName: str
|
||||||
|
hasTimeSyncedLyrics: bool
|
||||||
|
genreNames: List[str]
|
||||||
|
trackNumber: int
|
||||||
|
releaseDate: str
|
||||||
|
durationInMillis: int
|
||||||
|
isVocalAttenuationAllowed: bool
|
||||||
|
isMasteredForItunes: bool
|
||||||
|
isrc: str
|
||||||
|
artwork: Artwork1
|
||||||
|
composerName: str
|
||||||
|
audioLocale: str
|
||||||
|
url: str
|
||||||
|
playParams: PlayParams1
|
||||||
|
discNumber: int
|
||||||
|
hasCredits: bool
|
||||||
|
hasLyrics: bool
|
||||||
|
isAppleDigitalMaster: bool
|
||||||
|
audioTraits: List[str]
|
||||||
|
name: str
|
||||||
|
previews: List[Preview]
|
||||||
|
artistName: str
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes2(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class Datum2(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
attributes: Attributes2
|
||||||
|
|
||||||
|
|
||||||
|
class Artists(BaseModel):
|
||||||
|
href: str
|
||||||
|
data: List[Datum2]
|
||||||
|
|
||||||
|
|
||||||
|
class Relationships1(BaseModel):
|
||||||
|
artists: Artists
|
||||||
|
|
||||||
|
|
||||||
|
class Datum1(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
attributes: Attributes1
|
||||||
|
relationships: Relationships1
|
||||||
|
|
||||||
|
|
||||||
|
class Tracks(BaseModel):
|
||||||
|
href: str
|
||||||
|
next: Optional[str] = None
|
||||||
|
data: List[Datum1]
|
||||||
|
|
||||||
|
|
||||||
|
class Relationships(BaseModel):
|
||||||
|
tracks: Tracks
|
||||||
|
|
||||||
|
|
||||||
|
class Datum(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
attributes: Attributes
|
||||||
|
relationships: Relationships
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistMeta(BaseModel):
|
||||||
|
data: List[Datum]
|
||||||
137
src/models/song_data.py
Normal file
137
src/models/song_data.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Artwork(BaseModel):
|
||||||
|
width: int
|
||||||
|
url: str
|
||||||
|
height: int
|
||||||
|
textColor3: str
|
||||||
|
textColor2: str
|
||||||
|
textColor4: str
|
||||||
|
textColor1: str
|
||||||
|
bgColor: str
|
||||||
|
hasP3: bool
|
||||||
|
|
||||||
|
|
||||||
|
class PlayParams(BaseModel):
|
||||||
|
id: str
|
||||||
|
kind: str
|
||||||
|
|
||||||
|
|
||||||
|
class Preview(BaseModel):
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
class ExtendedAssetUrls(BaseModel):
|
||||||
|
plus: str
|
||||||
|
lightweight: str
|
||||||
|
superLightweight: str
|
||||||
|
lightweightPlus: str
|
||||||
|
enhancedHls: str
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes(BaseModel):
|
||||||
|
hasTimeSyncedLyrics: bool
|
||||||
|
albumName: str
|
||||||
|
genreNames: List[str]
|
||||||
|
trackNumber: int
|
||||||
|
durationInMillis: int
|
||||||
|
releaseDate: str
|
||||||
|
isVocalAttenuationAllowed: bool
|
||||||
|
isMasteredForItunes: bool
|
||||||
|
isrc: str
|
||||||
|
artwork: Artwork
|
||||||
|
composerName: str
|
||||||
|
audioLocale: str
|
||||||
|
url: str
|
||||||
|
playParams: PlayParams
|
||||||
|
discNumber: int
|
||||||
|
hasCredits: bool
|
||||||
|
isAppleDigitalMaster: bool
|
||||||
|
hasLyrics: bool
|
||||||
|
audioTraits: List[str]
|
||||||
|
name: str
|
||||||
|
previews: List[Preview]
|
||||||
|
artistName: str
|
||||||
|
extendedAssetUrls: ExtendedAssetUrls
|
||||||
|
|
||||||
|
|
||||||
|
class Artwork1(BaseModel):
|
||||||
|
width: int
|
||||||
|
url: str
|
||||||
|
height: int
|
||||||
|
textColor3: str
|
||||||
|
textColor2: str
|
||||||
|
textColor4: str
|
||||||
|
textColor1: str
|
||||||
|
bgColor: str
|
||||||
|
hasP3: bool
|
||||||
|
|
||||||
|
|
||||||
|
class PlayParams1(BaseModel):
|
||||||
|
id: str
|
||||||
|
kind: str
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes1(BaseModel):
|
||||||
|
copyright: str
|
||||||
|
genreNames: List[str]
|
||||||
|
releaseDate: str
|
||||||
|
isMasteredForItunes: bool
|
||||||
|
upc: str
|
||||||
|
artwork: Artwork1
|
||||||
|
url: str
|
||||||
|
playParams: PlayParams1
|
||||||
|
recordLabel: str
|
||||||
|
isCompilation: bool
|
||||||
|
trackCount: int
|
||||||
|
isPrerelease: bool
|
||||||
|
audioTraits: List[str]
|
||||||
|
isSingle: bool
|
||||||
|
name: str
|
||||||
|
artistName: str
|
||||||
|
isComplete: bool
|
||||||
|
|
||||||
|
|
||||||
|
class Datum1(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
attributes: Attributes1
|
||||||
|
|
||||||
|
|
||||||
|
class Albums(BaseModel):
|
||||||
|
href: str
|
||||||
|
data: List[Datum1]
|
||||||
|
|
||||||
|
|
||||||
|
class Datum2(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
|
||||||
|
|
||||||
|
class Artists(BaseModel):
|
||||||
|
href: str
|
||||||
|
data: List[Datum2]
|
||||||
|
|
||||||
|
|
||||||
|
class Relationships(BaseModel):
|
||||||
|
albums: Albums
|
||||||
|
artists: Artists
|
||||||
|
|
||||||
|
|
||||||
|
class Datum(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
attributes: Attributes
|
||||||
|
relationships: Relationships
|
||||||
|
|
||||||
|
|
||||||
|
class SongData(BaseModel):
|
||||||
|
data: List[Datum]
|
||||||
25
src/models/song_lyrics.py
Normal file
25
src/models/song_lyrics.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class PlayParams(BaseModel):
|
||||||
|
id: str
|
||||||
|
kind: str
|
||||||
|
catalogId: str
|
||||||
|
displayType: int
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes(BaseModel):
|
||||||
|
ttml: str
|
||||||
|
playParams: PlayParams
|
||||||
|
|
||||||
|
|
||||||
|
class Datum(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
attributes: Attributes
|
||||||
|
|
||||||
|
|
||||||
|
class SongLyrics(BaseModel):
|
||||||
|
data: List[Datum]
|
||||||
63
src/models/tracks_meta.py
Normal file
63
src/models/tracks_meta.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Artwork(BaseModel):
|
||||||
|
width: int
|
||||||
|
url: str
|
||||||
|
height: int
|
||||||
|
textColor3: str
|
||||||
|
textColor2: str
|
||||||
|
textColor4: str
|
||||||
|
textColor1: str
|
||||||
|
bgColor: str
|
||||||
|
hasP3: bool
|
||||||
|
|
||||||
|
|
||||||
|
class PlayParams(BaseModel):
|
||||||
|
id: str
|
||||||
|
kind: str
|
||||||
|
|
||||||
|
|
||||||
|
class Preview(BaseModel):
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
class Attributes(BaseModel):
|
||||||
|
hasTimeSyncedLyrics: bool
|
||||||
|
albumName: str
|
||||||
|
genreNames: List[str]
|
||||||
|
trackNumber: int
|
||||||
|
releaseDate: str
|
||||||
|
durationInMillis: int
|
||||||
|
isVocalAttenuationAllowed: bool
|
||||||
|
isMasteredForItunes: bool
|
||||||
|
isrc: str
|
||||||
|
artwork: Artwork
|
||||||
|
composerName: Optional[str] = None
|
||||||
|
audioLocale: str
|
||||||
|
url: str
|
||||||
|
playParams: PlayParams
|
||||||
|
discNumber: int
|
||||||
|
hasCredits: bool
|
||||||
|
isAppleDigitalMaster: bool
|
||||||
|
hasLyrics: bool
|
||||||
|
audioTraits: List[str]
|
||||||
|
name: str
|
||||||
|
previews: List[Preview]
|
||||||
|
artistName: str
|
||||||
|
|
||||||
|
|
||||||
|
class Datum(BaseModel):
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
href: str
|
||||||
|
attributes: Attributes
|
||||||
|
|
||||||
|
|
||||||
|
class TracksMeta(BaseModel):
|
||||||
|
next: Optional[str] = None
|
||||||
|
data: List[Datum]
|
||||||
165
src/mp4.py
Normal file
165
src/mp4.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import subprocess
|
||||||
|
import uuid
|
||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
import m3u8
|
||||||
|
import regex
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from src.metadata import SongMetadata
|
||||||
|
from src.types import *
|
||||||
|
from src.utils import find_best_codec
|
||||||
|
|
||||||
|
|
||||||
|
async def extract_media(m3u8_url: str, codec: str) -> Tuple[str, list[str], str]:
|
||||||
|
parsed_m3u8 = m3u8.load(m3u8_url)
|
||||||
|
specifyPlaylist = find_best_codec(parsed_m3u8, codec)
|
||||||
|
selected_codec = specifyPlaylist.media[0].group_id
|
||||||
|
if not specifyPlaylist:
|
||||||
|
raise
|
||||||
|
stream = m3u8.load(specifyPlaylist.absolute_uri)
|
||||||
|
skds = [key.uri for key in stream.keys if regex.match('(skd?://[^"]*)', key.uri)]
|
||||||
|
keys = [prefetchKey]
|
||||||
|
key_suffix = CodecKeySuffix.KeySuffixDefault
|
||||||
|
match codec:
|
||||||
|
case Codec.ALAC:
|
||||||
|
key_suffix = CodecKeySuffix.KeySuffixAlac
|
||||||
|
case Codec.EC3:
|
||||||
|
key_suffix = CodecKeySuffix.KeySuffixAtmos
|
||||||
|
case Codec.AAC:
|
||||||
|
key_suffix = CodecKeySuffix.KeySuffixAAC
|
||||||
|
case Codec.AAC_BINAURAL:
|
||||||
|
key_suffix = CodecKeySuffix.KeySuffixAACBinaural
|
||||||
|
case Codec.AAC_DOWNMIX:
|
||||||
|
key_suffix = CodecKeySuffix.KeySuffixAACDownmix
|
||||||
|
for key in skds:
|
||||||
|
if key.endswith(key_suffix) or key.endswith(CodecKeySuffix.KeySuffixDefault):
|
||||||
|
keys.append(key)
|
||||||
|
return stream.segment_map[0].absolute_uri, keys, selected_codec
|
||||||
|
|
||||||
|
|
||||||
|
def extract_song(raw_song: bytes, codec: str) -> SongInfo:
|
||||||
|
tmp_dir = TemporaryDirectory()
|
||||||
|
mp4_name = uuid.uuid4().hex
|
||||||
|
raw_mp4 = Path(tmp_dir.name) / Path(f"{mp4_name}.mp4")
|
||||||
|
with open(raw_mp4.absolute(), "wb") as f:
|
||||||
|
f.write(raw_song)
|
||||||
|
nhml_name = (Path(tmp_dir.name) / Path(mp4_name).with_suffix('.nhml')).absolute()
|
||||||
|
media_name = (Path(tmp_dir.name) / Path(mp4_name).with_suffix('.media')).absolute()
|
||||||
|
subprocess.run(f"gpac -i {raw_mp4.absolute()} nhmlw:pckp=true -o {nhml_name}",
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
xml_name = (Path(tmp_dir.name) / Path(mp4_name).with_suffix('.xml')).absolute()
|
||||||
|
subprocess.run(f"mp4box -diso {raw_mp4.absolute()} -out {xml_name}",
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
decoder_params = None
|
||||||
|
|
||||||
|
with open(xml_name, "r") as f:
|
||||||
|
info_xml = BeautifulSoup(f.read(), "xml")
|
||||||
|
with open(nhml_name, "r") as f:
|
||||||
|
raw_nhml = f.read()
|
||||||
|
nhml = BeautifulSoup(raw_nhml, "xml")
|
||||||
|
with open(media_name, "rb") as f:
|
||||||
|
media = BytesIO(f.read())
|
||||||
|
|
||||||
|
if codec == Codec.ALAC:
|
||||||
|
alac_atom_name = (Path(tmp_dir.name) / Path(mp4_name).with_suffix('.atom')).absolute()
|
||||||
|
subprocess.run(f"mp4extract moov/trak/mdia/minf/stbl/stsd/enca[0]/alac {raw_mp4.absolute()} {alac_atom_name}",
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
with open(alac_atom_name, "rb") as f:
|
||||||
|
decoder_params = f.read()
|
||||||
|
|
||||||
|
samples = []
|
||||||
|
moofs = info_xml.find_all("MovieFragmentBox")
|
||||||
|
nhnt_sample_number = 0
|
||||||
|
nhnt_samples = {}
|
||||||
|
for sample in nhml.find_all("NHNTSample"):
|
||||||
|
nhnt_samples.update({int(sample.get("number")): sample})
|
||||||
|
for i, moof in enumerate(moofs):
|
||||||
|
tfhd = moof.TrackFragmentBox.TrackFragmentHeaderBox
|
||||||
|
index = 0 if not tfhd.get("SampleDescriptionIndex") else int(tfhd.get("SampleDescriptionIndex")) - 1
|
||||||
|
truns = moof.TrackFragmentBox.find_all("TrackRunBox")
|
||||||
|
for trun in truns:
|
||||||
|
for sample_number in range(int(trun.get("SampleCount"))):
|
||||||
|
nhnt_sample_number += 1
|
||||||
|
nhnt_sample = nhnt_samples[nhnt_sample_number]
|
||||||
|
sample_data = media.read(int(nhnt_sample.get("dataLength")))
|
||||||
|
duration = int(nhnt_sample.get("duration"))
|
||||||
|
samples.append(SampleInfo(descIndex=index, data=sample_data, duration=int(duration)))
|
||||||
|
tmp_dir.cleanup()
|
||||||
|
return SongInfo(codec=codec, raw=raw_song, samples=samples, nhml=raw_nhml, decoderParams=decoder_params)
|
||||||
|
|
||||||
|
|
||||||
|
def encapsulate(song_info: SongInfo, decrypted_media: bytes, atmos_convent: bool) -> bytes:
|
||||||
|
tmp_dir = TemporaryDirectory()
|
||||||
|
name = uuid.uuid4().hex
|
||||||
|
media = Path(tmp_dir.name) / Path(name).with_suffix(".media")
|
||||||
|
with open(media.absolute(), "wb") as f:
|
||||||
|
f.write(decrypted_media)
|
||||||
|
if song_info.codec == Codec.EC3 and not atmos_convent:
|
||||||
|
song_name = Path(tmp_dir.name) / Path(name).with_suffix(".ec3")
|
||||||
|
else:
|
||||||
|
song_name = Path(tmp_dir.name) / Path(name).with_suffix(".m4a")
|
||||||
|
match song_info.codec:
|
||||||
|
case Codec.ALAC:
|
||||||
|
nhml_name = Path(tmp_dir.name) / Path(f"{name}.nhml")
|
||||||
|
with open(nhml_name.absolute(), "w", encoding="utf-8") as f:
|
||||||
|
nhml_xml = BeautifulSoup(song_info.nhml, features="xml")
|
||||||
|
nhml_xml.NHNTStream["baseMediaFile"] = media.name
|
||||||
|
f.write(str(nhml_xml))
|
||||||
|
subprocess.run(f"gpac -i {nhml_name.absolute()} nhmlr -o {song_name.absolute()}",
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
alac_params_atom_name = Path(tmp_dir.name) / Path(f"{name}.atom")
|
||||||
|
with open(alac_params_atom_name.absolute(), "wb") as f:
|
||||||
|
f.write(song_info.decoderParams)
|
||||||
|
final_m4a_name = Path(tmp_dir.name) / Path(f"{name}_final.m4a")
|
||||||
|
subprocess.run(
|
||||||
|
f"mp4edit --insert moov/trak/mdia/minf/stbl/stsd/alac:{alac_params_atom_name.absolute()} {song_name.absolute()} {final_m4a_name.absolute()}",
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
song_name = final_m4a_name
|
||||||
|
case Codec.EC3:
|
||||||
|
if not atmos_convent:
|
||||||
|
with open(song_name.absolute(), "wb") as f:
|
||||||
|
f.write(decrypted_media)
|
||||||
|
subprocess.run(f"gpac -i {media.absolute()} -o {song_name.absolute()}",
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
case Codec.AAC_BINAURAL | Codec.AAC_DOWNMIX | Codec.AAC:
|
||||||
|
nhml_name = Path(tmp_dir.name) / Path(f"{name}.nhml")
|
||||||
|
with open(nhml_name.absolute(), "w", encoding="utf-8") as f:
|
||||||
|
nhml_xml = BeautifulSoup(song_info.nhml, features="xml")
|
||||||
|
nhml_xml.NHNTStream["baseMediaFile"] = media.name
|
||||||
|
del nhml_xml.NHNTStream["streamType"]
|
||||||
|
del nhml_xml.NHNTStream["objectTypeIndication"]
|
||||||
|
del nhml_xml.NHNTStream["specificInfoFile"]
|
||||||
|
nhml_xml.NHNTStream["mediaType"] = "soun"
|
||||||
|
nhml_xml.NHNTStream["mediaSubType"] = "mp4a"
|
||||||
|
f.write(str(nhml_xml))
|
||||||
|
subprocess.run(f"gpac -i {nhml_name.absolute()} nhmlr -o {song_name.absolute()}",
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
with open(song_name.absolute(), "rb") as f:
|
||||||
|
final_song = f.read()
|
||||||
|
tmp_dir.cleanup()
|
||||||
|
return final_song
|
||||||
|
|
||||||
|
|
||||||
|
def write_metadata(song: bytes, metadata: SongMetadata, embed_metadata: list[str], cover_format: str) -> bytes:
|
||||||
|
tmp_dir = TemporaryDirectory()
|
||||||
|
name = uuid.uuid4().hex
|
||||||
|
song_name = Path(tmp_dir.name) / Path(f"{name}.m4a")
|
||||||
|
with open(song_name.absolute(), "wb") as f:
|
||||||
|
f.write(song)
|
||||||
|
absolute_cover_path = ""
|
||||||
|
if "cover" in embed_metadata:
|
||||||
|
cover_path = Path(tmp_dir.name) / Path(f"cover.{cover_format}")
|
||||||
|
absolute_cover_path = cover_path.absolute()
|
||||||
|
with open(cover_path.absolute(), "wb") as f:
|
||||||
|
f.write(metadata.cover)
|
||||||
|
subprocess.run(["mp4box", "-time", "0", "-mtime", "0", "-keep-utc", "-name", f"1={metadata.title}", "-itags",
|
||||||
|
":".join(["tool=\"\"", f"cover={absolute_cover_path}", metadata.to_itags_params(embed_metadata, cover_format)]),
|
||||||
|
song_name.absolute()], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
with open(song_name.absolute(), "rb") as f:
|
||||||
|
embed_song = f.read()
|
||||||
|
tmp_dir.cleanup()
|
||||||
|
return embed_song
|
||||||
61
src/rip.py
Normal file
61
src/rip.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from src.api import get_info_from_adam, get_song_lyrics, get_meta, download_song
|
||||||
|
from src.config import Config, Device
|
||||||
|
from src.decrypt import decrypt
|
||||||
|
from src.metadata import SongMetadata
|
||||||
|
from src.mp4 import extract_media, extract_song, encapsulate, write_metadata
|
||||||
|
from src.save import save
|
||||||
|
from src.types import GlobalAuthParams, Codec
|
||||||
|
from src.url import Song, Album, URLType
|
||||||
|
from src.utils import check_song_exists
|
||||||
|
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
|
async def rip_song(song: Song, auth_params: GlobalAuthParams, codec: str, config: Config, device: Device,
|
||||||
|
force_save: bool = False):
|
||||||
|
logger.debug(f"Task of song id {song.id} was created")
|
||||||
|
token = auth_params.anonymousAccessToken
|
||||||
|
song_data = await get_info_from_adam(song.id, token, song.storefront)
|
||||||
|
song_metadata = SongMetadata.parse_from_song_data(song_data)
|
||||||
|
logger.info(f"Ripping song: {song_metadata.artist} - {song_metadata.title}")
|
||||||
|
if not force_save and check_song_exists(song_metadata, config.download, codec):
|
||||||
|
logger.info(f"Song: {song_metadata.artist} - {song_metadata.title} already exists")
|
||||||
|
return
|
||||||
|
await song_metadata.get_cover(config.download.coverFormat)
|
||||||
|
if song_data.attributes.hasTimeSyncedLyrics:
|
||||||
|
lyrics = await get_song_lyrics(song.id, song.storefront, auth_params.accountAccessToken,
|
||||||
|
auth_params.dsid, auth_params.accountToken)
|
||||||
|
song_metadata.lyrics = lyrics
|
||||||
|
song_uri, keys, selected_codec = await extract_media(song_data.attributes.extendedAssetUrls.enhancedHls, codec)
|
||||||
|
logger.info(f"Selected codec: {selected_codec} for song: {song_metadata.artist} - {song_metadata.title}")
|
||||||
|
logger.info(f"Downloading song: {song_metadata.artist} - {song_metadata.title}")
|
||||||
|
raw_song = await download_song(song_uri)
|
||||||
|
song_info = extract_song(raw_song, codec)
|
||||||
|
decrypted_song = await decrypt(song_info, keys, song_data, device)
|
||||||
|
song = encapsulate(song_info, decrypted_song, config.download.atmosConventToM4a)
|
||||||
|
if codec != Codec.EC3 or (codec == Codec.EC3 and config.download.atmosConventToM4a):
|
||||||
|
song = write_metadata(song, song_metadata, config.metadata.embedMetadata, config.download.coverFormat)
|
||||||
|
save(song, codec, song_metadata, config.download)
|
||||||
|
logger.info(f"Song {song_metadata.artist} - {song_metadata.title} saved!")
|
||||||
|
|
||||||
|
|
||||||
|
async def rip_album(album: Album, auth_params: GlobalAuthParams, codec: str, config: Config, device: Device,
|
||||||
|
force_save: bool = False):
|
||||||
|
album_info = await get_meta(album.id, auth_params.anonymousAccessToken, album.storefront)
|
||||||
|
logger.info(f"Ripping Album: {album_info.data[0].attributes.artistName} - {album_info.data[0].attributes.name}")
|
||||||
|
async with asyncio.TaskGroup() as tg:
|
||||||
|
for track in album_info.data[0].relationships.tracks.data:
|
||||||
|
song = Song(id=track.id, storefront=album.storefront, url="", type=URLType.Song)
|
||||||
|
tg.create_task(rip_song(song, auth_params, codec, config, device, force_save))
|
||||||
|
logger.info(f"Album: {album_info.data[0].attributes.artistName} - {album_info.data[0].attributes.name} finished ripping")
|
||||||
|
|
||||||
|
|
||||||
|
async def rip_playlist():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def rip_artist():
|
||||||
|
pass
|
||||||
29
src/save.py
Normal file
29
src/save.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from src.config import Download
|
||||||
|
from src.metadata import SongMetadata
|
||||||
|
from src.types import Codec
|
||||||
|
from src.utils import ttml_convent_to_lrc, get_valid_filename
|
||||||
|
|
||||||
|
|
||||||
|
def save(song: bytes, codec: str, metadata: SongMetadata, config: Download):
|
||||||
|
song_name = get_valid_filename(config.songNameFormat.format(**metadata.model_dump()))
|
||||||
|
dir_path = Path(config.dirPathFormat.format(**metadata.model_dump()))
|
||||||
|
if not dir_path.exists() or not dir_path.is_dir():
|
||||||
|
os.makedirs(dir_path.absolute())
|
||||||
|
if codec == Codec.EC3 and not config.atmosConventToM4a:
|
||||||
|
song_path = dir_path / Path(song_name).with_suffix(".ec3")
|
||||||
|
else:
|
||||||
|
song_path = dir_path / Path(song_name).with_suffix(".m4a")
|
||||||
|
with open(song_path.absolute(), "wb") as f:
|
||||||
|
f.write(song)
|
||||||
|
if config.saveCover:
|
||||||
|
cover_path = dir_path / Path(f"cover.{config.coverFormat}")
|
||||||
|
with open(cover_path.absolute(), "wb") as f:
|
||||||
|
f.write(metadata.cover)
|
||||||
|
if config.saveLyrics and metadata.lyrics:
|
||||||
|
lrc_path = dir_path / Path(song_name).with_suffix(".lrc")
|
||||||
|
with open(lrc_path.absolute(), "w", encoding="utf-8") as f:
|
||||||
|
f.write(ttml_convent_to_lrc(metadata.lyrics))
|
||||||
|
return song_path.absolute()
|
||||||
68
src/types.py
Normal file
68
src/types.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
defaultId = "0"
|
||||||
|
prefetchKey = "skd://itunes.apple.com/P000000000/s1/e1"
|
||||||
|
|
||||||
|
|
||||||
|
class SampleInfo(BaseModel):
|
||||||
|
data: bytes
|
||||||
|
duration: int
|
||||||
|
descIndex: int
|
||||||
|
|
||||||
|
|
||||||
|
class SongInfo(BaseModel):
|
||||||
|
codec: str
|
||||||
|
raw: bytes
|
||||||
|
samples: list[SampleInfo]
|
||||||
|
nhml: str
|
||||||
|
decoderParams: Optional[bytes] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Codec:
|
||||||
|
ALAC = "alac"
|
||||||
|
EC3 = "ec3"
|
||||||
|
AAC_BINAURAL = "aac-binaural"
|
||||||
|
AAC_DOWNMIX = "aac-downmix"
|
||||||
|
AAC = "aac"
|
||||||
|
|
||||||
|
|
||||||
|
class CodecKeySuffix:
|
||||||
|
KeySuffixAtmos = "c24"
|
||||||
|
KeySuffixAlac = "c23"
|
||||||
|
KeySuffixAAC = "c22"
|
||||||
|
KeySuffixAACDownmix = "c24"
|
||||||
|
KeySuffixAACBinaural = "c24"
|
||||||
|
KeySuffixDefault = "c6"
|
||||||
|
|
||||||
|
|
||||||
|
class CodecRegex:
|
||||||
|
RegexCodecAtmos = "audio-atmos-\\d{4}$"
|
||||||
|
RegexCodecAlac = "audio-alac-stereo-\\d{5}-\\d{2}$"
|
||||||
|
RegexCodecBinaural = "audio-stereo-\\d{3}-binaural$"
|
||||||
|
RegexCodecDownmix = "audio-stereo-\\d{3}-downmix$"
|
||||||
|
RegexCodecAAC = "audio-stereo-\\d{3}$"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_pattern_by_codec(cls, codec: str):
|
||||||
|
codec_pattern_mapping = {Codec.ALAC: cls.RegexCodecAlac, Codec.EC3: cls.RegexCodecAtmos,
|
||||||
|
Codec.AAC_DOWNMIX: cls.RegexCodecDownmix, Codec.AAC_BINAURAL: cls.RegexCodecBinaural,
|
||||||
|
Codec.AAC: cls.RegexCodecAAC}
|
||||||
|
return codec_pattern_mapping.get(codec)
|
||||||
|
|
||||||
|
|
||||||
|
class AuthParams(BaseModel):
|
||||||
|
dsid: str
|
||||||
|
accountToken: str
|
||||||
|
accountAccessToken: str
|
||||||
|
storefront: str
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalAuthParams(AuthParams):
|
||||||
|
anonymousAccessToken: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_auth_params_and_token(cls, auth_params: AuthParams, token: str):
|
||||||
|
return cls(dsid=auth_params.dsid, accountToken=auth_params.accountToken, anonymousAccessToken=token,
|
||||||
|
accountAccessToken=auth_params.accountAccessToken, storefront=auth_params.storefront)
|
||||||
62
src/url.py
Normal file
62
src/url.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class URLType:
|
||||||
|
Song = "song"
|
||||||
|
Album = "album"
|
||||||
|
Playlist = "playlist"
|
||||||
|
Artist = "artist"
|
||||||
|
|
||||||
|
|
||||||
|
class AppleMusicURL(BaseModel):
|
||||||
|
url: str
|
||||||
|
storefront: str
|
||||||
|
type: str
|
||||||
|
id: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_url(cls, url: str):
|
||||||
|
parsed_url = urlparse(url)
|
||||||
|
paths = parsed_url.path.split("/")
|
||||||
|
storefront = paths[1]
|
||||||
|
url_type = paths[2]
|
||||||
|
match url_type:
|
||||||
|
case URLType.Song:
|
||||||
|
url_id = paths[4]
|
||||||
|
return Song(url=url, storefront=storefront, id=url_id, type=URLType.Song)
|
||||||
|
case URLType.Album:
|
||||||
|
if not parsed_url.query:
|
||||||
|
url_id = paths[4]
|
||||||
|
return Album(url=url, storefront=storefront, id=url_id, type=URLType.Album)
|
||||||
|
else:
|
||||||
|
url_query = parse_qs(parsed_url.query)
|
||||||
|
if url_query.get("i"):
|
||||||
|
url_id = url_query.get("i")[0]
|
||||||
|
return Song(url=url, storefront=storefront, id=url_id, type=URLType.Song)
|
||||||
|
else:
|
||||||
|
url_id = paths[4]
|
||||||
|
return Album(url=url, storefront=storefront, id=url_id, type=URLType.Album)
|
||||||
|
case URLType.Artist:
|
||||||
|
url_id = paths[4]
|
||||||
|
return Artist(url=url, storefront=storefront, id=url_id, type=URLType.Artist)
|
||||||
|
case URLType.Playlist:
|
||||||
|
url_id = paths[4]
|
||||||
|
return Playlist(url=url, storefront=storefront, id=url_id, type=URLType.Playlist)
|
||||||
|
|
||||||
|
|
||||||
|
class Song(AppleMusicURL):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Album(AppleMusicURL):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Playlist(AppleMusicURL):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Artist(AppleMusicURL):
|
||||||
|
...
|
||||||
117
src/utils.py
Normal file
117
src/utils.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
from itertools import islice
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import m3u8
|
||||||
|
import regex
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from src.config import Download
|
||||||
|
from src.exceptions import NotTimeSyncedLyricsException
|
||||||
|
|
||||||
|
from src.types import *
|
||||||
|
|
||||||
|
|
||||||
|
def check_url(url):
|
||||||
|
pattern = regex.compile(
|
||||||
|
r'^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/album|\/album\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)')
|
||||||
|
result = regex.findall(pattern, url)
|
||||||
|
return result[0][0], result[0][1]
|
||||||
|
|
||||||
|
|
||||||
|
def check_playlist_url(url):
|
||||||
|
pattern = regex.compile(
|
||||||
|
r'^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/playlist|\/playlist\/.+))\/(?:id)?(pl\.[\w-]+)(?:$|\?)')
|
||||||
|
result = regex.findall(pattern, url)
|
||||||
|
return result[0][0], result[0][1]
|
||||||
|
|
||||||
|
|
||||||
|
def byte_length(i):
|
||||||
|
return (i.bit_length() + 7) // 8
|
||||||
|
|
||||||
|
|
||||||
|
def find_best_codec(parsed_m3u8: m3u8.M3U8, codec: str) -> Optional[m3u8.Playlist]:
|
||||||
|
available_medias = [playlist for playlist in parsed_m3u8.playlists
|
||||||
|
if regex.match(CodecRegex.get_pattern_by_codec(codec), playlist.stream_info.audio)]
|
||||||
|
if not available_medias:
|
||||||
|
return None
|
||||||
|
available_medias.sort(key=lambda x: x.stream_info.average_bandwidth, reverse=True)
|
||||||
|
return available_medias[0]
|
||||||
|
|
||||||
|
|
||||||
|
def chunk(it, size):
|
||||||
|
it = iter(it)
|
||||||
|
return iter(lambda: tuple(islice(it, size)), ())
|
||||||
|
|
||||||
|
|
||||||
|
def timeit(func):
|
||||||
|
async def process(func, *args, **params):
|
||||||
|
if asyncio.iscoroutinefunction(func):
|
||||||
|
print('this function is a coroutine: {}'.format(func.__name__))
|
||||||
|
return await func(*args, **params)
|
||||||
|
else:
|
||||||
|
print('this is not a coroutine')
|
||||||
|
return func(*args, **params)
|
||||||
|
|
||||||
|
async def helper(*args, **params):
|
||||||
|
print('{}.time'.format(func.__name__))
|
||||||
|
start = time.time()
|
||||||
|
result = await process(func, *args, **params)
|
||||||
|
|
||||||
|
# Test normal function route...
|
||||||
|
# result = await process(lambda *a, **p: print(*a, **p), *args, **params)
|
||||||
|
|
||||||
|
print('>>>', time.time() - start)
|
||||||
|
return result
|
||||||
|
|
||||||
|
return helper
|
||||||
|
|
||||||
|
|
||||||
|
def get_digit_from_string(text: str) -> int:
|
||||||
|
return int(''.join(filter(str.isdigit, text)))
|
||||||
|
|
||||||
|
|
||||||
|
def ttml_convent_to_lrc(ttml: str) -> str:
|
||||||
|
b = BeautifulSoup(ttml, features="xml")
|
||||||
|
lrc_lines = []
|
||||||
|
for item in b.tt.body.children:
|
||||||
|
for lyric in item.children:
|
||||||
|
h, m, s, ms = 0, 0, 0, 0
|
||||||
|
lyric_time: str = lyric.get("begin")
|
||||||
|
if not lyric_time:
|
||||||
|
raise NotTimeSyncedLyricsException
|
||||||
|
match lyric_time.count(":"):
|
||||||
|
case 0:
|
||||||
|
split_time = lyric_time.split(".")
|
||||||
|
s, ms = get_digit_from_string(split_time[0]), get_digit_from_string(split_time[1])
|
||||||
|
case 1:
|
||||||
|
split_time = lyric_time.split(":")
|
||||||
|
s_ms = split_time[-1]
|
||||||
|
del split_time[-1]
|
||||||
|
split_time.extend(s_ms.split("."))
|
||||||
|
m, s, ms = (get_digit_from_string(split_time[0]), get_digit_from_string(split_time[1]),
|
||||||
|
get_digit_from_string(split_time[2]))
|
||||||
|
case 2:
|
||||||
|
split_time = lyric_time.split(":")
|
||||||
|
s_ms = split_time[-1]
|
||||||
|
del split_time[-1]
|
||||||
|
split_time.extend(s_ms.split("."))
|
||||||
|
h, m, s, ms = (get_digit_from_string(split_time[0]), get_digit_from_string(split_time[1]),
|
||||||
|
get_digit_from_string(split_time[2]), get_digit_from_string(split_time[3]))
|
||||||
|
lrc_lines.append(
|
||||||
|
f"[{str(m + h * 60).rjust(2, '0')}:{str(s).rjust(2, '0')}.{str(int(ms / 10)).rjust(2, '0')}]{lyric.text}")
|
||||||
|
return "\n".join(lrc_lines)
|
||||||
|
|
||||||
|
|
||||||
|
def check_song_exists(metadata, config: Download, codec: str):
|
||||||
|
song_name = get_valid_filename(config.songNameFormat.format(**metadata.model_dump()))
|
||||||
|
dir_path = Path(config.dirPathFormat.format(**metadata.model_dump()))
|
||||||
|
if not config.atmosConventToM4a and codec == Codec.EC3:
|
||||||
|
return (Path(dir_path) / Path(song_name).with_suffix(".ec3")).exists()
|
||||||
|
else:
|
||||||
|
return (Path(dir_path) / Path(song_name).with_suffix(".m4a")).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def get_valid_filename(filename: str):
|
||||||
|
return "".join(i for i in filename if i not in "\/:*?<>|")
|
||||||
Reference in New Issue
Block a user