diff --git a/.gitignore b/.gitignore index aaab54b..748c60f 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,4 @@ cython_debug/ cache/ data/ docker-compose.yml +.pdm-python diff --git a/main.py b/main.py index 8643434..a531280 100644 --- a/main.py +++ b/main.py @@ -1,58 +1,18 @@ -import asyncio - -from signal import signal as signal_fn, SIGINT, SIGTERM, SIGABRT - -from src.app import web -from src.bot import bot -from src.env import MIYOUSHE, HOYOLAB, BOT +from persica.context.application import ApplicationContext +from persica.applicationbuilder import ApplicationBuilder -async def idle(): - task = None - - def signal_handler(_, __): - if web.web_server_task: - web.web_server_task.cancel() - task.cancel() - - for s in (SIGINT, SIGTERM, SIGABRT): - signal_fn(s, signal_handler) - - while True: - task = asyncio.create_task(asyncio.sleep(600)) - web.bot_main_task = task - try: - await task - except asyncio.CancelledError: - break - - -async def main(): - if MIYOUSHE: - from src.render.article import refresh_recommend_posts - - await refresh_recommend_posts() - if HOYOLAB: - from src.render.article_hoyolab import refresh_hoyo_recommend_posts - - await refresh_hoyo_recommend_posts() - await web.start() - if BOT: - await bot.start() - try: - await idle() - finally: - if BOT: - try: - await bot.stop() - except RuntimeError: - pass - if web.web_server: - try: - await web.web_server.shutdown() - except AttributeError: - pass +def main(): + app = ( + ApplicationBuilder() + .set_application_context_class(ApplicationContext) + .set_scanner_packages("src/core") + .build() + ) + app.class_scanner.flash("src/plugins") + app.class_scanner.flash("src/route") + app.run() if __name__ == "__main__": - asyncio.get_event_loop().run_until_complete(main()) + main() diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 0000000..0d6e85f --- /dev/null +++ b/pdm.lock @@ -0,0 +1,840 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "test"] +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:268dec44f0ecec5ed8bcdf7cbe20bb3d7b945f16196c3ff5ec721cd01dc19be0" + +[[metadata.targets]] +requires_python = ">=3.10" + +[[package]] +name = "aiofiles" +version = "24.1.0" +requires_python = ">=3.8" +summary = "File support for asyncio." +groups = ["default"] +files = [ + {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, + {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +requires_python = ">=3.8" +summary = "Reusable constraint types to use with typing.Annotated" +groups = ["default"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.9\"", +] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.5.0" +requires_python = ">=3.8" +summary = "High level compatibility layer for multiple asynchronous event loop implementations" +groups = ["default"] +dependencies = [ + "exceptiongroup>=1.0.2; python_version < \"3.11\"", + "idna>=2.8", + "sniffio>=1.1", + "typing-extensions>=4.1; python_version < \"3.11\"", +] +files = [ + {file = "anyio-4.5.0-py3-none-any.whl", hash = "sha256:fdeb095b7cc5a5563175eedd926ec4ae55413bb4be5770c424af0ba46ccb4a78"}, + {file = "anyio-4.5.0.tar.gz", hash = "sha256:c5a275fe5ca0afd788001f58fca1e69e29ce706d746e317d660e21f70c530ef9"}, +] + +[[package]] +name = "apscheduler" +version = "3.10.4" +requires_python = ">=3.6" +summary = "In-process task scheduler with Cron-like capabilities" +groups = ["default"] +dependencies = [ + "importlib-metadata>=3.6.0; python_version < \"3.8\"", + "pytz", + "six>=1.4.0", + "tzlocal!=3.*,>=2.0", +] +files = [ + {file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"}, + {file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +requires_python = ">=3.6.0" +summary = "Screen-scraping library" +groups = ["default"] +dependencies = [ + "soupsieve>1.2", +] +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +groups = ["default"] +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "click" +version = "8.1.7" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +groups = ["default"] +dependencies = [ + "colorama; platform_system == \"Windows\"", + "importlib-metadata; python_version < \"3.8\"", +] +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +groups = ["default", "test"] +marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "Colored terminal output for Python's logging module" +groups = ["default"] +dependencies = [ + "humanfriendly>=9.1", +] +files = [ + {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, + {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" +groups = ["default", "test"] +marker = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[[package]] +name = "fastapi" +version = "0.115.0" +requires_python = ">=3.8" +summary = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +groups = ["default"] +dependencies = [ + "pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4", + "starlette<0.39.0,>=0.37.2", + "typing-extensions>=4.8.0", +] +files = [ + {file = "fastapi-0.115.0-py3-none-any.whl", hash = "sha256:17ea427674467486e997206a5ab25760f6b09e069f099b96f5b55a32fb6f1631"}, + {file = "fastapi-0.115.0.tar.gz", hash = "sha256:f93b4ca3529a8ebc6fc3fcf710e5efa8de3df9b41570958abf1d97d843138004"}, +] + +[[package]] +name = "filelock" +version = "3.16.1" +requires_python = ">=3.8" +summary = "A platform independent file lock." +groups = ["default"] +files = [ + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, +] + +[[package]] +name = "h11" +version = "0.14.0" +requires_python = ">=3.7" +summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +groups = ["default"] +dependencies = [ + "typing-extensions; python_version < \"3.8\"", +] +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +requires_python = ">=3.8" +summary = "A minimal low-level HTTP client." +groups = ["default"] +dependencies = [ + "certifi", + "h11<0.15,>=0.13", +] +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[[package]] +name = "httpx" +version = "0.27.2" +requires_python = ">=3.8" +summary = "The next generation HTTP client." +groups = ["default"] +dependencies = [ + "anyio", + "certifi", + "httpcore==1.*", + "idna", + "sniffio", +] +files = [ + {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, + {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "Human friendly output for text interfaces using Python" +groups = ["default"] +dependencies = [ + "monotonic; python_version == \"2.7\"", + "pyreadline3; sys_platform == \"win32\" and python_version >= \"3.8\"", + "pyreadline; sys_platform == \"win32\" and python_version < \"3.8\"", +] +files = [ + {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, + {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, +] + +[[package]] +name = "idna" +version = "3.10" +requires_python = ">=3.6" +summary = "Internationalized Domain Names in Applications (IDNA)" +groups = ["default"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" +groups = ["test"] +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +requires_python = ">=3.7" +summary = "A very fast and expressive template engine." +groups = ["default"] +dependencies = [ + "MarkupSafe>=2.0", +] +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[[package]] +name = "lxml" +version = "5.3.0" +requires_python = ">=3.6" +summary = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +groups = ["default"] +files = [ + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, + {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, + {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, + {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, + {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, + {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, + {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, + {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, + {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, + {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +requires_python = ">=3.7" +summary = "Safely add untrusted strings to HTML/XML markup." +groups = ["default"] +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "networkx" +version = "3.3" +requires_python = ">=3.10" +summary = "Python package for creating and manipulating graphs and networks" +groups = ["default"] +files = [ + {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, + {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, +] + +[[package]] +name = "packaging" +version = "24.1" +requires_python = ">=3.8" +summary = "Core utilities for Python packages" +groups = ["test"] +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "persica" +version = "0.1.0a1" +requires_python = ">=3.10" +git = "https://github.com/luoshuijs/Persica" +ref = "dev" +revision = "0ef0f04a7938d7394ada2e2886a9eb70dd21c41e" +summary = "Automatic framework for Pythone" +groups = ["default"] +dependencies = [ + "networkx>=3.3", +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +requires_python = ">=3.8" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +groups = ["default"] +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +requires_python = ">=3.8" +summary = "plugin and hook calling mechanisms for python" +groups = ["test"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[[package]] +name = "pyaes" +version = "1.6.1" +summary = "Pure-Python Implementation of the AES block-cipher and common modes of operation" +groups = ["default"] +files = [ + {file = "pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"}, +] + +[[package]] +name = "pydantic" +version = "2.9.2" +requires_python = ">=3.8" +summary = "Data validation using Python type hints" +groups = ["default"] +dependencies = [ + "annotated-types>=0.6.0", + "pydantic-core==2.23.4", + "typing-extensions>=4.12.2; python_version >= \"3.13\"", + "typing-extensions>=4.6.1; python_version < \"3.13\"", +] +files = [ + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, +] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +requires_python = ">=3.8" +summary = "Core functionality for Pydantic validation and serialization" +groups = ["default"] +dependencies = [ + "typing-extensions!=4.7.0,>=4.6.0", +] +files = [ + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, +] + +[[package]] +name = "pyreadline3" +version = "3.5.4" +requires_python = ">=3.8" +summary = "A python implementation of GNU readline." +groups = ["default"] +marker = "sys_platform == \"win32\" and python_version >= \"3.8\"" +files = [ + {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, + {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, +] + +[[package]] +name = "pyrogram" +version = "2.0.124" +requires_python = "~=3.8" +git = "https://github.com/TeamPGM/pyrogram" +revision = "bcfc7c386bea65939775fa02f5e6d40bdccb7c7f" +summary = "Elegant, modern and asynchronous Telegram MTProto API framework in Python for users and bots" +groups = ["default"] +dependencies = [ + "pyaes==1.6.1", + "pysocks==1.7.1", +] + +[[package]] +name = "pyrotgcrypto" +version = "1.2.7" +requires_python = "~=3.7" +summary = "Fast and Portable Cryptography Extension Library for Pyrogram" +groups = ["default"] +files = [ + {file = "PyroTgCrypto-1.2.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:35b2b5e5162ecea4f754412614243933536466d93c3cf3c857526d861d8c661d"}, + {file = "PyroTgCrypto-1.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:991323f7c2d8c2780bd1e5342693bda6d6300205a48732b57ca865a962750fa2"}, + {file = "PyroTgCrypto-1.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:38202a4672951b350afc209700b88c93f9b75d80c948dc5802f9fe80803a12db"}, + {file = "PyroTgCrypto-1.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21f2ff6398036990d51a424ad630b253c455d5e57ce15a24fce1af99af4d9e1f"}, + {file = "PyroTgCrypto-1.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29741048e5a54ca13094e0649d867c609feffa264f4ae76964142484f07b800f"}, + {file = "PyroTgCrypto-1.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:677fc0517d6c3a94c7d90944bc77827c801af6f75a1a4a20206ceaf1db856ef2"}, + {file = "PyroTgCrypto-1.2.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:64a89f1a251d9cfbbecd5255097c561f1693be54eef455c67ab43f7f5477c851"}, + {file = "PyroTgCrypto-1.2.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9ddf241cfdde91b1f9dc242cddcf95d5b1ead0b446f056f650d029a38410c9da"}, + {file = "PyroTgCrypto-1.2.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8b9e02dfa4ceb28a33d6d32b68d8547463713dd848f75304e06aeb9c82f9c446"}, + {file = "PyroTgCrypto-1.2.7-cp310-cp310-win32.whl", hash = "sha256:61b7a1d70572795100c22129d7d848386e1890c93504dbfb54599474627cf657"}, + {file = "PyroTgCrypto-1.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:dc1413f54e5f05eaa4f86525d87c78d765cbbb5f20389db46458fa4e7b429019"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cd81935567e792a57768b3242c268cb8ccc538504c82b4d2ac2cc2e64a23a95"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd965133558d9995635b77d461eb4c12a90b2387b9a2f4d3be0329a0cc03c66d"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4d423d9bf1e15a1527bcfe3a3ff7aa625c7112db97635a3c2f7e9da86f19112"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e364743e762fed1006b6675f2cda117968450205a98f83c8e87b3f337d7fbf3e"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5acda6512167e2a8fdb431ec5919f1bd33b67c3a6802d69f0e7ab6b535968f9f"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72ccd78d50c3480717690f01b8e0533e591017946f02da89a954c863a9da230d"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9b6418477eb7d8d2878a873ff4352947b7c5ff8256bc8f4f714b2636f707f5f1"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0dd9b0e560a1f575bf86ab649757a7ceafd03f858d1a686398b216c5929c936"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:48542a8de389a3a5258df70708b5fdce8c63cf8a068e8cb580cd3a01689f6261"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-win32.whl", hash = "sha256:85a57a10cafafc0f95a8dd7988754f4194b281ac07e6c3e2c7c6065097f0dba9"}, + {file = "PyroTgCrypto-1.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:6b58710373939a9c2c520c00666631cd782adbb14434a179d22c5eeae3f0727f"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:75dac820a0ead43bbc8f134f741472d889318d32b4f587e13342a4f412b6e6c8"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3af1a29b6c7392abb060b53dcbdbfc213253b8f891d37dc2890151356ef8ece4"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5a09deeb201de18a7e3ed2b5fca2db421ad5c610d7b6db4a9b29d63df5dae6db"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1306b9f0fbc77be8add4a6f3c68a9d1d005d1bdd970965b6936ca7419820afba"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd99a80f8c104104e30a94a25e7e8c4f190891d3aceaa9f81983358b568c2438"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca1568499a7699c399a92b84775061ce5c617ad87c1e6e1180e01caf60f47c4d"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a7f7c20bb1136afeea3e760e7e07f352787c6f9a681cdeec1b8aad18805443a"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fc384fc568f1fcdcfa3ae5b556898c9caa48f61a91ca39767cd52b420b7f1a44"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:222a5c4a243d379db075105aed4f071a377b7dad7e8c02b4055b680033a4a57b"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-win32.whl", hash = "sha256:1eda8e08a39eead928285560029957a2cf4877da3e3cb233649da073a47dc9e8"}, + {file = "PyroTgCrypto-1.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:8eff5b2f18fc03b2ddcfd702d9363cb064ee56b470816a5eb2421ee7881b67f4"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:15ade101da138e520f48574e49ea7a44bab13275d019eb21feb5e9d718b6d65c"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8418ecb1a51d484de79502da4b1d7587a32599cd18ef3834f228e04370b1be3e"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a75a8c9177bcd186d37b82ce9964009c8dcf0996626cb40d743f2fa32fc61bf8"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5df7e330f0dfddbb2dae6bc9c8d0ac8acb8842383efe35590801ef247f25485c"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad37d19118656d6e6e0e7e4ff4b6f31247e892924ff1e4170c52d1db1fd22a96"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2b5ecfdd5e1609789e0ae479154661756d60c8c61beb7ed7660139f4fec1e4"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:74446fa32f9a4da4bd95903ce73553d6c9e790d480bf4a99624e56279cbeb2dc"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:96814fc33194e5c9114110197690f81b8f60bb65eb0f492d81ba3f048f7d5d9f"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c8775ebefcc3c25fa68f67126ceab6d8c38490e14249d4f80463faed2ae86612"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-win32.whl", hash = "sha256:ddcdb4ce089fe691bdca571d0a6661f6df8c1f5dbb6470e66ae43097457fcdb2"}, + {file = "PyroTgCrypto-1.2.7-cp313-cp313-win_amd64.whl", hash = "sha256:bba87c07da6f76fd70ce894abc9d5321c2339aebf7968877e3726f9710f98458"}, + {file = "PyroTgCrypto-1.2.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:021d1f1be6d49c6ff8fd351785c02bcd5e525d9e9bef6a4d3f06ce8e9e1a13eb"}, + {file = "PyroTgCrypto-1.2.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f75d252d6544c88eda9f6273798d5b8d5e41fe5131b5d9accf4770e449bba59b"}, + {file = "PyroTgCrypto-1.2.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d8211806db398ca0432824669755e547b56735eb82e26806ae60e59ebe2661c"}, + {file = "PyroTgCrypto-1.2.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64572319f81e64d52acad145851d03aba74dbe82a32db80e8202bc938ebb395e"}, + {file = "PyroTgCrypto-1.2.7-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddce8ab3709fb3cb074295187d7b8baade3bf0cf77c26683bfc9631ffcf28740"}, + {file = "PyroTgCrypto-1.2.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c83f6f0a8f6c7edba2961a62dc5ffa5bcf24cd69a7f0848c287800c673797fc7"}, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +groups = ["default"] +files = [ + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] + +[[package]] +name = "pytest" +version = "8.3.3" +requires_python = ">=3.8" +summary = "pytest: simple powerful testing with Python" +groups = ["test"] +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "iniconfig", + "packaging", + "pluggy<2,>=1.5", + "tomli>=1; python_version < \"3.11\"", +] +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[[package]] +name = "pytest-asyncio" +version = "0.24.0" +requires_python = ">=3.8" +summary = "Pytest support for asyncio" +groups = ["test"] +dependencies = [ + "pytest<9,>=8.2", +] +files = [ + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +requires_python = ">=3.8" +summary = "Read key-value pairs from a .env file and set them as environment variables" +groups = ["default"] +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[[package]] +name = "pytz" +version = "2024.2" +summary = "World timezone definitions, modern and historical" +groups = ["default"] +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" +groups = ["default"] +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +requires_python = ">=3.7" +summary = "Sniff out which async library your code is running under" +groups = ["default"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "soupsieve" +version = "2.6" +requires_python = ">=3.8" +summary = "A modern CSS selector implementation for Beautiful Soup." +groups = ["default"] +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + +[[package]] +name = "starlette" +version = "0.38.5" +requires_python = ">=3.8" +summary = "The little ASGI library that shines." +groups = ["default"] +dependencies = [ + "anyio<5,>=3.4.0", + "typing-extensions>=3.10.0; python_version < \"3.10\"", +] +files = [ + {file = "starlette-0.38.5-py3-none-any.whl", hash = "sha256:632f420a9d13e3ee2a6f18f437b0a9f1faecb0bc42e1942aa2ea0e379a4c4206"}, + {file = "starlette-0.38.5.tar.gz", hash = "sha256:04a92830a9b6eb1442c766199d62260c3d4dc9c4f9188360626b1e0273cb7077"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +requires_python = ">=3.7" +summary = "A lil' TOML parser" +groups = ["test"] +marker = "python_version < \"3.11\"" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["default"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "tzdata" +version = "2024.1" +requires_python = ">=2" +summary = "Provider of IANA time zone data" +groups = ["default"] +marker = "platform_system == \"Windows\"" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + +[[package]] +name = "tzlocal" +version = "5.2" +requires_python = ">=3.8" +summary = "tzinfo object for the local timezone" +groups = ["default"] +dependencies = [ + "backports-zoneinfo; python_version < \"3.9\"", + "tzdata; platform_system == \"Windows\"", +] +files = [ + {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, + {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, +] + +[[package]] +name = "uritools" +version = "4.0.3" +requires_python = ">=3.7" +summary = "URI parsing, classification and composition" +groups = ["default"] +files = [ + {file = "uritools-4.0.3-py3-none-any.whl", hash = "sha256:bae297d090e69a0451130ffba6f2f1c9477244aa0a5543d66aed2d9f77d0dd9c"}, + {file = "uritools-4.0.3.tar.gz", hash = "sha256:ee06a182a9c849464ce9d5fa917539aacc8edd2a4924d1b7aabeeecabcae3bc2"}, +] + +[[package]] +name = "urlextract" +version = "1.9.0" +summary = "Collects and extracts URLs from given text." +groups = ["default"] +dependencies = [ + "filelock", + "idna", + "platformdirs", + "uritools", +] +files = [ + {file = "urlextract-1.9.0-py3-none-any.whl", hash = "sha256:f88963532488b1c7c405e21bd162ae97871754ea04b60e18d33ee075b19b82fd"}, + {file = "urlextract-1.9.0.tar.gz", hash = "sha256:70508e02ba9df372e25cf0642db367cece273e8712cd0ce78178fc5dd7ea00db"}, +] + +[[package]] +name = "uvicorn" +version = "0.30.6" +requires_python = ">=3.8" +summary = "The lightning-fast ASGI server." +groups = ["default"] +dependencies = [ + "click>=7.0", + "h11>=0.8", + "typing-extensions>=4.0; python_version < \"3.11\"", +] +files = [ + {file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"}, + {file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fca51ed --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,38 @@ +[project] +name = "FixMiYouShe" +version = "0.1.0" +description = "Default template for PDM package" +authors = [ + {name = "xtaodada", email = "xtao@xtaolink.cn"}, +] +dependencies = [ + "httpx>=0.24.1", + "fastapi>=0.110.0", + "starlette", + "uvicorn>=0.23.2", + "pydantic", + "python-dotenv", + "coloredlogs", + "pytz", + "apscheduler", + "aiofiles>=23.2.1", + "jinja2>=3.1.3", + "beautifulsoup4", + "lxml", + "PyroTgCrypto>=1.2.7", + "pyrogram @ git+https://github.com/TeamPGM/pyrogram", + "urlextract", + "persica @ git+https://github.com/luoshuijs/Persica@dev", +] +requires-python = ">=3.10" +readme = "README.md" +license = {text = "AGPL 3.0+"} + +[tool.pdm] +distribution = false + +[tool.pdm.dev-dependencies] +test = [ + "pytest>=8.3.3", + "pytest-asyncio>=0.24.0", +] diff --git a/requirements.txt b/requirements.txt index 5b61ecb..1584b7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,51 @@ -httpx==0.24.1 -fastapi~=0.110.0 -starlette -uvicorn~=0.23.2 -pydantic -python-dotenv -coloredlogs -pytz -apscheduler -aiofiles==23.2.1 -jinja2==3.1.3 -beautifulsoup4 -lxml -PyroTgCrypto==1.2.6a0 -git+https://github.com/TeamPGM/pyrogram -urlextract +# This file is @generated by PDM. +# Please do not edit it manually. + +aiofiles==24.1.0 +annotated-types==0.7.0 +anyio==4.5.0 +apscheduler==3.10.4 +beautifulsoup4==4.12.3 +certifi==2024.8.30 +click==8.1.7 +colorama==0.4.6; sys_platform == "win32" or platform_system == "Windows" +coloredlogs==15.0.1 +exceptiongroup==1.2.2; python_version < "3.11" +fastapi==0.115.0 +filelock==3.16.1 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.2 +humanfriendly==10.0 +idna==3.10 +iniconfig==2.0.0 +jinja2==3.1.4 +lxml==5.3.0 +markupsafe==2.1.5 +networkx==3.3 +packaging==24.1 +persica @ git+https://github.com/luoshuijs/Persica@dev +platformdirs==4.3.6 +pluggy==1.5.0 +pyaes==1.6.1 +pydantic==2.9.2 +pydantic-core==2.23.4 +pyreadline3==3.5.4; sys_platform == "win32" and python_version >= "3.8" +pyrogram @ git+https://github.com/TeamPGM/pyrogram +pyrotgcrypto==1.2.7 +pysocks==1.7.1 +pytest==8.3.3 +pytest-asyncio==0.24.0 +python-dotenv==1.0.1 +pytz==2024.2 +six==1.16.0 +sniffio==1.3.1 +soupsieve==2.6 +starlette==0.38.5 +tomli==2.0.1; python_version < "3.11" +typing-extensions==4.12.2 +tzdata==2024.1; platform_system == "Windows" +tzlocal==5.2 +uritools==4.0.3 +urlextract==1.9.0 +uvicorn==0.30.6 diff --git a/src/bot.py b/src/bot.py deleted file mode 100644 index ef63f48..0000000 --- a/src/bot.py +++ /dev/null @@ -1,17 +0,0 @@ -from pyrogram import Client - -from pathlib import Path - -from .env import BOT_API_ID_INT, BOT_API_HASH, BOT_TOKEN - -data_path = Path("data") -data_path.mkdir(exist_ok=True) - -bot = Client( - "bot", - api_id=int(BOT_API_ID_INT), - api_hash=BOT_API_HASH, - bot_token=BOT_TOKEN, - workdir="data", - plugins=dict(root="src/plugins"), -) diff --git a/src/services/__init__.py b/src/core/__init__.py similarity index 100% rename from src/services/__init__.py rename to src/core/__init__.py diff --git a/src/core/bot.py b/src/core/bot.py new file mode 100644 index 0000000..e1b0cab --- /dev/null +++ b/src/core/bot.py @@ -0,0 +1,34 @@ +from persica.factory.component import AsyncInitializingComponent + +from pyrogram import Client + +from pathlib import Path + +from src.env import BOT, BOT_API_ID_INT, BOT_API_HASH, BOT_TOKEN + +data_path = Path("data") +data_path.mkdir(exist_ok=True) + + +class TelegramBot(AsyncInitializingComponent): + def __init__(self): + self.bot = Client( + "bot", + api_id=int(BOT_API_ID_INT), + api_hash=BOT_API_HASH, + bot_token=BOT_TOKEN, + workdir="data", + ) + + async def initialize(self): + if not BOT: + return + await self.bot.start() + + async def shutdown(self): + if not BOT: + return + try: + await self.bot.stop() + except RuntimeError: + pass diff --git a/src/core/scheduler.py b/src/core/scheduler.py new file mode 100644 index 0000000..2097324 --- /dev/null +++ b/src/core/scheduler.py @@ -0,0 +1,13 @@ +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from fastapi import FastAPI +from persica.factory.component import BaseComponent + + +class TimeScheduler(BaseComponent): + def __init__(self): + self.scheduler = AsyncIOScheduler(timezone="Asia/ShangHai") + + def register_scheduler(self, app: "FastAPI"): + @app.on_event("startup") + async def start_event(): + self.scheduler.start() diff --git a/src/app.py b/src/core/web_app.py similarity index 58% rename from src/app.py rename to src/core/web_app.py index 67064fa..d9dcf1a 100644 --- a/src/app.py +++ b/src/core/web_app.py @@ -3,39 +3,33 @@ import asyncio import uvicorn from fastapi import FastAPI +from persica.factory.component import AsyncInitializingComponent from starlette.middleware.trustedhost import TrustedHostMiddleware -from .env import DOMAIN, DEBUG, PORT -from .route import get_routes -from .route.base import UserAgentMiddleware -from .services.scheduler import register_scheduler - -app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None) +from src.core.scheduler import TimeScheduler +from src.env import DOMAIN, DEBUG, PORT -class Web: - def __init__(self): +class WebApp(AsyncInitializingComponent): + def __init__(self, scheduler: TimeScheduler): + self.app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None) + scheduler.register_scheduler(self.app) self.web_server = None self.web_server_task = None - self.bot_main_task = None - @staticmethod - def init_web(): + def init_web(self): if not DEBUG: - app.add_middleware( + self.app.add_middleware( TrustedHostMiddleware, allowed_hosts=[ DOMAIN, ], ) - app.add_middleware(UserAgentMiddleware) - get_routes() - register_scheduler(app) async def start(self): self.init_web() self.web_server = uvicorn.Server( - config=uvicorn.Config(app, host="0.0.0.0", port=PORT) + config=uvicorn.Config(self.app, host="0.0.0.0", port=PORT) ) server_config = self.web_server.config server_config.setup_event_loop() @@ -54,8 +48,14 @@ class Web: def stop(self): if self.web_server_task: self.web_server_task.cancel() - if self.bot_main_task: - self.bot_main_task.cancel() + async def initialize(self): + await self.start() -web = Web() + async def shutdown(self): + self.stop() + if self.web_server: + try: + await self.web_server.shutdown() + except AttributeError: + pass diff --git a/src/data/bg.json b/src/data/bg.json index 4e56a51..19c9881 100644 --- a/src/data/bg.json +++ b/src/data/bg.json @@ -2,16 +2,16 @@ "game_list": [ { "id": "2", - "icon": "https://fastcdn.hoyoverse.com/static-resource-v2/2023/11/08/9db76fb146f82c045bc276956f86e047_6878380451593228482.png", - "bg": "https://upload-os-bbs.hoyolab.com/upload/2024/07/11/af3fa25fb0cac1959da97b6b867fdb57_6289156149732603194.jpg?x-oss-process=image/auto-orient,0/interlace,1/format,webp/quality,q_70", + "icon": "https://fastcdn.hoyoverse.com/static-resource-v2/2024/08/28/477df7dc8ccb9343da4aea31091c4723_802231292277516724.png", + "bg": "https://upload-os-bbs.hoyolab.com/upload/2024/08/22/2c103142df58ff983fcd3bc47d397c49_3439523162404792698.png?x-oss-process=image/auto-orient,0/interlace,1/format,webp/quality,q_70", "name": "原神", - "bg_color": "#244C74", + "bg_color": "#6E2C14", "focus_channel_id": "30" }, { "id": "6", "icon": "https://hyl-static-res-prod.hoyolab.com/communityweb/business/starrail_hoyoverse.png", - "bg": "https://upload-os-bbs.hoyolab.com/upload/2024/07/31/8981eebc4cd223331a0eedaca9b73f19_928166619611008481.jpg?x-oss-process=image/auto-orient,0/interlace,1/format,webp/quality,q_70", + "bg": "https://upload-os-bbs.hoyolab.com/upload/2024/09/10/a2e1dbea2d1b7af095941428eb233f95_5829553462118520641.jpg?x-oss-process=image/auto-orient,0/interlace,1/format,webp/quality,q_70", "name": "崩坏:星穹铁道", "bg_color": "#101521", "focus_channel_id": "42" @@ -19,7 +19,7 @@ { "id": "8", "icon": "https://hyl-static-res-prod.hoyolab.com/communityweb/business/nap.png", - "bg": "https://upload-os-bbs.hoyolab.com/upload/2024/06/28/1f817e30accd6c117aba10760c52552f_1609948843932447239.jpg?x-oss-process=image/auto-orient,0/interlace,1/format,webp/quality,q_70", + "bg": "https://upload-os-bbs.hoyolab.com/upload/2024/08/13/d0ebd742d2afe541118c14d2d863118e_7814679909409111693.jpg?x-oss-process=image/auto-orient,0/interlace,1/format,webp/quality,q_70", "name": "绝区零", "bg_color": "#F5F6FB", "focus_channel_id": "46" @@ -27,9 +27,9 @@ { "id": "1", "icon": "https://fastcdn.hoyoverse.com/static-resource-v2/2024/02/29/3d96534fd7a35a725f7884e6137346d1_3942255444511793944.png", - "bg": "https://upload-os-bbs.hoyolab.com/upload/2024/07/24/c6e8a685963be61470df222b71e28a3f_2653988210193011390.jpg?x-oss-process=image/auto-orient,0/interlace,1/format,webp/quality,q_70", + "bg": "https://upload-os-bbs.hoyolab.com/upload/2024/09/02/ef9669e2ec4f331407d265aa5d5eb4c1_7138618278434371705.jpg?x-oss-process=image/auto-orient,0/interlace,1/format,webp/quality,q_70", "name": "Honkai Impact 3rd", - "bg_color": "#581312", + "bg_color": "#292180", "focus_channel_id": "0" }, { @@ -42,7 +42,7 @@ }, { "id": "5", - "icon": "https://webstatic.hoyoverse.com/upload/static-resource/2022/08/04/8a31e3d6bce7684556cd45b1e1c309bf_1216320235452608527.png", + "icon": "https://hyl-static-res-prod.hoyolab.com/communityWeb/pgc/business/hyl_icon.png", "bg": "https://upload-os-bbs.hoyolab.com/upload/2024/08/09/1e4a3ac09f82fd7f7702c3ec19dc38fd_7412144515818250508.jpg?x-oss-process=image/auto-orient,0/interlace,1/format,webp/quality,q_70", "name": "HoYoLAB", "bg_color": "#61C1F2", diff --git a/src/plugins/inline.py b/src/plugins/inline.py index a340d10..3d9d4db 100644 --- a/src/plugins/inline.py +++ b/src/plugins/inline.py @@ -1,5 +1,7 @@ from typing import List, Optional +from persica.factory.component import BaseComponent +from pyrogram import Client from pyrogram.types import ( InputTextMessageContent, InlineQueryResultArticle, @@ -9,14 +11,14 @@ from pyrogram.types import ( InlineQueryResultDocument, ) +from src.core.bot import TelegramBot from .start import get_test_button from ..api.bot_request import get_post_info from ..api.models import PostInfo -from ..bot import bot from ..utils.url import get_lab_link -def get_help_article() -> InlineQueryResultArticle: +def get_help_article(bot: Client) -> InlineQueryResultArticle: text = f"欢迎使用 @{bot.me.username} 来转换 米游社/HoYoLab 链接,您也可以将 Bot 添加到群组或频道自动匹配消息。" return InlineQueryResultArticle( title=">> 帮助 <<", @@ -72,10 +74,9 @@ async def add_document_results( return result -@bot.on_inline_query() -async def inline(_, query: InlineQuery): +async def inline(bot: Client, query: InlineQuery): message = query.query - results = [get_help_article()] + results = [get_help_article(bot)] if message: replace_list = get_lab_link(message) if replace_list: @@ -98,3 +99,10 @@ async def inline(_, query: InlineQuery): switch_pm_parameter="start", results=results, ) + + +class InlineBotPlugin(BaseComponent): + def __init__(self, telegram_bot: TelegramBot): + @telegram_bot.bot.on_inline_query() + async def inline_query(_, query: InlineQuery): + await inline(_, query) diff --git a/src/plugins/message.py b/src/plugins/message.py index 1f58ce2..b1e3854 100644 --- a/src/plugins/message.py +++ b/src/plugins/message.py @@ -1,11 +1,12 @@ from asyncio import sleep +from persica.factory.component import BaseComponent from pyrogram import filters from pyrogram.enums import MessageEntityType, ChatType from pyrogram.errors import WebpageNotFound from pyrogram.types import Message, MessageEntity -from src.bot import bot +from src.core.bot import TelegramBot from src.log import logger from src.utils.url import get_lab_link @@ -80,15 +81,6 @@ async def process_link_func(markdown_text: str, message: Message): await sleep(0.5) -@bot.on_message( - filters=filters.incoming - & ~filters.via_bot - & need_text - & need_chat - & ~forward_from_bot - & ~forward_in_group, - group=1, -) async def process_link(_, message: Message): text = message.text or message.caption markdown_text = text.markdown @@ -99,13 +91,6 @@ async def process_link(_, message: Message): await process_link_func(markdown_text, message) -@bot.on_message( - filters=filters.incoming - & filters.command("parse") - & ~filters.forwarded - & ~filters.via_bot - & need_chat, -) async def parse_reply_link(_, message: Message): reply = message.reply_to_message if not reply: @@ -117,3 +102,28 @@ async def parse_reply_link(_, message: Message): if not markdown_text: return await process_link_func(markdown_text, reply) + + +class MessageBotPlugin(BaseComponent): + def __init__(self, telegram_bot: TelegramBot): + @telegram_bot.bot.on_message( + filters=filters.incoming + & ~filters.via_bot + & need_text + & need_chat + & ~forward_from_bot + & ~forward_in_group, + group=1, + ) + async def _process_link(_, message: Message): + await process_link(_, message) + + @telegram_bot.bot.on_message( + filters=filters.incoming + & filters.command("parse") + & ~filters.forwarded + & ~filters.via_bot + & need_chat, + ) + async def _parse_reply_link(_, message: Message): + await parse_reply_link(_, message) diff --git a/src/plugins/start.py b/src/plugins/start.py index 3f613b2..7d42925 100644 --- a/src/plugins/start.py +++ b/src/plugins/start.py @@ -1,12 +1,12 @@ import contextlib -from datetime import datetime, timedelta +from asyncio import sleep +from persica.factory.component import BaseComponent from pyrogram import filters from pyrogram.enums import ChatType from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message -from src.bot import bot -from src.services.scheduler import scheduler +from src.core.bot import TelegramBot HELP_MSG = "此 BOT 将会自动回复可以转换成 Telegram 预览的 URL 链接,可以提供更直观、方便的浏览体验。" TEST_URL = "https://m.miyoushe.com/ys?channel=xiaomi/#/article/51867765" @@ -25,21 +25,6 @@ def get_test_button() -> InlineKeyboardMarkup: ) -async def delete_message(message: Message): - with contextlib.suppress(Exception): - await message.delete() - - -def add_delete_message_task(message: Message): - scheduler.add_job( - delete_message, - "date", - run_date=datetime.now() + timedelta(seconds=60), - args=(message,), - ) - - -@bot.on_message(filters=filters.command("start")) async def start(_, message: "Message"): reply = await message.reply_text( HELP_MSG, @@ -47,4 +32,13 @@ async def start(_, message: "Message"): reply_markup=get_test_button(), ) if message.chat and message.chat.type in [ChatType.GROUP, ChatType.SUPERGROUP]: - add_delete_message_task(reply) + await sleep(30) + with contextlib.suppress(Exception): + await reply.delete() + + +class StartBotPlugin(BaseComponent): + def __init__(self, telegram_bot: TelegramBot): + @telegram_bot.bot.on_message(filters=filters.command("start")) + async def _start(_, message: "Message"): + await start(_, message) diff --git a/src/render/article.py b/src/render/article.py index 9b7b062..1d429fb 100644 --- a/src/render/article.py +++ b/src/render/article.py @@ -18,10 +18,9 @@ from src.api.models import ( clean_url, ) from src.data.get_bg import BG_MAP -from src.env import DOMAIN, MIYOUSHE +from src.env import DOMAIN from src.error import ArticleNotFoundError from src.log import logger -from src.services.scheduler import scheduler RECOMMEND_POST_MAP: Dict[str, List[PostRecommend]] = {} template = template_env.get_template("article.jinja2") @@ -227,17 +226,14 @@ async def process_article(game_id: str, post_id: int, i18n: I18n = I18n()) -> st return content # noqa -if MIYOUSHE: - - @scheduler.scheduled_job("cron", minute="0", second="10") - async def refresh_recommend_posts(): - logger.info("Start to refresh recommend posts") - async with Hyperion() as hyperion: - for key, gids in GAME_ID_MAP.items(): - try: - RECOMMEND_POST_MAP[key] = ( - await hyperion.get_official_recommended_posts(gids) - ) - except Exception as _: - logger.exception(f"Failed to get recommend posts gids={gids}") - logger.info("Finish to refresh recommend posts") +async def refresh_recommend_posts(): + logger.info("Start to refresh recommend posts") + async with Hyperion() as hyperion: + for key, gids in GAME_ID_MAP.items(): + try: + RECOMMEND_POST_MAP[key] = await hyperion.get_official_recommended_posts( + gids + ) + except Exception as _: + logger.exception(f"Failed to get recommend posts gids={gids}") + logger.info("Finish to refresh recommend posts") diff --git a/src/render/article_hoyolab.py b/src/render/article_hoyolab.py index 09fcfb5..6a5f3ed 100644 --- a/src/render/article_hoyolab.py +++ b/src/render/article_hoyolab.py @@ -4,7 +4,6 @@ from typing import Dict, List, Callable from src.api.hoyolab import Hoyolab from src.api.i18n import I18n, i18n_alias from src.api.models import PostRecommend, PostType, PostInfo -from src.env import HOYOLAB from src.log import logger from src.render.article import ( process_article_text, @@ -12,7 +11,6 @@ from src.render.article import ( template, get_public_data, ) -from src.services.scheduler import scheduler GAME_ID_MAP = {"bh3": 1, "ys": 2, "wd": 4, "dby": 5, "sr": 6, "zzz": 8} RECOMMEND_POST_MAP: Dict[int, List[PostRecommend]] = {} @@ -75,20 +73,17 @@ async def process_article(post_id: int, lang: str) -> str: return content # noqa -if HOYOLAB: - - @scheduler.scheduled_job("cron", minute="0", second="10") - async def refresh_hoyo_recommend_posts(): - logger.info("Start to refresh hoyolab recommend posts") - async with Hoyolab() as hoyolab: - for gids in GAME_ID_MAP.values(): - temp = [] - for k in (1, 2, 3): - try: - temp.extend(await hoyolab.get_news_recommend(gids, type_=k)) - except Exception as _: - logger.exception( - f"Failed to get recommend posts gids={gids} type={k}" - ) - RECOMMEND_POST_MAP[gids] = temp - logger.info("Finish to refresh hoyolab recommend posts") +async def refresh_hoyo_recommend_posts(): + logger.info("Start to refresh hoyolab recommend posts") + async with Hoyolab() as hoyolab: + for gids in GAME_ID_MAP.values(): + temp = [] + for k in (1, 2, 3): + try: + temp.extend(await hoyolab.get_news_recommend(gids, type_=k)) + except Exception as _: + logger.exception( + f"Failed to get recommend posts gids={gids} type={k}" + ) + RECOMMEND_POST_MAP[gids] = temp + logger.info("Finish to refresh hoyolab recommend posts") diff --git a/src/route/__init__.py b/src/route/__init__.py index a23881b..e69de29 100644 --- a/src/route/__init__.py +++ b/src/route/__init__.py @@ -1,18 +0,0 @@ -from src.env import MIYOUSHE, HOYOLAB - - -def get_routes(): - from .error import validation_exception_handler - - routes = [ - validation_exception_handler, - ] - - if MIYOUSHE: - from .article import parse_article - - routes.append(parse_article) - if HOYOLAB: - from .article_hoyolab import parse_hoyo_article - - routes.append(parse_hoyo_article) diff --git a/src/route/article.py b/src/route/article.py index 62e8903..bd5e38d 100644 --- a/src/route/article.py +++ b/src/route/article.py @@ -1,39 +1,57 @@ +from persica.factory.component import AsyncInitializingComponent from starlette.requests import Request from starlette.responses import HTMLResponse from .base import get_redirect_response -from ..app import app -from ..error import ArticleError, ResponseException -from ..log import logger -from ..render.article import process_article, get_post_info + +from src.core.web_app import WebApp +from src.core.scheduler import TimeScheduler +from src.env import MIYOUSHE +from src.error import ArticleError, ResponseException +from src.log import logger +from src.render.article import process_article, get_post_info, refresh_recommend_posts -@app.get("/{game_id}/article/{post_id}") -async def parse_article(game_id: str, post_id: int, request: Request): - try: - return HTMLResponse(await process_article(game_id, post_id)) - except ResponseException as e: - logger.warning(e.message) - return get_redirect_response(request) - except ArticleError as e: - logger.warning(e.msg) - return get_redirect_response(request) - except Exception as _: - logger.exception( - "Failed to get article game_id[%s] post_id[%s]", game_id, post_id +class ArticlePlugin(AsyncInitializingComponent): + def __init__(self, web_app: WebApp, sche: TimeScheduler): + if not MIYOUSHE: + return + web_app.app.add_api_route("/{game_id}/article/{post_id}", self.parse_article) + web_app.app.add_api_route( + "/{game_id}/article/{post_id}/json", self.parse_article_json ) - return get_redirect_response(request) + sche.scheduler.add_job(refresh_recommend_posts, "cron", minute="0", second="10") + async def initialize(self): + if not MIYOUSHE: + return + await refresh_recommend_posts() -@app.get("/{game_id}/article/{post_id}/json") -async def parse_article_json(game_id: str, post_id: int, request: Request): - try: - return await get_post_info(game_id, post_id) - except ArticleError as e: - logger.warning(e.msg) - return get_redirect_response(request) - except Exception as _: - logger.exception( - "Failed to get article game_id[%s] post_id[%s]", game_id, post_id - ) - return get_redirect_response(request) + @staticmethod + async def parse_article(game_id: str, post_id: int, request: Request): + try: + return HTMLResponse(await process_article(game_id, post_id)) + except ResponseException as e: + logger.warning(e.message) + return get_redirect_response(request) + except ArticleError as e: + logger.warning(e.msg) + return get_redirect_response(request) + except Exception as _: + logger.exception( + "Failed to get article game_id[%s] post_id[%s]", game_id, post_id + ) + return get_redirect_response(request) + + @staticmethod + async def parse_article_json(game_id: str, post_id: int, request: Request): + try: + return await get_post_info(game_id, post_id) + except ArticleError as e: + logger.warning(e.msg) + return get_redirect_response(request) + except Exception as _: + logger.exception( + "Failed to get article game_id[%s] post_id[%s]", game_id, post_id + ) + return get_redirect_response(request) diff --git a/src/route/article_hoyolab.py b/src/route/article_hoyolab.py index edc39af..4c037b7 100644 --- a/src/route/article_hoyolab.py +++ b/src/route/article_hoyolab.py @@ -1,26 +1,48 @@ +from persica.factory.component import AsyncInitializingComponent from starlette.requests import Request from starlette.responses import HTMLResponse from .base import get_redirect_response -from ..app import app -from ..error import ArticleError, ResponseException -from ..log import logger -from ..render.article_hoyolab import process_article, get_post_info + +from src.core.web_app import WebApp +from src.core.scheduler import TimeScheduler +from src.env import HOYOLAB +from src.error import ArticleError, ResponseException +from src.log import logger +from src.render.article_hoyolab import ( + process_article, + get_post_info, + refresh_hoyo_recommend_posts, +) -@app.get("/article/{post_id}") -@app.get("/article/{post_id}/{lang}") -async def parse_hoyo_article(post_id: int, request: Request, lang: str = "zh-cn"): - try: - if lang == "json": - return await get_post_info(post_id, "zh-cn") - return HTMLResponse(await process_article(post_id, lang)) - except ResponseException as e: - logger.warning(e.message) - return get_redirect_response(request) - except ArticleError as e: - logger.warning(e.msg) - return get_redirect_response(request) - except Exception as _: - logger.exception(f"Failed to get article {post_id} lang {lang}") - return get_redirect_response(request) +class ArticleHoYoPlugin(AsyncInitializingComponent): + def __init__(self, web_app: WebApp, sche: TimeScheduler): + if not HOYOLAB: + return + web_app.app.add_api_route("/article/{post_id}", self.parse_hoyo_article) + web_app.app.add_api_route("/article/{post_id}/{lang}", self.parse_hoyo_article) + sche.scheduler.add_job( + refresh_hoyo_recommend_posts, "cron", minute="0", second="20" + ) + + async def initialize(self): + if not HOYOLAB: + return + await refresh_hoyo_recommend_posts() + + @staticmethod + async def parse_hoyo_article(post_id: int, request: Request, lang: str = "zh-cn"): + try: + if lang == "json": + return await get_post_info(post_id, "zh-cn") + return HTMLResponse(await process_article(post_id, lang)) + except ResponseException as e: + logger.warning(e.message) + return get_redirect_response(request) + except ArticleError as e: + logger.warning(e.msg) + return get_redirect_response(request) + except Exception as _: + logger.exception(f"Failed to get article {post_id} lang {lang}") + return get_redirect_response(request) diff --git a/src/route/base.py b/src/route/base.py index bc3ffb3..0f5e1a0 100644 --- a/src/route/base.py +++ b/src/route/base.py @@ -1,9 +1,11 @@ from typing import TYPE_CHECKING +from persica.factory.component import BaseComponent from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import RedirectResponse -from src.env import MIYOUSHE +from src.core.web_app import WebApp +from src.env import MIYOUSHE, DEBUG if TYPE_CHECKING: from starlette.middleware.base import RequestResponseEndpoint @@ -26,3 +28,9 @@ class UserAgentMiddleware(BaseHTTPMiddleware): if (not user_agent) or ("telegram" not in user_agent.lower()): return get_redirect_response(request) return await call_next(request) + + +class BaseRoutePlugin(BaseComponent): + def __init__(self, web_app: WebApp): + if not DEBUG: + web_app.app.add_middleware(UserAgentMiddleware) diff --git a/src/route/error.py b/src/route/error.py index 4fc8492..a41ea15 100644 --- a/src/route/error.py +++ b/src/route/error.py @@ -1,11 +1,18 @@ from fastapi.exceptions import RequestValidationError +from persica.factory.component import BaseComponent from starlette.requests import Request -from src.app import app +from src.core.web_app import WebApp from src.route.base import get_redirect_response -@app.exception_handler(RequestValidationError) -@app.exception_handler(404) -async def validation_exception_handler(request: "Request", _): - return get_redirect_response(request) +class ErrorRoutePlugin(BaseComponent): + def __init__(self, web_app: WebApp): + web_app.app.add_exception_handler( + RequestValidationError, self.validation_exception_handler + ) + web_app.app.add_exception_handler(404, self.validation_exception_handler) + + @staticmethod + async def validation_exception_handler(request: "Request", _): + return get_redirect_response(request) diff --git a/src/services/scheduler.py b/src/services/scheduler.py deleted file mode 100644 index fe65fb8..0000000 --- a/src/services/scheduler.py +++ /dev/null @@ -1,10 +0,0 @@ -from apscheduler.schedulers.asyncio import AsyncIOScheduler -from fastapi import FastAPI - -scheduler = AsyncIOScheduler(timezone="Asia/ShangHai") - - -def register_scheduler(app: "FastAPI"): - @app.on_event("startup") - async def start_event(): - scheduler.start() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29