diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..968c7736 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 + +[*.md] +max_line_length = off +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..bb8f43cf --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,17 @@ +name: 'CI' + +on: + pull_request: + push: + branches: + - main + - dev + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..591d6862 --- /dev/null +++ b/.gitignore @@ -0,0 +1,136 @@ +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..cf49582c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.typeCheckingMode": "strict" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..4fc3277e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,47 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.0] - 2021-09-15 + +### Added + +- 支持检查包是否发布到 PyPI +- 支持检查项目仓库/主页是否可以访问 + +## [0.3.0] - 2021-07-26 + +### Fixed + +- 修改分支名为 publish + +## [0.2.0] - 2020-11-20 + +### Changed + +- 事件判定更严格 +- 支持重开议题事件 +- 检查拉取请求的标签是否为插件 + +### Fixed + +- 拉取请求合并时报错 +- 打开议题触发时,在插件信息中将仓库拥有者误设为插件作者 + +## [0.1.0] - 2020-11-19 + +### Added + +- 最初的版本 + +[Unreleased]: https://github.com/nonebot/nonebot2-publish-bot/compare/v0.4.0...HEAD + +[0.4.0]: https://github.com/nonebot/nonebot2-publish-bot/compare/v0.3.0...v0.4.0 +[0.3.0]: https://github.com/nonebot/nonebot2-publish-bot/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/nonebot/nonebot2-publish-bot/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/nonebot/nonebot2-publish-bot/releases/tag/v0.1.0 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..0906c751 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.9 + +WORKDIR /app + +RUN pip install "PyGithub>=1.55,<2.0" "pydantic>=v1.8.2,<2.0" + +COPY ./main.py /app/ +COPY ./src /app/src + +CMD ["python", "/app/main.py"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..b5fe3cd4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 hemengyang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..dd2d1ed7 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# NoneBot2 Publish Bot + +[NoneBot2](https://github.com/nonebot/nonebot2) 插件/协议/机器人 发布机器人 + +## 主要功能 + +根据 插件/协议/机器人 发布(带 Plugin/Adapter/Bot 标题)议题,自动修改对应文件,并创建拉取请求。 + +## 自动处理 + +- 商店发布议题创建后,自动根据议题内容创建拉取请求 +- 相关议题修改时,自动修改已创建的拉取请求,如果没有创建则重新创建 +- 拉取请求关闭时,自动关闭对应议题,并删除对应分支 +- 已经创建的拉取请求在其他拉取请求合并后,自动解决冲突 +- 自动检查是否符合发布要求 + +### 发布要求 + +- 项目主页能够访问 +- 项目发布至 PyPI + +## 使用方法 + +简单的示例 + +```yaml +name: 'NoneBot2 Publish Bot' + +on: + push: + branches: + - master + issues: + types: [opened, reopened, edited] + pull_request: + types: [closed] + +jobs: + publish_bot: + runs-on: ubuntu-latest + name: nonebot2 publish bot + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: NoneBot2 Publish Bot + uses: nonebot/nonebot2-publish-bot@main + with: + token: ${{ secrets.GITHUB_TOKEN }} + config: > + { + "base": "master", + "plugin_path": "docs/.vuepress/public/plugins.json", + "bot_path": "docs/.vuepress/public/bots.json", + "adapter_path": "docs/.vuepress/public/adapters.json" + } +``` + +## 测试 + +在 [action-test](https://github.com/he0119/action-test) 仓库中测试。 diff --git a/action.yml b/action.yml new file mode 100644 index 00000000..5fdb0140 --- /dev/null +++ b/action.yml @@ -0,0 +1,16 @@ +name: 'NoneBot2 Publish Bot' +description: 'Manage publish related issues in nonebot2 project' +author: 'hemengyang ' +inputs: + token: + description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}' + required: true + config: + description: 'JSON with settings as described in the README' + required: true +runs: + using: 'docker' + image: 'Dockerfile' +branding: + icon: 'box' + color: 'orange' diff --git a/main.py b/main.py new file mode 100644 index 00000000..715026f8 --- /dev/null +++ b/main.py @@ -0,0 +1,42 @@ +import logging + +from github import Github + +from src.models import Settings +from src.process import ( + process_issues_event, + process_pull_request_event, + process_push_event, +) + + +def main(): + FORMAT = "%(asctime)s - %(levelname)s - %(message)s" + logging.basicConfig(level=logging.INFO, format=FORMAT) + + settings = Settings() + logging.info(f"当前配置: {settings.json()}") + + if not settings.input_token.get_secret_value(): + logging.info("无法获得 Token,跳过此次操作") + return + + g = Github(settings.input_token.get_secret_value()) + repo = g.get_repo(settings.github_repository) + + if not settings.github_event_path.is_file(): + logging.error(f"没有在 {settings.github_event_path} 找到 GitHub 事件文件") + return + + if settings.github_event_name == "push": + process_push_event(settings, repo) + elif settings.github_event_name == "pull_request": + process_pull_request_event(settings, repo) + elif settings.github_event_name == "issues": + process_issues_event(settings, repo) + else: + logging.info(f"不支持的事件: {settings.github_event_name},已跳过") + + +if __name__ == "__main__": + main() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..38c70f25 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,503 @@ +[[package]] +name = "black" +version = "21.12b0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" +tomli = ">=0.2.6,<2.0.0" +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, +] + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +python2 = ["typed-ast (>=1.4.3)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "cffi" +version = "1.15.0" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.9" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.0.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.4.0" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pydantic" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pygithub" +version = "1.55" +description = "Use the full Github API v3" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +deprecated = "*" +pyjwt = ">=2.0" +pynacl = ">=1.4.0" +requests = ">=2.14.0" + +[package.extras] +integrations = ["cryptography"] + +[[package]] +name = "pyjwt" +version = "2.3.0" +description = "JSON Web Token implementation in Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +crypto = ["cryptography (>=3.3.1)"] +dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"] + +[[package]] +name = "pynacl" +version = "1.4.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +cffi = ">=1.4.1" +six = "*" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] + +[[package]] +name = "requests" +version = "2.26.0" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "1.2.2" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "urllib3" +version = "1.26.7" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "wrapt" +version = "1.13.3" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "3485d0a308841e6aa0bb25de3f672f74c82c16855263296e2752cdb5258a5888" + +[metadata.files] +black = [ + {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, + {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, +] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] +cffi = [ + {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, + {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, + {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, + {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, + {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, + {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, + {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, + {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, + {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, + {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, + {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, + {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, + {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, + {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, + {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, + {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, + {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.9.tar.gz", hash = "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"}, + {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"}, +] +click = [ + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, + {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, +] +pycparser = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] +pydantic = [ + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, +] +pygithub = [ + {file = "PyGithub-1.55-py3-none-any.whl", hash = "sha256:2caf0054ea079b71e539741ae56c5a95e073b81fa472ce222e81667381b9601b"}, + {file = "PyGithub-1.55.tar.gz", hash = "sha256:1bbfff9372047ff3f21d5cd8e07720f3dbfdaf6462fcaed9d815f528f1ba7283"}, +] +pyjwt = [ + {file = "PyJWT-2.3.0-py3-none-any.whl", hash = "sha256:e0c4bb8d9f0af0c7f5b1ec4c5036309617d03d56932877f2f7a0beeb5318322f"}, + {file = "PyJWT-2.3.0.tar.gz", hash = "sha256:b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41"}, +] +pynacl = [ + {file = "PyNaCl-1.4.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff"}, + {file = "PyNaCl-1.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514"}, + {file = "PyNaCl-1.4.0-cp27-cp27m-win32.whl", hash = "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574"}, + {file = "PyNaCl-1.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80"}, + {file = "PyNaCl-1.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7"}, + {file = "PyNaCl-1.4.0-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122"}, + {file = "PyNaCl-1.4.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d"}, + {file = "PyNaCl-1.4.0-cp35-abi3-win32.whl", hash = "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634"}, + {file = "PyNaCl-1.4.0-cp35-abi3-win_amd64.whl", hash = "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6"}, + {file = "PyNaCl-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4"}, + {file = "PyNaCl-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25"}, + {file = "PyNaCl-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4"}, + {file = "PyNaCl-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6"}, + {file = "PyNaCl-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f"}, + {file = "PyNaCl-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f"}, + {file = "PyNaCl-1.4.0-cp38-cp38-win32.whl", hash = "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96"}, + {file = "PyNaCl-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420"}, + {file = "PyNaCl-1.4.0.tar.gz", hash = "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505"}, +] +requests = [ + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +tomli = [ + {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, + {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, +] +typing-extensions = [ + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, +] +urllib3 = [ + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, +] +wrapt = [ + {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, + {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, + {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, + {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, + {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, + {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, + {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, + {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, + {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, + {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, + {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, + {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, + {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, + {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, + {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, + {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, + {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, + {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, + {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..c9f66f4f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[tool.poetry] +name = "nonebot2-publish-bot" +version = "0.5.0" +description = "Manage publish related issues in nonebot2 project" +authors = ["hemengyang "] +license = "MIT" +readme = "README.md" +homepage = "https://github.com/nonebot/nonebot2-publish-bot" +repository = "https://github.com/nonebot/nonebot2-publish-bot" + +[tool.poetry.dependencies] +python = "^3.9" +pydantic = "^1.8.2" +PyGithub = "^1.55" +requests = "^2.26.0" + +[tool.poetry.dev-dependencies] +isort = "^5.9.3" +black = "^21.9b0" + +# [[tool.poetry.source]] +# default = true +# name = "aliyun" +# url = "https://mirrors.aliyun.com/pypi/simple/" + +[tool.black] +line-length = 88 + +[tool.isort] +profile = "black" +line_length = 88 +skip_gitignore = true + +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core>=1.0.0"] diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/constants.py b/src/constants.py new file mode 100644 index 00000000..1fcd14ee --- /dev/null +++ b/src/constants.py @@ -0,0 +1,7 @@ +COMMENT_TITLE = "# 📃 Publish Check Result" + +REUSE_MESSAGE = "♻️ This comment has been updated with latest result." + +POWERED_BY_BOT_MESSAGE = "💪 Powered by NoneBot2 Publish Bot" + +COMMIT_MESSAGE = ":beers: publish" diff --git a/src/models.py b/src/models.py new file mode 100644 index 00000000..69ff4dc7 --- /dev/null +++ b/src/models.py @@ -0,0 +1,341 @@ +import abc +import json +import re +from enum import Enum +from pathlib import Path +from typing import Optional + +import requests +from github.Issue import Issue +from pydantic import BaseModel, BaseSettings, SecretStr + + +class PartialGithubEventHeadCommit(BaseModel): + message: str + + +class PartialGitHubEventIssue(BaseModel): + number: int + + +class PartialGitHubIssuesEvent(BaseModel): + """议题事件 + + https://docs.github.com/cn/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#issues + """ + + action: str + issue: PartialGitHubEventIssue + + +class PartialGitHubPullRequestEvent(BaseModel): + """拉取请求事件 + + https://docs.github.com/cn/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request + """ + + action: str + pull_request: PartialGitHubEventIssue + + +class PartialGitHubPushEvent(BaseModel): + """推送事件 + + https://docs.github.com/cn/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#push + """ + + head_commit: PartialGithubEventHeadCommit + + +class Config(BaseModel): + base: str + plugin_path: Path + bot_path: Path + adapter_path: Path + + +class Settings(BaseSettings): + input_token: SecretStr + input_config: Config + github_repository: str + github_event_name: Optional[str] = None + github_event_path: Path + + +class PublishType(Enum): + """发布的类型 + + 值为标签名 + """ + + BOT = "Bot" + PLUGIN = "Plugin" + ADAPTER = "Adapter" + + +class PublishInfo(abc.ABC, BaseModel): + """发布信息""" + + name: str + desc: str + author: str + homepage: str + tags: list[str] + is_official: bool + + _homepage_status_code: Optional[int] = None + + def _update_file(self, path: Path): + with path.open("r", encoding="utf-8") as f: + data: list[dict[str, str]] = json.load(f) + with path.open("w", encoding="utf-8") as f: + data.append(self.dict()) + json.dump(data, f, ensure_ascii=False, indent=2) + + @abc.abstractmethod + def update_file(self, settings: Settings) -> None: + """更新文件""" + raise NotImplementedError + + @abc.abstractmethod + def get_type(self) -> PublishType: + """获取发布类型""" + raise NotImplementedError + + @abc.abstractmethod + def is_valid(self) -> bool: + """检查是否满足要求 + + 返回错误列表,如果为空则说明满足要求 + """ + raise NotImplementedError + + def homepage_status_code(self) -> Optional[int]: + """主页状态码""" + if not self._homepage_status_code: + self._homepage_status_code = check_url(self.homepage) + return self._homepage_status_code + + def homepage_is_valid(self) -> bool: + """主页是否可用""" + return self.homepage_status_code() == 200 + + def validate_message(self) -> str: + return generate_message(self) + + class Config: + underscore_attrs_are_private = True + + +class BotPublishInfo(PublishInfo): + """发布机器人所需信息""" + + def get_type(self) -> PublishType: + return PublishType.BOT + + def update_file(self, settings: Settings) -> None: + self._update_file(settings.input_config.bot_path) + + @classmethod + def from_issue(cls, issue: Issue) -> "BotPublishInfo": + body = issue.body + + name = re.search(r"- name: (.+)", body) + desc = re.search(r"- desc: (.+)", body) + author = issue.user.login + homepage = re.search(r"- homepage: (.+)", body) + tags = re.search(r"- tags: (.+)", body) + is_official = re.search(r"- is_official: (.+)", body) + + if not (name and desc and author and homepage and tags and is_official): + raise ValueError("无法获取机器人信息") + + return BotPublishInfo( + name=name.group(1).strip(), + desc=desc.group(1).strip(), + author=author, + homepage=homepage.group(1).strip(), + tags=tags.group(1).strip().split(","), + is_official=is_official.group(1).strip(), + ) + + def is_valid(self) -> bool: + return self.homepage_is_valid() + + +class PluginPublishInfo(PublishInfo): + """发布插件所需信息""" + + module_name: str + project_link: str + + _is_published: Optional[bool] = None + + def get_type(self) -> PublishType: + return PublishType.PLUGIN + + def update_file(self, settings: Settings) -> None: + self._update_file(settings.input_config.plugin_path) + + @classmethod + def from_issue(cls, issue: Issue) -> "PluginPublishInfo": + body = issue.body + + module_name = re.search(r"- module_name: (.+)", body) + project_link = re.search(r"- project_link: (.+)", body) + name = re.search(r"- name: (.+)", body) + desc = re.search(r"- desc: (.+)", body) + author = issue.user.login + homepage = re.search(r"- homepage: (.+)", body) + tags = re.search(r"- tags: (.+)", body) + is_official = re.search(r"- is_official: (.+)", body) + + if not ( + module_name + and project_link + and name + and desc + and author + and homepage + and tags + and is_official + ): + raise ValueError("无法获取插件信息") + + return PluginPublishInfo( + module_name=module_name.group(1).strip(), + project_link=project_link.group(1).strip(), + name=name.group(1).strip(), + desc=desc.group(1).strip(), + author=author, + homepage=homepage.group(1).strip(), + tags=tags.group(1).strip().split(","), + is_official=is_official.group(1).strip(), + ) + + def is_published(self) -> bool: + if self._is_published is None: + self._is_published = check_pypi(self.project_link) + return self._is_published + + def is_valid(self) -> bool: + return self.is_published and self.homepage_is_valid() + + +class AdapterPublishInfo(PublishInfo): + """发布适配器所需信息""" + + module_name: str + project_link: str + + _is_published: Optional[bool] = None + + def get_type(self) -> PublishType: + return PublishType.ADAPTER + + def update_file(self, settings: Settings) -> None: + self._update_file(settings.input_config.adapter_path) + + @classmethod + def from_issue(cls, issue: Issue) -> "AdapterPublishInfo": + body = issue.body + + module_name = re.search(r"- module_name: (.+)", body) + project_link = re.search(r"- project_link: (.+)", body) + name = re.search(r"- name: (.+)", body) + desc = re.search(r"- desc: (.+)", body) + author = issue.user.login + homepage = re.search(r"- homepage: (.+)", body) + tags = re.search(r"- tags: (.+)", body) + is_official = re.search(r"- is_official: (.+)", body) + + if not ( + module_name + and project_link + and name + and desc + and author + and homepage + and tags + and is_official + ): + raise ValueError("无法获取适配器信息") + + return AdapterPublishInfo( + module_name=module_name.group(1).strip(), + project_link=project_link.group(1).strip(), + name=name.group(1).strip(), + desc=desc.group(1).strip(), + author=author, + homepage=homepage.group(1).strip(), + tags=tags.group(1).strip().split(","), + is_official=is_official.group(1).strip(), + ) + + def is_published(self) -> bool: + if self._is_published is None: + self._is_published = check_pypi(self.project_link) + return self._is_published + + def is_valid(self) -> bool: + return self.is_published and self.homepage_is_valid() + + +def check_pypi(project_link: str) -> bool: + """检查项目是否存在""" + url = f"https://pypi.org/pypi/{project_link}/json" + r = requests.get(url) + return r.status_code == 200 + + +def check_url(url: str) -> Optional[int]: + """检查网址是否可以访问 + + 返回状态码,如果报错则返回 None + """ + try: + r = requests.get(url) + return r.status_code + except: + pass + + +def generate_message(info: PublishInfo) -> str: + message = f"> {info.get_type().value}: {info.name}" + + if info.is_valid(): + message += "\n\n**✅ All tests passed, you are ready to go!**" + else: + message += ( + "\n\n**⚠️ We have found following problem(s) in pre-publish progress:**" + ) + + errors: list[str] = [] + if info.homepage_status_code() != 200: + errors.append( + f"""
  • ⚠️ Project homepage returns {info.homepage_status_code()}.
    Please make sure that your project has a publicly visible homepage.
  • """ + ) + if isinstance(info, AdapterPublishInfo) or isinstance(info, PluginPublishInfo): + if not info.is_published(): + errors.append( + f"""
  • ⚠️ Package {info.project_link} is not available on PyPI.
    Please publish your package to PyPI.
  • """ + ) + if len(errors) != 0: + error_message = "".join(errors) + message += f"\n
    {error_message}
    " + + details: list[str] = [] + if info.homepage_status_code() == 200: + details.append( + f"""
  • ✅ Project homepage returns {info.homepage_status_code()}.
  • """ + ) + if isinstance(info, AdapterPublishInfo) or isinstance(info, PluginPublishInfo): + if info.is_published(): + details.append( + f"""
  • ✅ Package {info.project_link} is available on PyPI.
  • """ + ) + if len(details) != 0: + detail_message = "".join(details) + message += f"""\n
    Report Detail
    {detail_message}
    """ + + return message diff --git a/src/process.py b/src/process.py new file mode 100644 index 00000000..3296ad41 --- /dev/null +++ b/src/process.py @@ -0,0 +1,119 @@ +import logging + +from github.Repository import Repository + +from .models import ( + PartialGitHubIssuesEvent, + PartialGitHubPullRequestEvent, + PartialGitHubPushEvent, + Settings, +) +from .utils import ( + comment_issue, + commit_and_push, + create_pull_request, + extract_issue_number_from_ref, + extract_publish_info_from_issue, + get_pull_requests_by_label, + get_type_by_commit_message, + get_type_by_labels, + get_type_by_title, + resolve_conflict_pull_requests, + run_shell_command, +) + + +def process_pull_request_event(settings: Settings, repo: Repository): + """处理 Pull Request 事件""" + event = PartialGitHubPullRequestEvent.parse_file(settings.github_event_path) + logging.info(f"当前事件{event.json()}") + + # 因为合并拉取请求只会触发 closed 事件 + # 其他事件均对商店发布流程无影响 + if event.action != "closed": + logging.info("事件不是关闭拉取请求,已跳过") + return + pull_request = repo.get_pull(event.pull_request.number) + + # 只处理支持标签的拉取请求 + publish_type = get_type_by_labels(pull_request.labels) + if not publish_type: + logging.info("拉取请求与发布无关,已跳过") + return + + ref = pull_request.head.ref + related_issue_number = extract_issue_number_from_ref(ref) + if not related_issue_number: + logging.error("无法获取相关的议题编号") + return + + issue = repo.get_issue(related_issue_number) + issue.edit(state="closed") + logging.info(f"议题 #{related_issue_number} 已关闭") + + try: + run_shell_command(f"git push origin --delete {ref}") + logging.info(f"已删除对应分支") + except: + logging.info("对应分支不存在或已删除") + + if pull_request.merged: + logging.info("发布的拉取请求已合并,准备更新拉取请求的提交") + pull_requests = get_pull_requests_by_label(repo, publish_type.value) + resolve_conflict_pull_requests(settings, pull_requests, repo) + else: + logging.info("发布的拉取请求未合并,已跳过") + + +def process_push_event(settings: Settings, repo: Repository): + """处理提交""" + event = PartialGitHubPushEvent.parse_file(settings.github_event_path) + logging.info(f"当前事件{event.json()}") + + publish_type = get_type_by_commit_message(event.head_commit.message) + if not publish_type: + logging.info("该提交不是发布,已跳过") + return + + logging.info("发现提交为发布,准备更新拉取请求的提交") + pull_requests = get_pull_requests_by_label(repo, publish_type.value) + resolve_conflict_pull_requests(settings, pull_requests, repo) + + +def process_issues_event(settings: Settings, repo: Repository): + """处理议题""" + event = PartialGitHubIssuesEvent.parse_file(settings.github_event_path) + logging.info(f"当前事件{event.json()}") + + if not event.action in ["opened", "reopened", "edited"]: + logging.info("不支持的事件,已跳过") + return + issue = repo.get_issue(event.issue.number) + + publish_type = get_type_by_title(issue.title) + + if not publish_type: + logging.info("") + return + + info = extract_publish_info_from_issue(issue, publish_type) + + # 自动给议题添加标签 + issue.edit(labels=[publish_type.value]) + + # 检查是否满足发布要求 + # 仅在通过检查的情况下创建拉取请求 + if info.is_valid(): + # 创建新分支 + # 命名示例 publish/issue123 + branch_name = f"publish/issue{issue.number}" + run_shell_command(f"git checkout -b {branch_name}") + # 更新文件并提交更改 + info.update_file(settings) + commit_and_push(info, branch_name) + # 创建拉取请求 + create_pull_request( + repo, info, settings.input_config.base, branch_name, issue.number + ) + + comment_issue(issue, info.validate_message()) diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 00000000..19295c8d --- /dev/null +++ b/src/utils.py @@ -0,0 +1,177 @@ +import logging +import re +import subprocess +from typing import Optional + +from github.Issue import Issue +from github.Label import Label +from github.PullRequest import PullRequest +from github.Repository import Repository + +from .constants import ( + COMMENT_TITLE, + COMMIT_MESSAGE, + POWERED_BY_BOT_MESSAGE, + REUSE_MESSAGE, +) +from .models import ( + AdapterPublishInfo, + BotPublishInfo, + PluginPublishInfo, + PublishInfo, + PublishType, + Settings, +) + + +def run_shell_command(command: str): + """运行 shell 命令 + + 如果遇到错误则抛出异常 + """ + subprocess.run(command, shell=True, check=True) + + +def get_type_by_labels(labels: list[Label]) -> Optional[PublishType]: + """通过标签获取类型""" + for label in labels: + if label.name == PublishType.BOT.value: + return PublishType.BOT + if label.name == PublishType.PLUGIN.value: + return PublishType.PLUGIN + if label.name == PublishType.ADAPTER.value: + return PublishType.ADAPTER + + +def get_type_by_title(title: str) -> Optional[PublishType]: + """通过标题获取类型""" + if title.startswith(f"{PublishType.BOT.value}:"): + return PublishType.BOT + if title.startswith(f"{PublishType.PLUGIN.value}:"): + return PublishType.PLUGIN + if title.startswith(f"{PublishType.ADAPTER.value}:"): + return PublishType.ADAPTER + + +def get_type_by_commit_message(message: str) -> Optional[PublishType]: + """通过提交信息获取类型""" + if message.startswith(f"{COMMIT_MESSAGE} {PublishType.BOT.value.lower()}"): + return PublishType.BOT + if message.startswith(f"{COMMIT_MESSAGE} {PublishType.PLUGIN.value.lower()}"): + return PublishType.PLUGIN + if message.startswith(f"{COMMIT_MESSAGE} {PublishType.ADAPTER.value.lower()}"): + return PublishType.ADAPTER + + +def commit_and_push(info: PublishInfo, branch_name: str): + """提交并推送""" + commit_message = f"{COMMIT_MESSAGE} {info.get_type().value.lower()} {info.name}" + run_shell_command(f"git config --global user.name {info.author}") + user_email = f"{info.author}@users.noreply.github.com" + run_shell_command(f"git config --global user.email {user_email}") + run_shell_command(f"git add -A") + run_shell_command(f'git commit -m "{commit_message}"') + run_shell_command(f"git push origin {branch_name} -f") + + +def create_pull_request( + repo: Repository, + info: PublishInfo, + base: str, + branch_name: str, + issue_number: int, +): + """创建拉取请求 + + 同时添加对应标签 + 内容关联上对应的议题 + """ + title = f"{info.get_type().value} {info.name}" + # 关联相关议题,当拉取请求合并时会自动关闭对应议题 + body = f"resolve #{issue_number}" + try: + # 创建拉取请求 + pull = repo.create_pull( + title=title, + body=body, + base=base, + head=branch_name, + ) + # 自动给拉取请求添加标签 + pull.add_to_labels(info.get_type().value) + except: + logging.info("该分支的拉取请求已创建,请前往查看") + + +def get_pull_requests_by_label(repo: Repository, label: str) -> list[PullRequest]: + """获取所有带有指定标签的拉取请求""" + pulls = list(repo.get_pulls(state="open")) + return [pull for pull in pulls if label in [label.name for label in pull.labels]] + + +def extract_issue_number_from_ref(ref: str) -> Optional[int]: + """从 Ref 中提取议题号""" + match = re.search(r"\/issue(\d+)", ref) + if match: + return int(match.group(1)) + + +def resolve_conflict_pull_requests( + settings: Settings, pulls: list[PullRequest], repo: Repository +): + """根据关联的议题提交来解决冲突 + + 参考对应的议题重新更新对应分支 + """ + for pull in pulls: + # 切换到对应分支 + run_shell_command(f"git checkout -b {pull.head.ref}") + # 重置之前的提交 + run_shell_command(f"git reset --hard {settings.input_config.base}") + issue_number = extract_issue_number_from_ref(pull.head.ref) + if not issue_number: + logging.error(f"无法获取 {pull.title} 对应的议题") + return + + logging.info(f"正在处理 {pull.title}") + issue = repo.get_issue(issue_number) + publish_type = get_type_by_labels(issue.labels) + + if publish_type: + info = extract_publish_info_from_issue(issue, publish_type) + info.update_file(settings) + commit_and_push(info, pull.head.ref) + logging.info("拉取请求更新完毕") + + +def extract_publish_info_from_issue( + issue: Issue, publish_type: PublishType +) -> PublishInfo: + """从议题中提取发布所需数据""" + if publish_type == PublishType.BOT: + return BotPublishInfo.from_issue(issue) + elif publish_type == PublishType.PLUGIN: + return PluginPublishInfo.from_issue(issue) + return AdapterPublishInfo.from_issue(issue) + + +def comment_issue(issue: Issue, body: str): + """在议题中发布评论""" + # 给评论添加统一的标题 + body = f"{COMMENT_TITLE}\n{body}" + logging.info("开始发布评论") + comments = issue.get_comments() + + # 重复利用评论 + # 如果发现之前评论过,直接修改之前的评论 + reusable_comment = next( + filter(lambda x: x.body.startswith(COMMENT_TITLE), comments), None + ) + if reusable_comment: + logging.info(f"发现已有评论 {reusable_comment.id},正在修改") + body += f"\n\n---\n{REUSE_MESSAGE}\n\n{POWERED_BY_BOT_MESSAGE}" + reusable_comment.edit(body) + logging.info("评论修改完成") + else: + issue.create_comment(body) + logging.info("评论创建完成")