commit
6d36521005
@ -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/ |
||||||
|
|
@ -0,0 +1,8 @@ |
|||||||
|
# 默认忽略的文件 |
||||||
|
/shelf/ |
||||||
|
/workspace.xml |
||||||
|
# 基于编辑器的 HTTP 客户端请求 |
||||||
|
/httpRequests/ |
||||||
|
# Datasource local storage ignored files |
||||||
|
/dataSources/ |
||||||
|
/dataSources.local.xml |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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/>. |
@ -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); |
@ -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 |
||||||
|
} |
||||||
|
] |
@ -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"] |
@ -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() |
@ -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" |
@ -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,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 |
@ -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 |
@ -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") |
@ -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) |
@ -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 |
@ -0,0 +1,18 @@ |
|||||||
|
class FridaNotExistException(Exception): |
||||||
|
... |
||||||
|
|
||||||
|
|
||||||
|
class ADBConnectException(Exception): |
||||||
|
... |
||||||
|
|
||||||
|
|
||||||
|
class FailedGetAuthParamException(Exception): |
||||||
|
... |
||||||
|
|
||||||
|
|
||||||
|
class DecryptException(Exception): |
||||||
|
... |
||||||
|
|
||||||
|
|
||||||
|
class NotTimeSyncedLyricsException(Exception): |
||||||
|
... |
@ -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) |
@ -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 |
@ -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] |
@ -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] |
@ -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] |
@ -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] |
@ -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] |
@ -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 |
@ -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 |
@ -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() |
@ -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) |
@ -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): |
||||||
|
... |
@ -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 "\/:*?<>|") |
Loading…
Reference in new issue