├── lyric_chinese.png ├── lyric_english.png ├── .flake8 ├── .travis.yml ├── pyproject.toml ├── touchbar_lyric ├── service │ ├── qq.py │ ├── __init__.py │ ├── netease.py │ └── misc.py ├── __main__.py ├── __init__.py └── utility │ └── __init__.py ├── .gitignore ├── lyric.json ├── README.md └── poetry.lock /lyric_chinese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenghaoMou/touchbar-lyric/HEAD/lyric_chinese.png -------------------------------------------------------------------------------- /lyric_english.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenghaoMou/touchbar-lyric/HEAD/lyric_english.png -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | max-complexity = 18 4 | select = B,C,E,F,W,T4,B9 5 | ignore = E203, E266, E501, W503, F403, F401 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: osx 2 | language: shell 3 | addons: 4 | homebrew: 5 | packages: python3 6 | before_install: 7 | - pip3 install virtualenv 8 | - virtualenv -p python3 ~/venv 9 | - source ~/venv/bin/activate 10 | - pip3 install poetry 11 | 12 | install: poetry install 13 | script: python -m touchbar_lyric 14 | notifications: 15 | email: 16 | on_failure: never 17 | 18 | before_deploy: 19 | - pip install --upgrade pip 20 | - pip install poetry 21 | - poetry config http-basic.pypi $PYPI_USER $PYPI_PASS 22 | - poetry build 23 | 24 | deploy: 25 | provider: script 26 | script: poetry publish 27 | skip_cleanup: true 28 | on: 29 | tags: true 30 | branch: 31 | - "master" 32 | - "/v?(\\d+\\.)?(\\d+\\.)?(\\*|\\d+)$/" 33 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "touchbar-lyric" 3 | version = "0.8.2" 4 | description = "Display lyrics on your touchbar with BTT" 5 | authors = ["Chenghao Mou "] 6 | license = "MIT" 7 | readme = "README.md" 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.7" 11 | applescript = "^2021.2.9" 12 | qqmusic-api = "^0.1" 13 | typer = "^0.3.2" 14 | loguru = "^0.5.3" 15 | strsimpy = "^0.2.0" 16 | coverage = "^5.5" 17 | regex = "^2021.3.17" 18 | hanziconv = "^0.3.2" 19 | diskcache = "^5.2.1" 20 | pyaes = "^1.6.1" 21 | beautifulsoup4 = "^4.11.1" 22 | 23 | [tool.poetry.dev-dependencies] 24 | pre-commit = "^2.19.0" 25 | 26 | [build-system] 27 | requires = ["poetry-core>=1.0.0"] 28 | build-backend = "poetry.core.masonry.api" 29 | 30 | [tool.ruff] 31 | max-line-length = 120 32 | -------------------------------------------------------------------------------- /touchbar_lyric/service/qq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2020-08-16 09:10:24 4 | # @Author : Chenghao Mou (mouchenghao@gmail.com) 5 | import json 6 | from asyncio.log import logger 7 | from typing import List 8 | 9 | from QQMusicAPI import QQMusic 10 | 11 | from touchbar_lyric import Song 12 | 13 | 14 | def qq_music_search(title: str, artists: str) -> List[Song]: 15 | """ 16 | Search from QQ Music with artists and title. 17 | Parameters 18 | ---------- 19 | title : str 20 | Name of the song 21 | artists : str 22 | Names of the artists 23 | Returns 24 | ------- 25 | List[Song] 26 | List of songs 27 | Examples 28 | -------- 29 | >>> songs = qq_music_search("海阔天空", "Beyond") 30 | >>> len(songs) > 0 31 | True 32 | >>> any(s.anchor(10) is not None for s in songs) 33 | True 34 | """ 35 | try: 36 | response = QQMusic.search(title) 37 | except json.decoder.JSONDecodeError as e: 38 | logger.error(e) 39 | return [] 40 | songs = [] 41 | top: int = 3 42 | for song in response.data: 43 | lyric = song.lyric 44 | lyric.extract() 45 | if lyric.lyric or lyric.trans: 46 | content = lyric.lyric or lyric.trans 47 | songs.append( 48 | Song( 49 | title=song.name, 50 | artists=",".join([x.name for x in song.singer]), 51 | target_title=title, 52 | target_artists=artists, 53 | lyric=content, 54 | ) 55 | ) 56 | top -= 1 57 | if top == 0: 58 | break 59 | return songs 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .vscode 6 | .mypy_cache 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | 107 | .codacy-coverage 108 | cobertura.xml 109 | .isort.cfg 110 | .pre-commit-config.yaml 111 | *jar 112 | .DS_Store 113 | -------------------------------------------------------------------------------- /lyric.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "BTTWidgetName" : "Lyric", 4 | "BTTTriggerType" : 642, 5 | "BTTTriggerTypeDescription" : "Shell Script \/ Task Widget", 6 | "BTTTriggerClass" : "BTTTriggerTypeTouchBar", 7 | "BTTPredefinedActionType" : -1, 8 | "BTTPredefinedActionName" : "No Action", 9 | "BTTShellScriptWidgetGestureConfig" : "\/bin\/sh:::-c:::-:::", 10 | "BTTEnabled2" : 1, 11 | "BTTRepeatDelay" : 0, 12 | "BTTUUID" : "93C8B565-73C7-49BE-960A-4FCF512CE881", 13 | "BTTNotesInsteadOfDescription" : 0, 14 | "BTTEnabled" : 1, 15 | "BTTModifierMode" : 0, 16 | "BTTOrder" : 0, 17 | "BTTDisplayOrder" : 99, 18 | "BTTMergeIntoTouchBarGroups" : 0, 19 | "BTTTriggerConfig" : { 20 | "BTTTouchBarFreeSpaceAfterButton" : 0, 21 | "BTTTouchBarOnlyShowIcon" : false, 22 | "BTTTouchBarButtonColor" : "0.000000, 0.000000, 0.000000, 255.000000", 23 | "BTTTouchBarApplyCornerRadiusTo" : 0, 24 | "BTTTouchBarItemPlacement" : 0, 25 | "BTTTouchBarAppleScriptStringRunOnInit" : true, 26 | "BTTTouchBarAlwaysShowButton" : false, 27 | "BTTTouchBarFontColorAlternate" : "0.000000, 0.000000, 0.000000, 255.000000", 28 | "BTTScriptType" : 0, 29 | "BTTTouchBarButtonWidth" : 550, 30 | "BTTTouchBarScriptUpdateInterval" : 1, 31 | "BTTTouchBarItemIconHeight" : 22, 32 | "BTTTouchBarButtonUseFixedWidth" : 0, 33 | "BTTTouchBarButtonTextAlignment" : 1, 34 | "BTTTouchBarAlternateBackgroundColor" : "0.000000, 0.000000, 0.000000, 255.000000", 35 | "BTTTBWidgetWidth" : 400, 36 | "BTTTouchBarItemIconWidth" : 22, 37 | "BTTTouchBarShellScriptString" : "$PYTHONPATH -m touchbar_lyric --app Spotify", 38 | "BTTTouchBarIconTextOffset" : 5, 39 | "BTTTouchBarButtonFontSize" : 12, 40 | "BTTTouchBarFontColor" : "156.000006, 226.000002, 90.000002, 255.000000", 41 | "BTTTouchBarButtonName" : "Lyric", 42 | "BTTTouchBarButtonCornerRadius" : 6, 43 | "BTTTouchBarItemPadding" : 20 44 | } 45 | } 46 | ] -------------------------------------------------------------------------------- /touchbar_lyric/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2021-03-14 15:29:12 4 | # @Author : Chenghao Mou (chenghao@gmail.com) 5 | import hashlib 6 | import logging.config 7 | import os 8 | import time 9 | from pathlib import Path 10 | 11 | import typer 12 | from hanziconv import HanziConv 13 | from loguru import logger 14 | 15 | from touchbar_lyric.service import universal_search, CACHE 16 | from touchbar_lyric.utility import get_info 17 | 18 | logging.config.dictConfig( 19 | { 20 | "version": 1, 21 | "disable_existing_loggers": True, 22 | } 23 | ) 24 | 25 | 26 | def run( 27 | app: str = typer.Option(default="Spotify", help="Application to track"), 28 | debug: bool = typer.Option( 29 | default=False, is_flag=True, help="To show debug messages or not" 30 | ), 31 | traditional: bool = typer.Option( 32 | default=False, 33 | is_flag=True, 34 | help="Translate lyrics into Traditional Chinese if possible", 35 | ), 36 | ): # pragma: no cover 37 | {True: logger.enable, False: logger.disable}[debug]("touchbar_lyric") 38 | 39 | if not debug: 40 | logger.disable("touchbar_lyric") 41 | logger.disable("__main__") 42 | start_time = time.time() 43 | media_info = get_info(app) 44 | if media_info is None: 45 | return 46 | 47 | hash = hashlib.md5(f"{media_info.name}{media_info.artists}".encode()).hexdigest() 48 | if os.path.exists(Path(CACHE) / f"{hash}.lock"): 49 | return [] 50 | # Create a manual lock to prevent multiple searches 51 | open(Path(CACHE) / f"{hash}.lock", "w").close() 52 | songs = universal_search(media_info.name, media_info.artists) 53 | os.remove(Path(CACHE) / f"{hash}.lock") 54 | 55 | logger.debug("|".join(song.title for song in songs)) 56 | logger.debug("|".join(song.artists for song in songs)) 57 | for song in songs: 58 | if song.anchor(media_info.position): 59 | line: str = song.anchor(media_info.position + int(time.time() - start_time)) 60 | if traditional: 61 | line = HanziConv.toTraditional(line) 62 | print(line) 63 | break 64 | 65 | 66 | if __name__ == "__main__": # pragma: no cover 67 | typer.run(run) 68 | -------------------------------------------------------------------------------- /touchbar_lyric/service/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2021-03-14 15:54:40 4 | # @Author : Chenghao Mou (mouchenghao@gmail.com) 5 | import logging.config 6 | import os 7 | from concurrent.futures import ThreadPoolExecutor 8 | from typing import List, Tuple 9 | 10 | from diskcache import FanoutCache 11 | from hanziconv import HanziConv 12 | from strsimpy.normalized_levenshtein import NormalizedLevenshtein 13 | 14 | from touchbar_lyric import Song 15 | from touchbar_lyric.service.misc import (lyricsify_music_search, 16 | megalobiz_music_search, 17 | rclyricsband_music_search, 18 | rentanadviser_music_search) 19 | from touchbar_lyric.service.netease import netease_music_search 20 | from touchbar_lyric.service.qq import qq_music_search 21 | 22 | logging.config.dictConfig( 23 | { 24 | "version": 1, 25 | "disable_existing_loggers": True, 26 | } 27 | ) 28 | 29 | 30 | CACHE = os.path.join(os.path.expanduser("~"), ".cache") 31 | 32 | if not os.path.exists(CACHE): 33 | os.mkdir(CACHE) 34 | 35 | cache = FanoutCache(CACHE, timeout=2) 36 | 37 | 38 | @cache.memoize(typed=True, expire=None, tag="lyric") 39 | def universal_search(title: str, artists: str) -> List[Song]: # pragma: no cover 40 | songs: List[Tuple[int, Song]] = [] 41 | title = HanziConv.toSimplified(title) 42 | 43 | # Multithreaded search 44 | with ThreadPoolExecutor() as executor: 45 | results = list(executor.map( 46 | lambda f: f(title, artists), 47 | [ 48 | netease_music_search, 49 | qq_music_search, 50 | rentanadviser_music_search, 51 | megalobiz_music_search, 52 | lyricsify_music_search, 53 | rclyricsband_music_search, 54 | ], 55 | )) 56 | 57 | for result in results: 58 | songs.extend((-i, s) for i, s in enumerate(result)) 59 | 60 | return [s[1] for s in sorted( 61 | songs, 62 | key=lambda s: ( 63 | NormalizedLevenshtein().similarity(s[1].title, s[1].target_title) 64 | + NormalizedLevenshtein().similarity(s[1].artists, s[1].target_artists), 65 | s[0], 66 | ), 67 | reverse=True, 68 | )] 69 | -------------------------------------------------------------------------------- /touchbar_lyric/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2021-03-14 14:37:35 4 | # @Author : Chenghao Mou (mouchenghao@gmail.com) 5 | 6 | import html 7 | from dataclasses import dataclass 8 | from typing import List, Optional, Tuple 9 | 10 | import regex as re 11 | 12 | from touchbar_lyric.utility import search_intervals 13 | 14 | 15 | @dataclass 16 | class Song: 17 | 18 | title: str 19 | artists: str 20 | target_title: str 21 | target_artists: str 22 | lyric: str 23 | lines: Optional[List[Tuple[float, str]]] = None 24 | intervals: Optional[List[float]] = None 25 | 26 | def __post_init__(self): 27 | 28 | lyric = self.lyric 29 | lyric = html.unescape(lyric) 30 | 31 | self.lines = [] 32 | self.intervals = [] 33 | for line in lyric.split("\n"): 34 | info, *words = line.rsplit("]", 1) 35 | timestamp = re.search(r"\[([0-9]+):([0-9]+)\.([0-9]+)\]", info + "]") 36 | if not timestamp: 37 | continue 38 | minute, second, subsecond = ( 39 | timestamp.group(1), 40 | timestamp.group(2), 41 | timestamp.group(3), 42 | ) 43 | curr_stamp = int(minute) * 60 + int(second) + \ 44 | int(subsecond) / (10**len(subsecond)) 45 | self.lines.append((curr_stamp, "".join(words))) 46 | self.intervals.append(curr_stamp) 47 | 48 | def anchor(self, timestamp: float) -> Optional[str]: 49 | """Find current timestamp for this song. 50 | 51 | Parameters 52 | ---------- 53 | timestamp : float 54 | Current timestamp 55 | 56 | Returns 57 | ------- 58 | Optional[str] 59 | A line or None 60 | 61 | Examples 62 | -------- 63 | >>> song = Song("Hello", "Adele", "Hello", "Adele", "[01:12.34]Hello") 64 | >>> song.anchor(60) 65 | 'Hello' 66 | >>> song.anchor(120) 67 | 'Hello' 68 | >>> song = Song("Hello", "Adele", "Hello", "Adele", "") 69 | >>> song.anchor(10) is None 70 | True 71 | """ 72 | if not self.intervals or not self.lines: 73 | return None 74 | 75 | idx = search_intervals(self.intervals, timestamp) 76 | if idx != -1: 77 | return self.lines[idx][-1] 78 | 79 | return None 80 | -------------------------------------------------------------------------------- /touchbar_lyric/utility/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2021-03-01 19:51:54 4 | # @Author : Chenghao Mou (mouchenghao@gmail.com) 5 | 6 | import bisect 7 | from collections import namedtuple 8 | from typing import List, Optional 9 | 10 | import applescript 11 | from loguru import logger 12 | 13 | MediaInformation = namedtuple( 14 | "MediaInformation", ["name", "artists", "position", "state", "durantion"] 15 | ) 16 | 17 | 18 | def get_info(app: str) -> Optional[MediaInformation]: 19 | """Get media information with apple script. 20 | 21 | Parameters 22 | ---------- 23 | app : str 24 | The name of the application 25 | 26 | Returns 27 | ------- 28 | Optional[MediaInformation] 29 | MediaInformation object 30 | 31 | Examples 32 | -------- 33 | >>> ans = get_info("Spotify") 34 | >>> assert ans is None or isinstance(ans, MediaInformation) 35 | """ 36 | 37 | script: str = f""" 38 | on run 39 | if application "{app}" is running then 40 | tell application "{app}" 41 | set currentInfo to {{name of current track, "|||", artist of current track, "|||", player position, "|||", player state, "|||", duration of current track}} 42 | end tell 43 | else 44 | set currentInfo to "Empty" 45 | end if 46 | return currentInfo 47 | end run 48 | """ 49 | 50 | r = applescript.run(script) 51 | 52 | logger.debug(r.out) 53 | 54 | ans: Optional[MediaInformation] = None 55 | if r.code == 0 and r.out != "Empty": 56 | segments = r.out.split(", |||, ") 57 | segments = [s.strip(', ') for s in segments] 58 | if len(segments) != 5: 59 | return ans 60 | ans = MediaInformation( 61 | segments[0], 62 | segments[1], 63 | float(segments[2]), 64 | {"playing": 2, "paused": 1, "stopped": 0}.get(segments[3], 0), 65 | float(segments[4]) // 1000 66 | if "." not in segments[4] 67 | else float(segments[4]), 68 | ) 69 | 70 | logger.debug(ans) 71 | 72 | return ans 73 | 74 | 75 | def search_intervals( 76 | intervals: List[float], position: float 77 | ) -> int: # pragma: no cover 78 | """Search a timestamp in a list of intervals. 79 | 80 | Parameters 81 | ---------- 82 | intervals : List[float] 83 | List of timestamps 84 | position : float 85 | Current timestamp 86 | 87 | Returns 88 | ------- 89 | int 90 | Index of the interval 91 | 92 | Examples 93 | -------- 94 | >>> search_intervals([12, 15], 13) 95 | 0 96 | >>> search_intervals([12, 15], 7) 97 | 0 98 | >>> search_intervals([12, 15], 16) 99 | 1 100 | """ 101 | idx = max(0, bisect.bisect_left(intervals, position) - 1) 102 | 103 | if len(intervals) > idx >= 0 and ( 104 | idx == 0 105 | or idx == len(intervals) - 1 106 | or (intervals[idx] <= position <= intervals[idx + 1]) 107 | ): 108 | return idx 109 | 110 | return -1 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Synced Lyric on TouchBar

2 | 3 | :warning: I no longer have a macbook with TouchBar, so I won't be able to update this project as often. 4 | 5 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/77de523131f9441997db18c608b3c54e)](https://app.codacy.com/manual/mouchenghao/touchbar-lyric?utm_source=github.com&utm_medium=referral&utm_content=ChenghaoMou/touchbar-lyric&utm_campaign=Badge_Grade_Dashboard) [![Build Status](https://travis-ci.com/ChenghaoMou/touchbar-lyric.svg?branch=master)](https://travis-ci.com/ChenghaoMou/touchbar-lyric) [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/aadeca6117a14aa6b655e21d5bbc09ea)](https://www.codacy.com/manual/mouchenghao/touchbar-lyric?utm_source=github.com&utm_medium=referral&utm_content=ChenghaoMou/touchbar-lyric&utm_campaign=Badge_Coverage) [![PyPI version](https://badge.fury.io/py/touchbar-lyric.svg)](https://badge.fury.io/py/touchbar-lyric) 6 | 7 | Show synced lyric in the touch-bar with BetterTouchTool and NetEase/QQ Music APIs. Based on the idea of [Kashi](https://community.folivora.ai/t/kashi-show-current-song-lyrics-on-touch-bar-spotify-itunes-youtube/6301). 8 | 9 | ![Preview](./lyric_chinese.png) 10 | ![Preview](./lyric_english.png) 11 | 12 | ## Features 13 | 14 | - **Synced lyrics** from QQ Music and NetEase Music APIs; 15 | - Support **Spotify** (Recommended) & **Music** (Only songs in your playlists); 16 | - Support for **English/Spanish/Chinese(Simplified/Traditional)/Japanese** and more; 17 | 18 | ## Instruction 19 | 20 | **If you are not familiar with command line, python ecosystem or having problems understanding this tutorial, find a friend to help you. Issues/DMs are not actively monitored for this project.** 21 | 22 | ### 1. Installation 23 | ```shell 24 | pip3 install touchbar_lyric --upgrade 25 | ``` 26 | 27 | ### 2. Configuration in BetterTouchTool 28 | 29 | Same as Kashi: 30 | 31 | 1. Copy&paste the content in `lyric.json` in _Meun Bar > Touch Bar_; 32 | 2. Change the python path `$PYTHONPATH` to your own python path in the script area; 33 | 34 | ```shell 35 | $PYTHONPATH -m touchbar_lyric --app Music 36 | ``` 37 | 38 | or use Spotify(default) 39 | 40 | ```shell 41 | $PYTHONPATH -m touchbar_lyric --app Spotify 42 | ``` 43 | 44 | Show Traditional Chinese lyrics 45 | 46 | ```shell 47 | $PYTHONPATH -m touchbar_lyric --app Spotify --traditional 48 | ``` 49 | 50 | **Be careful with typing double hyphens in BTT. It automatically change it to an em slash. Use copy & paste instead!** 51 | 52 | ## Acknowledgement 53 | 54 | 1. Inspired by [Kashi](https://community.folivora.ai/t/kashi-show-current-song-lyrics-on-touch-bar-spotify-itunes-youtube/6301) by [Jim Ho](https://github.com/jimu-gh). 55 | 2. Supported by wonderful projects like [qq-music-api](https://github.com/Rain120/qq-music-api) by [Rain120](https://github.com/Rain120) and [spotifylyrics](https://github.com/SimonIT/spotifylyrics) by [SimonIT](https://github.com/SimonIT). 56 | 57 | ## Disclaimer 58 | 59 | This project is not affiliated with Apple, Spotify, QQ Music, NetEase Music, BetterTouchTool or any other third party. This project is not intended to violate any terms of service of the aforementioned parties. This project is for educational purposes only. 60 | -------------------------------------------------------------------------------- /touchbar_lyric/service/netease.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2020-03-10 10:54:37 4 | # @Author : Chenghao Mou (mouchenghao@gmail.com) 5 | 6 | import base64 7 | import binascii 8 | import json 9 | import os 10 | from typing import Any, Dict, List 11 | 12 | import pyaes 13 | import requests 14 | from loguru import logger 15 | 16 | from touchbar_lyric import Song 17 | 18 | 19 | class NeteaseRequest: # pragma: no cover 20 | """A request wrapper for Netease music.""" 21 | 22 | session = requests.Session() 23 | session.headers.update( 24 | { 25 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 26 | "Accept-Charset": "UTF-8,*;q=0.5", 27 | "Accept-Encoding": "gzip,deflate,sdch", 28 | "Accept-Language": "en-US,en;q=0.8", 29 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101 Firefox/60.0", 30 | "referer": "http://music.163.com/", 31 | } 32 | ) 33 | 34 | @classmethod 35 | def encode_netease_data(cls, data) -> str: # pragma: no cover 36 | data = json.dumps(data) 37 | key = binascii.unhexlify("7246674226682325323F5E6544673A51") 38 | encryptor = pyaes.Encrypter(pyaes.AESModeOfOperationECB(key)) 39 | pad = 16 - len(data) % 16 40 | fix = chr(pad) * pad 41 | byte_data = (data + fix).encode("utf-8") 42 | ciphertext = encryptor.feed(byte_data) 43 | ciphertext += encryptor.feed() 44 | return binascii.hexlify(ciphertext).upper().decode() 45 | 46 | @classmethod 47 | def encrypted_request(cls, data) -> dict: # pragma: no cover 48 | MODULUS = ( 49 | "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7" 50 | "b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280" 51 | "104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932" 52 | "575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b" 53 | "3ece0462db0a22b8e7" 54 | ) 55 | PUBKEY = "010001" 56 | NONCE = b"0CoJUm6Qyw8W8jud" 57 | data = json.dumps(data).encode("utf-8") 58 | secret = cls.create_key(16) 59 | params = cls.aes(cls.aes(data, NONCE), secret) 60 | encseckey = cls.rsa(secret, PUBKEY, MODULUS) 61 | return {"params": params, "encSecKey": encseckey} 62 | 63 | @classmethod 64 | def aes(cls, text, key): # pragma: no cover 65 | pad = 16 - len(text) % 16 66 | text = text + bytearray([pad] * pad) 67 | encryptor = pyaes.Encrypter( 68 | pyaes.AESModeOfOperationCBC(key, iv=b"0102030405060708")) 69 | ciphertext = encryptor.feed(text) 70 | ciphertext += encryptor.feed() 71 | return base64.b64encode(ciphertext) 72 | 73 | @classmethod 74 | def rsa(cls, text, pubkey, modulus): # pragma: no cover 75 | text = text[::-1] 76 | rs = pow(int(binascii.hexlify(text), 16), int(pubkey, 16), int(modulus, 16)) 77 | return format(rs, "x").zfill(256) 78 | 79 | @classmethod 80 | def create_key(cls, size): # pragma: no cover 81 | return binascii.hexlify(os.urandom(size))[:16] 82 | 83 | @classmethod 84 | def request( 85 | cls, url: str, data: Dict[str, Any], method: str = "POST" 86 | ) -> Dict[str, Any]: 87 | 88 | results = {} 89 | status = requests.codes.ok 90 | text = "" 91 | 92 | try: 93 | if method == "GET": 94 | resp = cls.session.get(url, params=data, timeout=20) 95 | else: 96 | resp = cls.session.post(url, data=data, timeout=20) 97 | results = resp.json() 98 | text = resp.text 99 | status = resp.status_code 100 | except Exception as e: 101 | results = {} 102 | logger.debug(e) 103 | 104 | if status != requests.codes.ok or not text: 105 | results = {} 106 | 107 | return results 108 | 109 | 110 | def netease_music_get_lyric(idx) -> str: 111 | data = NeteaseRequest.encrypted_request( 112 | {"csrf_token": "", "id": idx, "lv": -1, "tv": -1} 113 | ) 114 | return ( 115 | NeteaseRequest.request( 116 | url="https://music.163.com/weapi/song/lyric", method="POST", data=data 117 | ) 118 | .get("lrc", {}) 119 | .get("lyric", "") 120 | ) 121 | 122 | 123 | def netease_music_search(title: str, artists: str) -> List[Song]: 124 | """ 125 | Search from Netease Music with artists and title. 126 | Parameters 127 | ---------- 128 | title : str 129 | Name of the song 130 | artists : str 131 | Names of the artists 132 | Returns 133 | ------- 134 | List[Song] 135 | List of songs 136 | Examples 137 | -------- 138 | >>> songs = netease_music_search("海阔天空", "Beyond") 139 | >>> len(songs) > 0 140 | True 141 | >>> any(s.anchor(10) is not None for s in songs) 142 | True 143 | """ 144 | 145 | eparams = { 146 | "method": "POST", 147 | "url": "http://music.163.com/api/cloudsearch/pc", 148 | "params": {"s": title, "type": 1, "offset": 0, "limit": 30}, 149 | } 150 | data = {"eparams": NeteaseRequest.encode_netease_data(eparams)} 151 | 152 | res_data = ( 153 | NeteaseRequest.request( 154 | "http://music.163.com/api/linux/forward", method="POST", data=data 155 | ) 156 | .get("result", {}) 157 | .get("songs", []) 158 | ) 159 | songs = [] 160 | for _, item in enumerate(res_data): 161 | if item.get("id", None) is not None: 162 | s = Song( 163 | title=item.get("name", ""), 164 | artists=",".join( 165 | [x["name"] for x in item.get("ar", []) if "name" in x] 166 | ), 167 | target_title=title, 168 | target_artists=artists, 169 | lyric=netease_music_get_lyric(idx=item["id"]), 170 | ) 171 | songs.append(s) 172 | return songs 173 | -------------------------------------------------------------------------------- /touchbar_lyric/service/misc.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from urllib import parse, request 3 | 4 | import regex 5 | import requests 6 | from bs4 import BeautifulSoup 7 | 8 | from touchbar_lyric import Song 9 | 10 | UA = "Mozilla/5.0 (Maemo; Linux armv7l; rv:10.0.1) Gecko/20100101 Firefox/10.0.1 Fennec/10.0.1" 11 | 12 | 13 | def name_comparison(title: str, artists: str, target_title: str, target_artists: str) -> bool: 14 | """ 15 | Compare the name of the song and the name of the artist. 16 | 17 | Parameters 18 | ---------- 19 | title : str 20 | Name of the song 21 | artists : str 22 | Names of the artists 23 | target_title : str 24 | Name of the song to compare 25 | target_artists : str 26 | Names of the artists to compare 27 | 28 | Returns 29 | ------- 30 | bool 31 | Whether the name of the song and the name of the artist are the same 32 | 33 | Examples 34 | -------- 35 | >>> name_comparison("海阔天空", "Beyond", "海阔天空", "Beyond") 36 | True 37 | >>> name_comparison("I`m not the only one", "Sam Smith", "I'm not the only one", "Sam Smith") 38 | True 39 | """ 40 | def preprocess(text): 41 | return "".join(regex.findall(r"\w+", text.lower())) 42 | 43 | return preprocess(target_title) in preprocess(title) and preprocess(target_artists) in preprocess(artists) 44 | 45 | 46 | def rentanadviser_music_search(title: str, artists: str) -> List[Song]: 47 | proxy = request.getproxies() 48 | search_url = f'''https://www.rentanadviser.com/en/subtitles/subtitles4songs.aspx?{ 49 | parse.urlencode({ 50 | "src": f"{artists} {title}" 51 | }) 52 | }''' 53 | search_results = requests.get(search_url, proxies=proxy) 54 | soup = BeautifulSoup(search_results.text, 'html.parser') 55 | container = soup.find(id="tablecontainer") 56 | if not container: 57 | return [] 58 | result_links = container.find_all("a") 59 | 60 | for result_link in result_links: 61 | if result_link["href"] != "subtitles4songs.aspx": 62 | info = result_link.get_text() 63 | if not name_comparison(info, info, title, artists): 64 | continue 65 | url = f'https://www.rentanadviser.com/en/subtitles/{result_link["href"]}&type=lrc' 66 | possible_text = requests.get(url, proxies=proxy) 67 | soup = BeautifulSoup(possible_text.text, 'html.parser') 68 | event_validation = soup.find(id="__EVENTVALIDATION") 69 | if not event_validation: 70 | continue 71 | event_validation = event_validation.get("value") 72 | 73 | view_state = soup.find(id="__VIEWSTATE") 74 | if not view_state: 75 | continue 76 | view_state = view_state.get("value") 77 | 78 | lrc = requests.post( 79 | possible_text.url, 80 | { 81 | "__EVENTTARGET": "ctl00$ContentPlaceHolder1$btnlyrics", 82 | "__EVENTVALIDATION": event_validation, 83 | "__VIEWSTATE": view_state 84 | }, 85 | headers={"User-Agent": UA, "referer": possible_text.url}, 86 | proxies=proxy, 87 | cookies=search_results.cookies 88 | ) 89 | return [Song( 90 | title=info, 91 | artists=info, 92 | target_title=title, 93 | target_artists=artists, 94 | lyric=lrc.text 95 | )] 96 | return [] 97 | 98 | 99 | def lyricsify_music_search(title: str, artists: str) -> List[Song]: 100 | proxy = request.getproxies() 101 | search_url = f'''https://www.lyricsify.com/search?{ 102 | parse.urlencode({ 103 | "q": f"{artists} {title}" 104 | }) 105 | }''' 106 | search_results = requests.get(search_url, proxies=proxy, headers={"User-Agent": UA}) 107 | soup = BeautifulSoup(search_results.text, 'html.parser') 108 | 109 | result_container = soup.find("div", class_="sub") 110 | if not result_container: 111 | return [] 112 | 113 | result_list = result_container.find_all("div", class_="li") 114 | if not result_list: 115 | return [] 116 | 117 | for result in result_list: 118 | result_link = result.find("a") 119 | name = result_link.get_text() 120 | if not name_comparison(name, name, title, artists): 121 | continue 122 | 123 | url = f"https://www.lyricsify.com{result_link['href']}?download" 124 | lyrics_page = requests.get(url, proxies=proxy, headers={"User-Agent": UA}) 125 | soup = BeautifulSoup(lyrics_page.text, 'html.parser') 126 | element = soup.find(id="iframe_download") 127 | if not element: 128 | continue 129 | download_link = element.get("src") 130 | if not download_link: 131 | continue 132 | lrc = requests.get(download_link, proxies=proxy, 133 | cookies=lyrics_page.cookies, headers={"User-Agent": UA}).text 134 | return [Song( 135 | title=name, 136 | artists=name, 137 | target_title=title, 138 | target_artists=artists, 139 | lyric=lrc 140 | )] 141 | return [] 142 | 143 | 144 | def rclyricsband_music_search(title: str, artists: str) -> List[Song]: 145 | proxy = request.getproxies() 146 | search_results = requests.get( 147 | "https://rclyricsband.com/", params={"s": "%s %s" % (artists, title)}, proxies=proxy) 148 | search_soup = BeautifulSoup(search_results.text, 'html.parser') 149 | main = search_soup.find(id="main") 150 | if not main: 151 | return [] 152 | articles = main.find_all("article") 153 | if not articles: 154 | return [] 155 | for result in articles: 156 | try: 157 | title_link = result.find(class_="elementor-post__title").find("a") 158 | except Exception: 159 | continue 160 | info = title_link.get_text() 161 | if not name_comparison(info, info, title, artists): 162 | continue 163 | song_page = requests.get(title_link["href"]) 164 | song_page_soup = BeautifulSoup(song_page.text, 'html.parser') 165 | lrc_download_button = song_page_soup.find( 166 | lambda tag: tag.name == "a" and "LRC Download" in tag.text) 167 | if not lrc_download_button: 168 | continue 169 | response = requests.get(lrc_download_button["href"], proxies=proxy) 170 | if not response.status_code == 200: 171 | continue 172 | return [Song( 173 | title=info, 174 | artists=info, 175 | target_title=title, 176 | target_artists=artists, 177 | lyric=response.text 178 | )] 179 | return [] 180 | 181 | 182 | def megalobiz_music_search(title: str, artists: str) -> List[Song]: 183 | proxy = request.getproxies() 184 | search_url = "https://www.megalobiz.com/search/all?%s" % parse.urlencode({ 185 | "qry": f"{artists} {title}", 186 | "display": "more" 187 | }) 188 | search_results = requests.get(search_url, proxies=proxy) 189 | soup = BeautifulSoup(search_results.text, 'html.parser') 190 | container = soup.find(id="list_entity_container") 191 | if not container: 192 | return [] 193 | result_links = container.find_all( 194 | "a", class_="entity_name") 195 | 196 | for result_link in result_links: 197 | info = result_link.get_text() 198 | if not name_comparison(info, info, title, artists): 199 | continue 200 | url = f"https://www.megalobiz.com{result_link['href']}" 201 | possible_text = requests.get(url, proxies=proxy) 202 | soup = BeautifulSoup(possible_text.text, 'html.parser') 203 | 204 | div = soup.find("div", class_="lyrics_details") 205 | if not div: 206 | continue 207 | try: 208 | lrc = div.span.get_text() 209 | except Exception: 210 | continue 211 | 212 | return [Song( 213 | title=info, 214 | artists=info, 215 | target_title=title, 216 | target_artists=artists, 217 | lyric=lrc 218 | )] 219 | return [] 220 | 221 | 222 | if __name__ == "__main__": 223 | print(rentanadviser_music_search("I'm Not The Only One", "Sam Smith")) 224 | print(lyricsify_music_search("I'm Not The Only One", "Sam Smith")) 225 | print(rclyricsband_music_search("I'm Not The Only One", "Sam Smith")) 226 | print(megalobiz_music_search("I'm Not The Only One", "Sam Smith")) 227 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "applescript" 5 | version = "2021.2.9" 6 | description = "run applescript" 7 | optional = false 8 | python-versions = "*" 9 | files = [ 10 | {file = "applescript-2021.2.9.tar.gz", hash = "sha256:e39542c0411d82c3c860af23f5a54c121279a409e5cf55acfa7f881ea9798854"}, 11 | ] 12 | 13 | [[package]] 14 | name = "beautifulsoup4" 15 | version = "4.12.3" 16 | description = "Screen-scraping library" 17 | optional = false 18 | python-versions = ">=3.6.0" 19 | files = [ 20 | {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, 21 | {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, 22 | ] 23 | 24 | [package.dependencies] 25 | soupsieve = ">1.2" 26 | 27 | [package.extras] 28 | cchardet = ["cchardet"] 29 | chardet = ["chardet"] 30 | charset-normalizer = ["charset-normalizer"] 31 | html5lib = ["html5lib"] 32 | lxml = ["lxml"] 33 | 34 | [[package]] 35 | name = "certifi" 36 | version = "2024.2.2" 37 | description = "Python package for providing Mozilla's CA Bundle." 38 | optional = false 39 | python-versions = ">=3.6" 40 | files = [ 41 | {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, 42 | {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, 43 | ] 44 | 45 | [[package]] 46 | name = "cfgv" 47 | version = "3.3.1" 48 | description = "Validate configuration and produce human readable error messages." 49 | optional = false 50 | python-versions = ">=3.6.1" 51 | files = [ 52 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 53 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 54 | ] 55 | 56 | [[package]] 57 | name = "charset-normalizer" 58 | version = "3.3.2" 59 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 60 | optional = false 61 | python-versions = ">=3.7.0" 62 | files = [ 63 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 64 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 65 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 66 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 67 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 68 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 69 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 70 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 71 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 72 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 73 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 74 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 75 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 76 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 77 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 78 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 79 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 80 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 81 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 82 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 83 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 84 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 85 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 86 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 87 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 88 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 89 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 90 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 91 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 92 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 93 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 94 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 95 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 96 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 97 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 98 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 99 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 100 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 101 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 102 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 103 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 104 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 105 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 106 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 107 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 108 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 109 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 110 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 111 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 112 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 113 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 114 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 115 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 116 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 117 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 118 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 119 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 120 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 121 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 122 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 123 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 124 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 125 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 126 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 127 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 128 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 129 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 130 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 131 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 132 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 133 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 134 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 135 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 136 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 137 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 138 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 139 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 140 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 141 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 142 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 143 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 144 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 145 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 146 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 147 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 148 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 149 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 150 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 151 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 152 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 153 | ] 154 | 155 | [[package]] 156 | name = "click" 157 | version = "7.1.2" 158 | description = "Composable command line interface toolkit" 159 | optional = false 160 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 161 | files = [ 162 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 163 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 164 | ] 165 | 166 | [[package]] 167 | name = "colorama" 168 | version = "0.4.6" 169 | description = "Cross-platform colored terminal text." 170 | optional = false 171 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 172 | files = [ 173 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 174 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 175 | ] 176 | 177 | [[package]] 178 | name = "coverage" 179 | version = "5.5" 180 | description = "Code coverage measurement for Python" 181 | optional = false 182 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 183 | files = [ 184 | {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, 185 | {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, 186 | {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, 187 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, 188 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, 189 | {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, 190 | {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, 191 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, 192 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, 193 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, 194 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, 195 | {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, 196 | {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, 197 | {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, 198 | {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, 199 | {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, 200 | {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, 201 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, 202 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, 203 | {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, 204 | {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, 205 | {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, 206 | {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, 207 | {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, 208 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, 209 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, 210 | {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, 211 | {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, 212 | {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, 213 | {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, 214 | {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, 215 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, 216 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, 217 | {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, 218 | {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, 219 | {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, 220 | {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, 221 | {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, 222 | {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, 223 | {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, 224 | {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, 225 | {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, 226 | {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, 227 | {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, 228 | {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, 229 | {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, 230 | {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, 231 | {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, 232 | {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, 233 | {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, 234 | {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, 235 | {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, 236 | ] 237 | 238 | [package.extras] 239 | toml = ["toml"] 240 | 241 | [[package]] 242 | name = "diskcache" 243 | version = "5.6.3" 244 | description = "Disk Cache -- Disk and file backed persistent cache." 245 | optional = false 246 | python-versions = ">=3" 247 | files = [ 248 | {file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"}, 249 | {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, 250 | ] 251 | 252 | [[package]] 253 | name = "distlib" 254 | version = "0.3.8" 255 | description = "Distribution utilities" 256 | optional = false 257 | python-versions = "*" 258 | files = [ 259 | {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, 260 | {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, 261 | ] 262 | 263 | [[package]] 264 | name = "filelock" 265 | version = "3.12.2" 266 | description = "A platform independent file lock." 267 | optional = false 268 | python-versions = ">=3.7" 269 | files = [ 270 | {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, 271 | {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, 272 | ] 273 | 274 | [package.extras] 275 | docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] 276 | testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] 277 | 278 | [[package]] 279 | name = "hanziconv" 280 | version = "0.3.2" 281 | description = "Hanzi Converter for Traditional and Simplified Chinese" 282 | optional = false 283 | python-versions = "*" 284 | files = [ 285 | {file = "hanziconv-0.3.2.tar.gz", hash = "sha256:208866da6ae305bca19eb98702b65c93bb3a803b496e4287ca740d68892fc4c4"}, 286 | ] 287 | 288 | [[package]] 289 | name = "identify" 290 | version = "2.5.24" 291 | description = "File identification library for Python" 292 | optional = false 293 | python-versions = ">=3.7" 294 | files = [ 295 | {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, 296 | {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, 297 | ] 298 | 299 | [package.extras] 300 | license = ["ukkonen"] 301 | 302 | [[package]] 303 | name = "idna" 304 | version = "3.7" 305 | description = "Internationalized Domain Names in Applications (IDNA)" 306 | optional = false 307 | python-versions = ">=3.5" 308 | files = [ 309 | {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, 310 | {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, 311 | ] 312 | 313 | [[package]] 314 | name = "importlib-metadata" 315 | version = "6.7.0" 316 | description = "Read metadata from Python packages" 317 | optional = false 318 | python-versions = ">=3.7" 319 | files = [ 320 | {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, 321 | {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, 322 | ] 323 | 324 | [package.dependencies] 325 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 326 | zipp = ">=0.5" 327 | 328 | [package.extras] 329 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 330 | perf = ["ipython"] 331 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] 332 | 333 | [[package]] 334 | name = "loguru" 335 | version = "0.5.3" 336 | description = "Python logging made (stupidly) simple" 337 | optional = false 338 | python-versions = ">=3.5" 339 | files = [ 340 | {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, 341 | {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, 342 | ] 343 | 344 | [package.dependencies] 345 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 346 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 347 | 348 | [package.extras] 349 | dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"] 350 | 351 | [[package]] 352 | name = "nodeenv" 353 | version = "1.8.0" 354 | description = "Node.js virtual environment builder" 355 | optional = false 356 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" 357 | files = [ 358 | {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, 359 | {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, 360 | ] 361 | 362 | [package.dependencies] 363 | setuptools = "*" 364 | 365 | [[package]] 366 | name = "platformdirs" 367 | version = "4.0.0" 368 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 369 | optional = false 370 | python-versions = ">=3.7" 371 | files = [ 372 | {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, 373 | {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, 374 | ] 375 | 376 | [package.dependencies] 377 | typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.8\""} 378 | 379 | [package.extras] 380 | docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] 381 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] 382 | 383 | [[package]] 384 | name = "pre-commit" 385 | version = "2.21.0" 386 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 387 | optional = false 388 | python-versions = ">=3.7" 389 | files = [ 390 | {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, 391 | {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, 392 | ] 393 | 394 | [package.dependencies] 395 | cfgv = ">=2.0.0" 396 | identify = ">=1.0.0" 397 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 398 | nodeenv = ">=0.11.1" 399 | pyyaml = ">=5.1" 400 | virtualenv = ">=20.10.0" 401 | 402 | [[package]] 403 | name = "pyaes" 404 | version = "1.6.1" 405 | description = "Pure-Python Implementation of the AES block-cipher and common modes of operation" 406 | optional = false 407 | python-versions = "*" 408 | files = [ 409 | {file = "pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"}, 410 | ] 411 | 412 | [[package]] 413 | name = "pyyaml" 414 | version = "6.0.1" 415 | description = "YAML parser and emitter for Python" 416 | optional = false 417 | python-versions = ">=3.6" 418 | files = [ 419 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, 420 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, 421 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, 422 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, 423 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, 424 | {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, 425 | {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, 426 | {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, 427 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, 428 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, 429 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, 430 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, 431 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, 432 | {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, 433 | {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, 434 | {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, 435 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, 436 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, 437 | {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, 438 | {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, 439 | {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, 440 | {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, 441 | {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, 442 | {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, 443 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, 444 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, 445 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, 446 | {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, 447 | {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, 448 | {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, 449 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, 450 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, 451 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, 452 | {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, 453 | {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, 454 | {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, 455 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, 456 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, 457 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, 458 | {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, 459 | {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, 460 | {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, 461 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, 462 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, 463 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, 464 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, 465 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, 466 | {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, 467 | {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, 468 | {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, 469 | {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, 470 | ] 471 | 472 | [[package]] 473 | name = "qqmusic-api" 474 | version = "0.1" 475 | description = "some api about qqmusic" 476 | optional = false 477 | python-versions = ">=3.5.0" 478 | files = [ 479 | {file = "qqmusic-api-0.1.tar.gz", hash = "sha256:45b34e4de15cedb412b6a1f7baab74e150dd8f6342d9089a291c3e4ffd93bc27"}, 480 | {file = "qqmusic_api-0.1-py3-none-any.whl", hash = "sha256:a30570b6766965ea8fd95b25a7be595a7a1d964c083a6a94b3099846fb56e2d4"}, 481 | ] 482 | 483 | [package.dependencies] 484 | requests = "*" 485 | 486 | [[package]] 487 | name = "regex" 488 | version = "2021.11.10" 489 | description = "Alternative regular expression module, to replace re." 490 | optional = false 491 | python-versions = "*" 492 | files = [ 493 | {file = "regex-2021.11.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf"}, 494 | {file = "regex-2021.11.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f"}, 495 | {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965"}, 496 | {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667"}, 497 | {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36"}, 498 | {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f"}, 499 | {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0"}, 500 | {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4"}, 501 | {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9ed0b1e5e0759d6b7f8e2f143894b2a7f3edd313f38cf44e1e15d360e11749b"}, 502 | {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:473e67837f786404570eae33c3b64a4b9635ae9f00145250851a1292f484c063"}, 503 | {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2fee3ed82a011184807d2127f1733b4f6b2ff6ec7151d83ef3477f3b96a13d03"}, 504 | {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d5fd67df77bab0d3f4ea1d7afca9ef15c2ee35dfb348c7b57ffb9782a6e4db6e"}, 505 | {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5d408a642a5484b9b4d11dea15a489ea0928c7e410c7525cd892f4d04f2f617b"}, 506 | {file = "regex-2021.11.10-cp310-cp310-win32.whl", hash = "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a"}, 507 | {file = "regex-2021.11.10-cp310-cp310-win_amd64.whl", hash = "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12"}, 508 | {file = "regex-2021.11.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc"}, 509 | {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0"}, 510 | {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30"}, 511 | {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345"}, 512 | {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733"}, 513 | {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23"}, 514 | {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e"}, 515 | {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:74cbeac0451f27d4f50e6e8a8f3a52ca074b5e2da9f7b505c4201a57a8ed6286"}, 516 | {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:3598893bde43091ee5ca0a6ad20f08a0435e93a69255eeb5f81b85e81e329264"}, 517 | {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:50a7ddf3d131dc5633dccdb51417e2d1910d25cbcf842115a3a5893509140a3a"}, 518 | {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:61600a7ca4bcf78a96a68a27c2ae9389763b5b94b63943d5158f2a377e09d29a"}, 519 | {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:563d5f9354e15e048465061509403f68424fef37d5add3064038c2511c8f5e00"}, 520 | {file = "regex-2021.11.10-cp36-cp36m-win32.whl", hash = "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4"}, 521 | {file = "regex-2021.11.10-cp36-cp36m-win_amd64.whl", hash = "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e"}, 522 | {file = "regex-2021.11.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e"}, 523 | {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36"}, 524 | {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d"}, 525 | {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8"}, 526 | {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a"}, 527 | {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e"}, 528 | {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f"}, 529 | {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:42b50fa6666b0d50c30a990527127334d6b96dd969011e843e726a64011485da"}, 530 | {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6e1d2cc79e8dae442b3fa4a26c5794428b98f81389af90623ffcc650ce9f6732"}, 531 | {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:0416f7399e918c4b0e074a0f66e5191077ee2ca32a0f99d4c187a62beb47aa05"}, 532 | {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ce298e3d0c65bd03fa65ffcc6db0e2b578e8f626d468db64fdf8457731052942"}, 533 | {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dc07f021ee80510f3cd3af2cad5b6a3b3a10b057521d9e6aaeb621730d320c5a"}, 534 | {file = "regex-2021.11.10-cp37-cp37m-win32.whl", hash = "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec"}, 535 | {file = "regex-2021.11.10-cp37-cp37m-win_amd64.whl", hash = "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4"}, 536 | {file = "regex-2021.11.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83"}, 537 | {file = "regex-2021.11.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244"}, 538 | {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50"}, 539 | {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646"}, 540 | {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb"}, 541 | {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec"}, 542 | {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe"}, 543 | {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94"}, 544 | {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f5be7805e53dafe94d295399cfbe5227f39995a997f4fd8539bf3cbdc8f47ca8"}, 545 | {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a955b747d620a50408b7fdf948e04359d6e762ff8a85f5775d907ceced715129"}, 546 | {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:139a23d1f5d30db2cc6c7fd9c6d6497872a672db22c4ae1910be22d4f4b2068a"}, 547 | {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ca49e1ab99593438b204e00f3970e7a5f70d045267051dfa6b5f4304fcfa1dbf"}, 548 | {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:96fc32c16ea6d60d3ca7f63397bff5c75c5a562f7db6dec7d412f7c4d2e78ec0"}, 549 | {file = "regex-2021.11.10-cp38-cp38-win32.whl", hash = "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc"}, 550 | {file = "regex-2021.11.10-cp38-cp38-win_amd64.whl", hash = "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d"}, 551 | {file = "regex-2021.11.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b"}, 552 | {file = "regex-2021.11.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d"}, 553 | {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7"}, 554 | {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc"}, 555 | {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb"}, 556 | {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449"}, 557 | {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b"}, 558 | {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef"}, 559 | {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cd410a1cbb2d297c67d8521759ab2ee3f1d66206d2e4328502a487589a2cb21b"}, 560 | {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e6096b0688e6e14af6a1b10eaad86b4ff17935c49aa774eac7c95a57a4e8c296"}, 561 | {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:529801a0d58809b60b3531ee804d3e3be4b412c94b5d267daa3de7fadef00f49"}, 562 | {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f594b96fe2e0821d026365f72ac7b4f0b487487fb3d4aaf10dd9d97d88a9737"}, 563 | {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2409b5c9cef7054dde93a9803156b411b677affc84fca69e908b1cb2c540025d"}, 564 | {file = "regex-2021.11.10-cp39-cp39-win32.whl", hash = "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a"}, 565 | {file = "regex-2021.11.10-cp39-cp39-win_amd64.whl", hash = "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29"}, 566 | {file = "regex-2021.11.10.tar.gz", hash = "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6"}, 567 | ] 568 | 569 | [[package]] 570 | name = "requests" 571 | version = "2.31.0" 572 | description = "Python HTTP for Humans." 573 | optional = false 574 | python-versions = ">=3.7" 575 | files = [ 576 | {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 577 | {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 578 | ] 579 | 580 | [package.dependencies] 581 | certifi = ">=2017.4.17" 582 | charset-normalizer = ">=2,<4" 583 | idna = ">=2.5,<4" 584 | urllib3 = ">=1.21.1,<3" 585 | 586 | [package.extras] 587 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 588 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 589 | 590 | [[package]] 591 | name = "setuptools" 592 | version = "68.0.0" 593 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 594 | optional = false 595 | python-versions = ">=3.7" 596 | files = [ 597 | {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, 598 | {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, 599 | ] 600 | 601 | [package.extras] 602 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 603 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 604 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 605 | 606 | [[package]] 607 | name = "soupsieve" 608 | version = "2.4.1" 609 | description = "A modern CSS selector implementation for Beautiful Soup." 610 | optional = false 611 | python-versions = ">=3.7" 612 | files = [ 613 | {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, 614 | {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, 615 | ] 616 | 617 | [[package]] 618 | name = "strsimpy" 619 | version = "0.2.1" 620 | description = "A library implementing different string similarity and distance measures" 621 | optional = false 622 | python-versions = "*" 623 | files = [ 624 | {file = "strsimpy-0.2.1-py3-none-any.whl", hash = "sha256:d676a440d5d3dbcf5ba92d01814a03a218776ce07bd7a8185da7019e04cf9ba7"}, 625 | {file = "strsimpy-0.2.1.tar.gz", hash = "sha256:0842eb57f7af86c882a59a1bc8721ec2580a267e563fd0503ced2972040372c9"}, 626 | ] 627 | 628 | [[package]] 629 | name = "typer" 630 | version = "0.3.2" 631 | description = "Typer, build great CLIs. Easy to code. Based on Python type hints." 632 | optional = false 633 | python-versions = ">=3.6" 634 | files = [ 635 | {file = "typer-0.3.2-py3-none-any.whl", hash = "sha256:ba58b920ce851b12a2d790143009fa00ac1d05b3ff3257061ff69dbdfc3d161b"}, 636 | {file = "typer-0.3.2.tar.gz", hash = "sha256:5455d750122cff96745b0dec87368f56d023725a7ebc9d2e54dd23dc86816303"}, 637 | ] 638 | 639 | [package.dependencies] 640 | click = ">=7.1.1,<7.2.0" 641 | 642 | [package.extras] 643 | all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] 644 | dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"] 645 | doc = ["markdown-include (>=0.5.1,<0.6.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)"] 646 | test = ["black (>=19.10b0,<20.0b0)", "coverage (>=5.2,<6.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.782)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "shellingham (>=1.3.0,<2.0.0)"] 647 | 648 | [[package]] 649 | name = "typing-extensions" 650 | version = "4.7.1" 651 | description = "Backported and Experimental Type Hints for Python 3.7+" 652 | optional = false 653 | python-versions = ">=3.7" 654 | files = [ 655 | {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, 656 | {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, 657 | ] 658 | 659 | [[package]] 660 | name = "urllib3" 661 | version = "2.0.7" 662 | description = "HTTP library with thread-safe connection pooling, file post, and more." 663 | optional = false 664 | python-versions = ">=3.7" 665 | files = [ 666 | {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, 667 | {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, 668 | ] 669 | 670 | [package.extras] 671 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 672 | secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] 673 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 674 | zstd = ["zstandard (>=0.18.0)"] 675 | 676 | [[package]] 677 | name = "virtualenv" 678 | version = "20.26.2" 679 | description = "Virtual Python Environment builder" 680 | optional = false 681 | python-versions = ">=3.7" 682 | files = [ 683 | {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, 684 | {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, 685 | ] 686 | 687 | [package.dependencies] 688 | distlib = ">=0.3.7,<1" 689 | filelock = ">=3.12.2,<4" 690 | importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""} 691 | platformdirs = ">=3.9.1,<5" 692 | 693 | [package.extras] 694 | docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] 695 | test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] 696 | 697 | [[package]] 698 | name = "win32-setctime" 699 | version = "1.1.0" 700 | description = "A small Python utility to set file creation time on Windows" 701 | optional = false 702 | python-versions = ">=3.5" 703 | files = [ 704 | {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, 705 | {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, 706 | ] 707 | 708 | [package.extras] 709 | dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] 710 | 711 | [[package]] 712 | name = "zipp" 713 | version = "3.15.0" 714 | description = "Backport of pathlib-compatible object wrapper for zip files" 715 | optional = false 716 | python-versions = ">=3.7" 717 | files = [ 718 | {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, 719 | {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, 720 | ] 721 | 722 | [package.extras] 723 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 724 | testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 725 | 726 | [metadata] 727 | lock-version = "2.0" 728 | python-versions = "^3.7" 729 | content-hash = "4a92b5b736628cfd5b43ddb1b475bbfe6d2576a99a5296762c10a2b4455a25bf" 730 | --------------------------------------------------------------------------------