diff --git a/alembic.ini b/alembic.ini
new file mode 100644
index 0000000..50bbff9
--- /dev/null
+++ b/alembic.ini
@@ -0,0 +1,109 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts
+script_location = src/plugins/nonebot_bison/config/migrate
+
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# sys.path path, will be prepended to sys.path if present.
+# defaults to the current working directory.
+prepend_sys_path = ./src/plugins
+
+# timezone to use when rendering the date within the migration file
+# as well as the filename.
+# If specified, requires the python-dateutil library that can be
+# installed by adding `alembic[tz]` to the pip requirements
+# string value is passed to dateutil.tz.gettz()
+# leave blank for localtime
+# timezone =
+
+# max length of characters to apply to the
+# "slug" field
+# truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+# version location specification; This defaults
+# to src/plugins/nonebot_bison/config/migrate/versions. When using multiple version
+# directories, initial revisions must be specified with --version-path.
+# The path separator used here should be the separator specified by "version_path_separator" below.
+# version_locations = %(here)s/bar:%(here)s/bat:src/plugins/nonebot_bison/config/migrate/versions
+
+# version path separator; As mentioned above, this is the character used to split
+# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
+# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
+# Valid values for version_path_separator are:
+#
+# version_path_separator = :
+# version_path_separator = ;
+# version_path_separator = space
+version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
+
+# the output encoding used when revision files
+# are written from script.py.mako
+# output_encoding = utf-8
+
+sqlalchemy.url = sqlite:///data/data.db
+
+
+[post_write_hooks]
+# post_write_hooks defines scripts or Python functions that are run
+# on newly generated revision scripts. See the documentation for further
+# detail and examples
+
+# format using "black" - use the console_scripts runner, against the "black" entrypoint
+# hooks = black
+# black.type = console_scripts
+# black.entrypoint = black
+# black.options = -l 79 REVISION_SCRIPT_FILENAME
+
+hooks = pre-commit
+
+pre-commit.type = console_scripts
+pre-commit.entrypoint = pre-commit
+pre-commit.options = run --files REVISION_SCRIPT_FILENAME
+pre-commit.cwd = %(here)s
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/poetry.lock b/poetry.lock
index 8b4b4eb..47df52e 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -6,6 +6,32 @@ category = "main"
optional = false
python-versions = ">=3.6,<4.0"
+[[package]]
+name = "aiosqlite"
+version = "0.17.0"
+description = "asyncio bridge to the standard sqlite3 module"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+typing_extensions = ">=3.7.2"
+
+[[package]]
+name = "alembic"
+version = "1.8.0"
+description = "A database migration tool for SQLAlchemy."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+Mako = "*"
+SQLAlchemy = ">=1.3.0"
+
+[package.extras]
+tz = ["python-dateutil"]
+
[[package]]
name = "anyio"
version = "3.6.1"
@@ -203,11 +229,11 @@ beautifulsoup4 = "*"
[[package]]
name = "certifi"
-version = "2021.10.8"
+version = "2022.5.18.1"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
-python-versions = "*"
+python-versions = ">=3.6"
[[package]]
name = "cfgv"
@@ -275,14 +301,14 @@ six = ">=1.10"
[[package]]
name = "coverage"
-version = "6.4"
+version = "6.4.1"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
-tomli = {version = "*", optional = true, markers = "python_version < \"3.11\" and extra == \"toml\""}
+tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras]
toml = ["tomli"]
@@ -324,7 +350,7 @@ tests = ["dill", "coverage", "coveralls", "mock", "nose"]
[[package]]
name = "fastapi"
-version = "0.73.0"
+version = "0.78.0"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
category = "main"
optional = false
@@ -332,17 +358,17 @@ python-versions = ">=3.6.1"
[package.dependencies]
pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
-starlette = "0.17.1"
+starlette = "0.19.1"
[package.extras]
-all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "ujson (>=4.0.1,<5.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"]
-dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"]
-doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"]
-test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==21.9b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"]
+all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
+dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)", "pre-commit (>=2.17.0,<3.0.0)"]
+doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer (>=0.4.1,<0.5.0)", "pyyaml (>=5.3.1,<7.0.0)"]
+test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==22.3.0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==4.2.1)", "types-orjson (==3.6.2)", "types-dataclasses (==0.6.5)"]
[[package]]
name = "feedparser"
-version = "6.0.8"
+version = "6.0.10"
description = "Universal feed parser, handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds"
category = "main"
optional = false
@@ -353,7 +379,7 @@ sgmllib3k = "*"
[[package]]
name = "filelock"
-version = "3.7.0"
+version = "3.7.1"
description = "A platform independent file lock."
category = "dev"
optional = false
@@ -390,6 +416,26 @@ category = "main"
optional = false
python-versions = ">=3.6"
+[[package]]
+name = "h2"
+version = "4.1.0"
+description = "HTTP/2 State-Machine based protocol implementation"
+category = "main"
+optional = false
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+hpack = ">=4.0,<5"
+hyperframe = ">=6.0,<7"
+
+[[package]]
+name = "hpack"
+version = "4.0.0"
+description = "Pure-Python HPACK header compression"
+category = "main"
+optional = false
+python-versions = ">=3.6.1"
+
[[package]]
name = "httpcore"
version = "0.15.0"
@@ -429,6 +475,7 @@ python-versions = ">=3.7"
[package.dependencies]
certifi = "*"
+h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""}
httpcore = ">=0.15.0,<0.16.0"
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*"
@@ -439,9 +486,17 @@ cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (>=1.0.0,<2.0.0)"]
+[[package]]
+name = "hyperframe"
+version = "6.0.1"
+description = "HTTP/2 framing layer for Python"
+category = "main"
+optional = false
+python-versions = ">=3.6.1"
+
[[package]]
name = "identify"
-version = "2.5.0"
+version = "2.5.1"
description = "File identification library for Python"
category = "dev"
optional = false
@@ -481,7 +536,7 @@ toml = {version = ">=0.10.2", markers = "python_version > \"3.6\""}
[[package]]
name = "ipython"
-version = "8.3.0"
+version = "8.4.0"
description = "IPython: Productive Interactive Computing"
category = "dev"
optional = false
@@ -584,6 +639,22 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "black (>=19.10b0)", "isort (>=5.1.1)", "Sphinx (>=4.1.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)"]
+[[package]]
+name = "mako"
+version = "1.2.0"
+description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+MarkupSafe = ">=0.9.2"
+
+[package.extras]
+babel = ["babel"]
+lingua = ["lingua"]
+testing = ["pytest"]
+
[[package]]
name = "markdown"
version = "3.3.7"
@@ -614,6 +685,14 @@ python-versions = ">=3.5"
[package.dependencies]
traitlets = "*"
+[[package]]
+name = "msgpack"
+version = "1.0.4"
+description = "MessagePack serializer"
+category = "main"
+optional = false
+python-versions = "*"
+
[[package]]
name = "multidict"
version = "6.0.2"
@@ -622,6 +701,24 @@ category = "main"
optional = false
python-versions = ">=3.7"
+[[package]]
+name = "mypy"
+version = "0.960"
+description = "Optional static typing for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+mypy-extensions = ">=0.4.3"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = ">=3.10"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+python2 = ["typed-ast (>=1.4.0,<2)"]
+reports = ["lxml"]
+
[[package]]
name = "mypy-extensions"
version = "0.4.3"
@@ -632,7 +729,7 @@ python-versions = "*"
[[package]]
name = "nb-cli"
-version = "0.6.6"
+version = "0.6.7"
description = "CLI for nonebot2"
category = "dev"
optional = false
@@ -646,7 +743,7 @@ httpx = ">=0.18.0,<1.0.0"
nonebot2 = ">=2.0.0-beta.1,<3.0.0"
prompt-toolkit = ">=3.0.19,<4.0.0"
pyfiglet = ">=0.8.post1,<0.9"
-tomlkit = ">=0.9.0,<0.10.0"
+tomlkit = ">=0.10.0,<0.11.0"
wcwidth = ">=0.2.5,<0.3.0"
[package.extras]
@@ -663,28 +760,43 @@ python-versions = "*"
[[package]]
name = "nonebot-adapter-onebot"
-version = "2.0.0b1"
+version = "2.1.0"
description = "OneBot(CQHTTP) adapter for nonebot2"
category = "main"
optional = false
python-versions = ">=3.7.3,<4.0.0"
[package.dependencies]
-nonebot2 = ">=2.0.0-beta.1,<3.0.0"
+msgpack = ">=1.0.3,<2.0.0"
+nonebot2 = ">=2.0.0-beta.3,<3.0.0"
+
+[[package]]
+name = "nonebot-plugin-datastore"
+version = "0.3.1"
+description = "适用于 Nonebot2 的数据存储插件"
+category = "main"
+optional = false
+python-versions = ">=3.7.3,<4.0.0"
+
+[package.dependencies]
+aiosqlite = ">=0.17.0,<0.18.0"
+nonebot-plugin-localstore = ">=0.1.0,<0.2.0"
+nonebot2 = {version = ">=2.0.0-beta.1,<3.0.0", extras = ["httpx"]}
+SQLAlchemy = ">=1.4,<1.4.36"
+sqlmodel = ">=0.0.6,<0.0.7"
[[package]]
name = "nonebot-plugin-htmlrender"
-version = "0.0.4.6"
+version = "0.0.4.8"
description = "通过浏览器渲染图片"
category = "main"
optional = false
-python-versions = ">=3.7.3,<4.0"
+python-versions = ">=3.7.3,<4.0.0"
[package.dependencies]
aiofiles = ">=0.8.0,<0.9.0"
jinja2 = ">=3.0.3,<4.0.0"
markdown = ">=3.3.6,<4.0.0"
-nonebot-adapter-onebot = ">=2.0.0-beta.1,<3.0.0"
nonebot2 = ">=2.0.0-beta.1,<3.0.0"
playwright = ">=1.17.2,<2.0.0"
Pygments = ">=2.10.0,<3.0.0"
@@ -692,27 +804,39 @@ pymdown-extensions = ">=9.1,<10.0"
python-markdown-math = ">=0.8,<0.9"
uvicorn = ">=0.17.6,<0.18.0"
+[[package]]
+name = "nonebot-plugin-localstore"
+version = "0.1.0"
+description = "Local Storage Support for NoneBot2"
+category = "main"
+optional = false
+python-versions = ">=3.7.3,<4.0.0"
+
+[package.dependencies]
+nonebot2 = ">=2.0.0-alpha.8,<3.0.0"
+
[[package]]
name = "nonebot2"
-version = "2.0.0b2"
+version = "2.0.0b3"
description = "An asynchronous python bot framework."
category = "main"
optional = false
python-versions = ">=3.7.3,<4.0.0"
[package.dependencies]
-fastapi = ">=0.73.0,<0.74.0"
+fastapi = ">=0.78.0,<0.79.0"
+httpx = {version = ">=0.20.0,<1.0.0", extras = ["http2"], optional = true, markers = "extra == \"httpx\" or extra == \"all\""}
loguru = ">=0.6.0,<0.7.0"
pydantic = {version = ">=1.9.0,<1.10.0", extras = ["dotenv"]}
pygtrie = ">=2.4.1,<3.0.0"
-tomlkit = ">=0.9.0,<0.10.0"
+tomlkit = ">=0.10.0,<0.11.0"
typing-extensions = ">=3.10.0,<5.0.0"
uvicorn = {version = ">=0.17.0,<0.18.0", extras = ["standard"]}
yarl = ">=1.7.2,<2.0.0"
[package.extras]
-quart = ["Quart (>=0.16.0,<0.17.0)"]
-all = ["Quart (>=0.16.0,<0.17.0)", "websockets (>=10.0,<11.0)", "aiohttp[speedups] (>=3.7.4,<4.0.0)", "httpx[http2] (>=0.20.0,<1.0.0)"]
+quart = ["Quart (>=0.17.0,<0.18.0)"]
+all = ["Quart (>=0.17.0,<0.18.0)", "websockets (>=10.0,<11.0)", "aiohttp[speedups] (>=3.7.4,<4.0.0)", "httpx[http2] (>=0.20.0,<1.0.0)"]
websockets = ["websockets (>=10.0,<11.0)"]
aiohttp = ["aiohttp[speedups] (>=3.7.4,<4.0.0)"]
httpx = ["httpx[http2] (>=0.20.0,<1.0.0)"]
@@ -791,12 +915,16 @@ python-versions = "*"
[[package]]
name = "pillow"
-version = "9.0.1"
+version = "9.1.1"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
python-versions = ">=3.7"
+[package.extras]
+docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"]
+tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
+
[[package]]
name = "platformdirs"
version = "2.5.2"
@@ -898,8 +1026,8 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pydantic"
-version = "1.9.0"
-description = "Data validation and settings management using python 3.6 type hinting"
+version = "1.9.1"
+description = "Data validation and settings management using python type hints"
category = "main"
optional = false
python-versions = ">=3.6.1"
@@ -1207,6 +1335,74 @@ category = "main"
optional = false
python-versions = ">=3.6"
+[[package]]
+name = "sqlalchemy"
+version = "1.4.35"
+description = "Database Abstraction Library"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+
+[package.dependencies]
+greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
+
+[package.extras]
+aiomysql = ["greenlet (!=0.4.17)", "aiomysql"]
+aiosqlite = ["typing_extensions (!=3.10.0.1)", "greenlet (!=0.4.17)", "aiosqlite"]
+asyncio = ["greenlet (!=0.4.17)"]
+asyncmy = ["greenlet (!=0.4.17)", "asyncmy (>=0.2.3,!=0.2.4)"]
+mariadb_connector = ["mariadb (>=1.0.1)"]
+mssql = ["pyodbc"]
+mssql_pymssql = ["pymssql"]
+mssql_pyodbc = ["pyodbc"]
+mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"]
+mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"]
+mysql_connector = ["mysql-connector-python"]
+oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"]
+postgresql = ["psycopg2 (>=2.7)"]
+postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"]
+postgresql_pg8000 = ["pg8000 (>=1.16.6)"]
+postgresql_psycopg2binary = ["psycopg2-binary"]
+postgresql_psycopg2cffi = ["psycopg2cffi"]
+pymysql = ["pymysql (<1)", "pymysql"]
+sqlcipher = ["sqlcipher3-binary"]
+
+[[package]]
+name = "sqlalchemy-stubs"
+version = "0.4"
+description = "SQLAlchemy stubs and mypy plugin"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+mypy = ">=0.790"
+typing-extensions = ">=3.7.4"
+
+[[package]]
+name = "sqlalchemy2-stubs"
+version = "0.0.2a23"
+description = "Typing Stubs for SQLAlchemy 1.4"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+typing-extensions = ">=3.7.4"
+
+[[package]]
+name = "sqlmodel"
+version = "0.0.6"
+description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness."
+category = "main"
+optional = false
+python-versions = ">=3.6.1,<4.0.0"
+
+[package.dependencies]
+pydantic = ">=1.8.2,<2.0.0"
+SQLAlchemy = ">=1.4.17,<1.5.0"
+sqlalchemy2-stubs = "*"
+
[[package]]
name = "stack-data"
version = "0.2.0"
@@ -1225,14 +1421,14 @@ tests = ["pytest", "typeguard", "pygments", "littleutils", "cython"]
[[package]]
name = "starlette"
-version = "0.17.1"
+version = "0.19.1"
description = "The little ASGI library that shines."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
-anyio = ">=3.0.0,<4"
+anyio = ">=3.4.0,<5"
[package.extras]
full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
@@ -1271,7 +1467,7 @@ python-versions = ">=3.7"
[[package]]
name = "tomlkit"
-version = "0.9.2"
+version = "0.10.2"
description = "Style preserving TOML library"
category = "main"
optional = false
@@ -1279,7 +1475,7 @@ python-versions = ">=3.6,<4.0"
[[package]]
name = "traitlets"
-version = "5.2.1.post0"
+version = "5.2.2.post1"
description = ""
category = "dev"
optional = false
@@ -1440,13 +1636,21 @@ multidict = ">=4.0"
[metadata]
lock-version = "1.1"
python-versions = ">=3.10,<4.0.0"
-content-hash = "28457eb74ad24dd15a02b512fa3ae09a37be3ec85770465e6e6143c88d8c32fd"
+content-hash = "3af9d9634c376c91d54c493b0cb4a89358b1a4f34f3d6154e79051f9a6829ad6"
[metadata.files]
aiofiles = [
{file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"},
{file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"},
]
+aiosqlite = [
+ {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"},
+ {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"},
+]
+alembic = [
+ {file = "alembic-1.8.0-py3-none-any.whl", hash = "sha256:b5ae4bbfc7d1302ed413989d39474d102e7cfa158f6d5969d2497955ffe85a30"},
+ {file = "alembic-1.8.0.tar.gz", hash = "sha256:a2d4d90da70b30e70352cd9455e35873a255a31402a438fe24815758d7a0e5e1"},
+]
anyio = [
{file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
{file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
@@ -1527,8 +1731,8 @@ bs4 = [
{file = "bs4-0.0.1.tar.gz", hash = "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"},
]
certifi = [
- {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
- {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
+ {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"},
+ {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"},
]
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
@@ -1555,47 +1759,47 @@ cookiecutter = [
{file = "cookiecutter-1.7.3.tar.gz", hash = "sha256:6b9a4d72882e243be077a7397d0f1f76fe66cf3df91f3115dbb5330e214fa457"},
]
coverage = [
- {file = "coverage-6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50ed480b798febce113709846b11f5d5ed1e529c88d8ae92f707806c50297abf"},
- {file = "coverage-6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26f8f92699756cb7af2b30720de0c5bb8d028e923a95b6d0c891088025a1ac8f"},
- {file = "coverage-6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60c2147921da7f4d2d04f570e1838db32b95c5509d248f3fe6417e91437eaf41"},
- {file = "coverage-6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:750e13834b597eeb8ae6e72aa58d1d831b96beec5ad1d04479ae3772373a8088"},
- {file = "coverage-6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af5b9ee0fc146e907aa0f5fb858c3b3da9199d78b7bb2c9973d95550bd40f701"},
- {file = "coverage-6.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a022394996419142b33a0cf7274cb444c01d2bb123727c4bb0b9acabcb515dea"},
- {file = "coverage-6.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5a78cf2c43b13aa6b56003707c5203f28585944c277c1f3f109c7b041b16bd39"},
- {file = "coverage-6.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9229d074e097f21dfe0643d9d0140ee7433814b3f0fc3706b4abffd1e3038632"},
- {file = "coverage-6.4-cp310-cp310-win32.whl", hash = "sha256:fb45fe08e1abc64eb836d187b20a59172053999823f7f6ef4f18a819c44ba16f"},
- {file = "coverage-6.4-cp310-cp310-win_amd64.whl", hash = "sha256:3cfd07c5889ddb96a401449109a8b97a165be9d67077df6802f59708bfb07720"},
- {file = "coverage-6.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:03014a74023abaf5a591eeeaf1ac66a73d54eba178ff4cb1fa0c0a44aae70383"},
- {file = "coverage-6.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c82f2cd69c71698152e943f4a5a6b83a3ab1db73b88f6e769fabc86074c3b08"},
- {file = "coverage-6.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b546cf2b1974ddc2cb222a109b37c6ed1778b9be7e6b0c0bc0cf0438d9e45a6"},
- {file = "coverage-6.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc173f1ce9ffb16b299f51c9ce53f66a62f4d975abe5640e976904066f3c835d"},
- {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c53ad261dfc8695062fc8811ac7c162bd6096a05a19f26097f411bdf5747aee7"},
- {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:eef5292b60b6de753d6e7f2d128d5841c7915fb1e3321c3a1fe6acfe76c38052"},
- {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:543e172ce4c0de533fa892034cce260467b213c0ea8e39da2f65f9a477425211"},
- {file = "coverage-6.4-cp37-cp37m-win32.whl", hash = "sha256:00c8544510f3c98476bbd58201ac2b150ffbcce46a8c3e4fb89ebf01998f806a"},
- {file = "coverage-6.4-cp37-cp37m-win_amd64.whl", hash = "sha256:b84ab65444dcc68d761e95d4d70f3cfd347ceca5a029f2ffec37d4f124f61311"},
- {file = "coverage-6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d548edacbf16a8276af13063a2b0669d58bbcfca7c55a255f84aac2870786a61"},
- {file = "coverage-6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:033ebec282793bd9eb988d0271c211e58442c31077976c19c442e24d827d356f"},
- {file = "coverage-6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742fb8b43835078dd7496c3c25a1ec8d15351df49fb0037bffb4754291ef30ce"},
- {file = "coverage-6.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55fae115ef9f67934e9f1103c9ba826b4c690e4c5bcf94482b8b2398311bf9c"},
- {file = "coverage-6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd698341626f3c77784858427bad0cdd54a713115b423d22ac83a28303d1d95"},
- {file = "coverage-6.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:62d382f7d77eeeaff14b30516b17bcbe80f645f5cf02bb755baac376591c653c"},
- {file = "coverage-6.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:016d7f5cf1c8c84f533a3c1f8f36126fbe00b2ec0ccca47cc5731c3723d327c6"},
- {file = "coverage-6.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:69432946f154c6add0e9ede03cc43b96e2ef2733110a77444823c053b1ff5166"},
- {file = "coverage-6.4-cp38-cp38-win32.whl", hash = "sha256:83bd142cdec5e4a5c4ca1d4ff6fa807d28460f9db919f9f6a31babaaa8b88426"},
- {file = "coverage-6.4-cp38-cp38-win_amd64.whl", hash = "sha256:4002f9e8c1f286e986fe96ec58742b93484195defc01d5cc7809b8f7acb5ece3"},
- {file = "coverage-6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e4f52c272fdc82e7c65ff3f17a7179bc5f710ebc8ce8a5cadac81215e8326740"},
- {file = "coverage-6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b5578efe4038be02d76c344007b13119b2b20acd009a88dde8adec2de4f630b5"},
- {file = "coverage-6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8099ea680201c2221f8468c372198ceba9338a5fec0e940111962b03b3f716a"},
- {file = "coverage-6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a00441f5ea4504f5abbc047589d09e0dc33eb447dc45a1a527c8b74bfdd32c65"},
- {file = "coverage-6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e76bd16f0e31bc2b07e0fb1379551fcd40daf8cdf7e24f31a29e442878a827c"},
- {file = "coverage-6.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8d2e80dd3438e93b19e1223a9850fa65425e77f2607a364b6fd134fcd52dc9df"},
- {file = "coverage-6.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:341e9c2008c481c5c72d0e0dbf64980a4b2238631a7f9780b0fe2e95755fb018"},
- {file = "coverage-6.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:21e6686a95025927775ac501e74f5940cdf6fe052292f3a3f7349b0abae6d00f"},
- {file = "coverage-6.4-cp39-cp39-win32.whl", hash = "sha256:968ed5407f9460bd5a591cefd1388cc00a8f5099de9e76234655ae48cfdbe2c3"},
- {file = "coverage-6.4-cp39-cp39-win_amd64.whl", hash = "sha256:e35217031e4b534b09f9b9a5841b9344a30a6357627761d4218818b865d45055"},
- {file = "coverage-6.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:e637ae0b7b481905358624ef2e81d7fb0b1af55f5ff99f9ba05442a444b11e45"},
- {file = "coverage-6.4.tar.gz", hash = "sha256:727dafd7f67a6e1cad808dc884bd9c5a2f6ef1f8f6d2f22b37b96cb0080d4f49"},
+ {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"},
+ {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"},
+ {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"},
+ {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"},
+ {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"},
+ {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"},
+ {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"},
+ {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"},
+ {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"},
+ {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"},
+ {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"},
+ {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"},
+ {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"},
+ {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"},
+ {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"},
+ {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"},
+ {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"},
+ {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"},
+ {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"},
+ {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"},
+ {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"},
+ {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"},
+ {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"},
+ {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"},
+ {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"},
+ {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"},
+ {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"},
+ {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"},
+ {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"},
+ {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"},
+ {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"},
+ {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"},
+ {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"},
+ {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"},
+ {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"},
+ {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"},
+ {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"},
+ {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"},
+ {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"},
+ {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"},
+ {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"},
]
decorator = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
@@ -1613,16 +1817,16 @@ expiringdict = [
{file = "expiringdict-1.2.1.tar.gz", hash = "sha256:fe2ba427220425c3c8a3d29f6d2e2985bcee323f8bcd4021e68ebefbd90d8250"},
]
fastapi = [
- {file = "fastapi-0.73.0-py3-none-any.whl", hash = "sha256:f0a618aff5f6942862f2d3f20f39b1c037e33314d1b8207fd1c3a2cca76dfd8c"},
- {file = "fastapi-0.73.0.tar.gz", hash = "sha256:dcfee92a7f9a72b5d4b7ca364bd2b009f8fc10d95ed5769be20e94f39f7e5a15"},
+ {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
+ {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},
]
feedparser = [
- {file = "feedparser-6.0.8-py3-none-any.whl", hash = "sha256:1b7f57841d9cf85074deb316ed2c795091a238adb79846bc46dccdaf80f9c59a"},
- {file = "feedparser-6.0.8.tar.gz", hash = "sha256:5ce0410a05ab248c8c7cfca3a0ea2203968ee9ff4486067379af4827a59f9661"},
+ {file = "feedparser-6.0.10-py3-none-any.whl", hash = "sha256:79c257d526d13b944e965f6095700587f27388e50ea16fd245babe4dfae7024f"},
+ {file = "feedparser-6.0.10.tar.gz", hash = "sha256:27da485f4637ce7163cdeab13a80312b93b7d0c1b775bef4a47629a3110bca51"},
]
filelock = [
- {file = "filelock-3.7.0-py3-none-any.whl", hash = "sha256:c7b5fdb219b398a5b28c8e4c1893ef5f98ece6a38c6ab2c22e26ec161556fed6"},
- {file = "filelock-3.7.0.tar.gz", hash = "sha256:b795f1b42a61bbf8ec7113c341dad679d772567b936fbd1bf43c9a238e673e20"},
+ {file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"},
+ {file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"},
]
flaky = [
{file = "flaky-3.7.0-py2.py3-none-any.whl", hash = "sha256:d6eda73cab5ae7364504b7c44670f70abed9e75f77dd116352f662817592ec9c"},
@@ -1640,7 +1844,6 @@ greenlet = [
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"},
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"},
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"},
- {file = "greenlet-1.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965"},
{file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"},
{file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"},
{file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"},
@@ -1653,7 +1856,6 @@ greenlet = [
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"},
- {file = "greenlet-1.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f"},
{file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"},
{file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"},
{file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"},
@@ -1662,7 +1864,6 @@ greenlet = [
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"},
- {file = "greenlet-1.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe"},
{file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"},
{file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"},
{file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"},
@@ -1671,7 +1872,6 @@ greenlet = [
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"},
- {file = "greenlet-1.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2"},
{file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"},
{file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"},
{file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"},
@@ -1680,7 +1880,6 @@ greenlet = [
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"},
- {file = "greenlet-1.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3"},
{file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"},
{file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"},
{file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"},
@@ -1689,6 +1888,14 @@ h11 = [
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
]
+h2 = [
+ {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
+ {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
+]
+hpack = [
+ {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
+ {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
+]
httpcore = [
{file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"},
{file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"},
@@ -1733,9 +1940,13 @@ httpx = [
{file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
]
+hyperframe = [
+ {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
+ {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
+]
identify = [
- {file = "identify-2.5.0-py2.py3-none-any.whl", hash = "sha256:3acfe15a96e4272b4ec5662ee3e231ceba976ef63fd9980ed2ce9cc415df393f"},
- {file = "identify-2.5.0.tar.gz", hash = "sha256:c83af514ea50bf2be2c4a3f2fb349442b59dc87284558ae9ff54191bff3541d2"},
+ {file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"},
+ {file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
@@ -1749,8 +1960,8 @@ ipdb = [
{file = "ipdb-0.13.9.tar.gz", hash = "sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5"},
]
ipython = [
- {file = "ipython-8.3.0-py3-none-any.whl", hash = "sha256:341456643a764c28f670409bbd5d2518f9b82c013441084ff2c2fc999698f83b"},
- {file = "ipython-8.3.0.tar.gz", hash = "sha256:807ae3cf43b84693c9272f70368440a9a7eaa2e7e6882dad943c32fbf7e51402"},
+ {file = "ipython-8.4.0-py3-none-any.whl", hash = "sha256:7ca74052a38fa25fe9bedf52da0be7d3fdd2fb027c3b778ea78dfe8c212937d1"},
+ {file = "ipython-8.4.0.tar.gz", hash = "sha256:f2db3a10254241d9b447232cec8b424847f338d9d36f9a577a6192c332a46abd"},
]
isort = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
@@ -1772,6 +1983,10 @@ loguru = [
{file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"},
{file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"},
]
+mako = [
+ {file = "Mako-1.2.0-py3-none-any.whl", hash = "sha256:23aab11fdbbb0f1051b93793a58323ff937e98e34aece1c4219675122e57e4ba"},
+ {file = "Mako-1.2.0.tar.gz", hash = "sha256:9a7c7e922b87db3686210cf49d5d767033a41d4010b284e747682c92bddd8b39"},
+]
markdown = [
{file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"},
{file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"},
@@ -1822,6 +2037,60 @@ matplotlib-inline = [
{file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"},
{file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"},
]
+msgpack = [
+ {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"},
+ {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"},
+ {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"},
+ {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6"},
+ {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa"},
+ {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6"},
+ {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba"},
+ {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e"},
+ {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db"},
+ {file = "msgpack-1.0.4-cp310-cp310-win32.whl", hash = "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef"},
+ {file = "msgpack-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075"},
+ {file = "msgpack-1.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52"},
+ {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9"},
+ {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9"},
+ {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08"},
+ {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"},
+ {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6"},
+ {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae"},
+ {file = "msgpack-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6"},
+ {file = "msgpack-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661"},
+ {file = "msgpack-1.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c"},
+ {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0"},
+ {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227"},
+ {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff"},
+ {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd"},
+ {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e"},
+ {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236"},
+ {file = "msgpack-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44"},
+ {file = "msgpack-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1"},
+ {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d"},
+ {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab"},
+ {file = "msgpack-1.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb"},
+ {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9"},
+ {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e"},
+ {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1"},
+ {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e"},
+ {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43"},
+ {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243"},
+ {file = "msgpack-1.0.4-cp38-cp38-win32.whl", hash = "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2"},
+ {file = "msgpack-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6"},
+ {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae"},
+ {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55"},
+ {file = "msgpack-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da"},
+ {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f"},
+ {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92"},
+ {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f"},
+ {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624"},
+ {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8"},
+ {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae"},
+ {file = "msgpack-1.0.4-cp39-cp39-win32.whl", hash = "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c"},
+ {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"},
+ {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"},
+]
multidict = [
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"},
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"},
@@ -1883,29 +2152,62 @@ multidict = [
{file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"},
{file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"},
]
+mypy = [
+ {file = "mypy-0.960-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3a3e525cd76c2c4f90f1449fd034ba21fcca68050ff7c8397bb7dd25dd8b8248"},
+ {file = "mypy-0.960-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7a76dc4f91e92db119b1be293892df8379b08fd31795bb44e0ff84256d34c251"},
+ {file = "mypy-0.960-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffdad80a92c100d1b0fe3d3cf1a4724136029a29afe8566404c0146747114382"},
+ {file = "mypy-0.960-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7d390248ec07fa344b9f365e6ed9d205bd0205e485c555bed37c4235c868e9d5"},
+ {file = "mypy-0.960-cp310-cp310-win_amd64.whl", hash = "sha256:925aa84369a07846b7f3b8556ccade1f371aa554f2bd4fb31cb97a24b73b036e"},
+ {file = "mypy-0.960-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:239d6b2242d6c7f5822163ee082ef7a28ee02e7ac86c35593ef923796826a385"},
+ {file = "mypy-0.960-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f1ba54d440d4feee49d8768ea952137316d454b15301c44403db3f2cb51af024"},
+ {file = "mypy-0.960-cp36-cp36m-win_amd64.whl", hash = "sha256:cb7752b24528c118a7403ee955b6a578bfcf5879d5ee91790667c8ea511d2085"},
+ {file = "mypy-0.960-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:826a2917c275e2ee05b7c7b736c1e6549a35b7ea5a198ca457f8c2ebea2cbecf"},
+ {file = "mypy-0.960-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3eabcbd2525f295da322dff8175258f3fc4c3eb53f6d1929644ef4d99b92e72d"},
+ {file = "mypy-0.960-cp37-cp37m-win_amd64.whl", hash = "sha256:f47322796c412271f5aea48381a528a613f33e0a115452d03ae35d673e6064f8"},
+ {file = "mypy-0.960-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2c7f8bb9619290836a4e167e2ef1f2cf14d70e0bc36c04441e41487456561409"},
+ {file = "mypy-0.960-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbfb873cf2b8d8c3c513367febde932e061a5f73f762896826ba06391d932b2a"},
+ {file = "mypy-0.960-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc537885891382e08129d9862553b3d00d4be3eb15b8cae9e2466452f52b0117"},
+ {file = "mypy-0.960-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:481f98c6b24383188c928f33dd2f0776690807e12e9989dd0419edd5c74aa53b"},
+ {file = "mypy-0.960-cp38-cp38-win_amd64.whl", hash = "sha256:29dc94d9215c3eb80ac3c2ad29d0c22628accfb060348fd23d73abe3ace6c10d"},
+ {file = "mypy-0.960-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:33d53a232bb79057f33332dbbb6393e68acbcb776d2f571ba4b1d50a2c8ba873"},
+ {file = "mypy-0.960-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d645e9e7f7a5da3ec3bbcc314ebb9bb22c7ce39e70367830eb3c08d0140b9ce"},
+ {file = "mypy-0.960-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85cf2b14d32b61db24ade8ac9ae7691bdfc572a403e3cb8537da936e74713275"},
+ {file = "mypy-0.960-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a85a20b43fa69efc0b955eba1db435e2ffecb1ca695fe359768e0503b91ea89f"},
+ {file = "mypy-0.960-cp39-cp39-win_amd64.whl", hash = "sha256:0ebfb3f414204b98c06791af37a3a96772203da60636e2897408517fcfeee7a8"},
+ {file = "mypy-0.960-py3-none-any.whl", hash = "sha256:bfd4f6536bd384c27c392a8b8f790fd0ed5c0cf2f63fc2fed7bce56751d53026"},
+ {file = "mypy-0.960.tar.gz", hash = "sha256:d4fccf04c1acf750babd74252e0f2db6bd2ac3aa8fe960797d9f3ef41cf2bfd4"},
+]
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"},
]
nb-cli = [
- {file = "nb-cli-0.6.6.tar.gz", hash = "sha256:501a747dda00b2d384421634a98b136d9976d12d950d54f1e2bb3c71236ffa73"},
- {file = "nb_cli-0.6.6-py3-none-any.whl", hash = "sha256:72a3929e0de4405f5dee7a43cd5358065b36b39125bc99d7dbb1d672bf7f7713"},
+ {file = "nb-cli-0.6.7.tar.gz", hash = "sha256:394bf65eabbda6aa7c410961b901e9f7320fae12143818b1c078fc43f060fa0e"},
+ {file = "nb_cli-0.6.7-py3-none-any.whl", hash = "sha256:693342ebcf4dce14fd7be4555c65e5da41707a67e5de73d7d7fe67a716a1cad9"},
]
nodeenv = [
{file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
{file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
]
nonebot-adapter-onebot = [
- {file = "nonebot-adapter-onebot-2.0.0b1.tar.gz", hash = "sha256:9dad770371e577fead096ceacacc43b3ef304a8e238e8fff1163eefc4e947a75"},
- {file = "nonebot_adapter_onebot-2.0.0b1-py3-none-any.whl", hash = "sha256:ca1375de1dd503a5ab20440445026195b587e05a2b18ae8df9b6ab17c9e857b5"},
+ {file = "nonebot-adapter-onebot-2.1.0.tar.gz", hash = "sha256:b3696235581a25f52dec8744b82102bf138637845d1bbaaf305b9e89423a562f"},
+ {file = "nonebot_adapter_onebot-2.1.0-py3-none-any.whl", hash = "sha256:c1b4efd6a7ec22430897ba0e0698d58a95b2e28fe39c37271131c796ca366186"},
+]
+nonebot-plugin-datastore = [
+ {file = "nonebot-plugin-datastore-0.3.1.tar.gz", hash = "sha256:d18f29918ff41f657b59f6221913947553178f93ca367db79480f725cd312240"},
+ {file = "nonebot_plugin_datastore-0.3.1-py3-none-any.whl", hash = "sha256:7fc251ef9a8336a19710a9f20e26d777ed12256cf3d37de3c0692c9a3f85a8a2"},
]
nonebot-plugin-htmlrender = [
- {file = "nonebot-plugin-htmlrender-0.0.4.6.tar.gz", hash = "sha256:1d3a6cb9752e27097dc5cda97f552cb1a0617c66ebd9ed6e33eabf14bea15b5d"},
- {file = "nonebot_plugin_htmlrender-0.0.4.6-py3-none-any.whl", hash = "sha256:e7f0739a3e3e779820f2894c872afcbd5e2e54a04d9540d05efac11af34d72d7"},
+ {file = "nonebot-plugin-htmlrender-0.0.4.8.tar.gz", hash = "sha256:6c41afc95b8ab9980903e89841a47db5c02b3a62b9dab5bcf9714c716e69eb2a"},
+ {file = "nonebot_plugin_htmlrender-0.0.4.8-py3-none-any.whl", hash = "sha256:4ed625be82dfdf469864b32a6e129e43b91e863f25b1fdbc8c936418dbd6126e"},
+]
+nonebot-plugin-localstore = [
+ {file = "nonebot-plugin-localstore-0.1.0.tar.gz", hash = "sha256:6d264cf8adcc4a470b496a78384eafd6ffd13bb8b7e14125e38e641748d2ee79"},
+ {file = "nonebot_plugin_localstore-0.1.0-py3-none-any.whl", hash = "sha256:a5be70a32f24d92561120881b883e4583c7aecec9703399cc6eef4618e5ee933"},
]
nonebot2 = [
- {file = "nonebot2-2.0.0b2-py3-none-any.whl", hash = "sha256:8166490311b607f8fbf5e31934b005e29f6d39ff222a6771ec36c9456ec337ec"},
- {file = "nonebot2-2.0.0b2.tar.gz", hash = "sha256:2950f27a62f2a98b2abf3128c19d898a24c2867e70fb5c6af231eadf558b18a8"},
+ {file = "nonebot2-2.0.0b3-py3-none-any.whl", hash = "sha256:00f2ea63d5f2c665428bec4b7a33301f6b1b483d5635d2a3241f0a1ab3b5b2ea"},
+ {file = "nonebot2-2.0.0b3.tar.gz", hash = "sha256:b7ee6ddf387af268e36f4276c2d94b4f717c1a29078400738ca275c3fb266dd4"},
]
nonebug = []
packaging = [
@@ -1929,41 +2231,44 @@ pickleshare = [
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
]
pillow = [
- {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"},
- {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"},
- {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"},
- {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"},
- {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"},
- {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"},
- {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"},
- {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"},
- {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"},
- {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"},
- {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"},
- {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"},
- {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"},
- {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"},
- {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"},
- {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"},
- {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"},
- {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"},
- {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"},
- {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"},
- {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"},
- {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"},
- {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"},
- {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"},
- {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"},
- {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"},
- {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"},
- {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"},
- {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"},
- {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"},
- {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"},
- {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"},
- {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"},
- {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"},
- {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
+ {file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"},
+ {file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"},
+ {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"},
+ {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"},
+ {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"},
+ {file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"},
+ {file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"},
+ {file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"},
+ {file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"},
+ {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"},
+ {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"},
+ {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"},
+ {file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"},
+ {file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"},
+ {file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"},
+ {file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"},
+ {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"},
+ {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"},
+ {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"},
+ {file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"},
+ {file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"},
+ {file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"},
+ {file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"},
+ {file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"},
+ {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"},
+ {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"},
+ {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"},
+ {file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"},
+ {file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"},
+ {file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"},
+ {file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"},
+ {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"},
+ {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"},
+ {file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"},
+ {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"},
+ {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"},
+ {file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"},
+ {file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"},
]
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
@@ -2007,41 +2312,41 @@ py = [
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pydantic = [
- {file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"},
- {file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"},
- {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37"},
- {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25"},
- {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6"},
- {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c"},
- {file = "pydantic-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398"},
- {file = "pydantic-1.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65"},
- {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46"},
- {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c"},
- {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054"},
- {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed"},
- {file = "pydantic-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1"},
- {file = "pydantic-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070"},
- {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2"},
- {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1"},
- {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032"},
- {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6"},
- {file = "pydantic-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d"},
- {file = "pydantic-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7"},
- {file = "pydantic-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77"},
- {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9"},
- {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6"},
- {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"},
- {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034"},
- {file = "pydantic-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f"},
- {file = "pydantic-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b"},
- {file = "pydantic-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c"},
- {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce"},
- {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3"},
- {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d"},
- {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721"},
- {file = "pydantic-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16"},
- {file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"},
- {file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"},
+ {file = "pydantic-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8098a724c2784bf03e8070993f6d46aa2eeca031f8d8a048dff277703e6e193"},
+ {file = "pydantic-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320c64dd876e45254bdd350f0179da737463eea41c43bacbee9d8c9d1021f11"},
+ {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"},
+ {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11951b404e08b01b151222a1cb1a9f0a860a8153ce8334149ab9199cd198131"},
+ {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8bc541a405423ce0e51c19f637050acdbdf8feca34150e0d17f675e72d119580"},
+ {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e565a785233c2d03724c4dc55464559639b1ba9ecf091288dd47ad9c629433bd"},
+ {file = "pydantic-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4a88dcd6ff8fd47c18b3a3709a89adb39a6373f4482e04c1b765045c7e282fd"},
+ {file = "pydantic-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:447d5521575f18e18240906beadc58551e97ec98142266e521c34968c76c8761"},
+ {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985ceb5d0a86fcaa61e45781e567a59baa0da292d5ed2e490d612d0de5796918"},
+ {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059b6c1795170809103a1538255883e1983e5b831faea6558ef873d4955b4a74"},
+ {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d12f96b5b64bec3f43c8e82b4aab7599d0157f11c798c9f9c528a72b9e0b339a"},
+ {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ae72f8098acb368d877b210ebe02ba12585e77bd0db78ac04a1ee9b9f5dd2166"},
+ {file = "pydantic-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:79b485767c13788ee314669008d01f9ef3bc05db9ea3298f6a50d3ef596a154b"},
+ {file = "pydantic-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:494f7c8537f0c02b740c229af4cb47c0d39840b829ecdcfc93d91dcbb0779892"},
+ {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f047e11febe5c3198ed346b507e1d010330d56ad615a7e0a89fae604065a0e"},
+ {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:969dd06110cb780da01336b281f53e2e7eb3a482831df441fb65dd30403f4608"},
+ {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:177071dfc0df6248fd22b43036f936cfe2508077a72af0933d0c1fa269b18537"},
+ {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9bcf8b6e011be08fb729d110f3e22e654a50f8a826b0575c7196616780683380"},
+ {file = "pydantic-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a955260d47f03df08acf45689bd163ed9df82c0e0124beb4251b1290fa7ae728"},
+ {file = "pydantic-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9ce157d979f742a915b75f792dbd6aa63b8eccaf46a1005ba03aa8a986bde34a"},
+ {file = "pydantic-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0bf07cab5b279859c253d26a9194a8906e6f4a210063b84b433cf90a569de0c1"},
+ {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d93d4e95eacd313d2c765ebe40d49ca9dd2ed90e5b37d0d421c597af830c195"},
+ {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1542636a39c4892c4f4fa6270696902acb186a9aaeac6f6cf92ce6ae2e88564b"},
+ {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a9af62e9b5b9bc67b2a195ebc2c2662fdf498a822d62f902bf27cccb52dbbf49"},
+ {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fe4670cb32ea98ffbf5a1262f14c3e102cccd92b1869df3bb09538158ba90fe6"},
+ {file = "pydantic-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:9f659a5ee95c8baa2436d392267988fd0f43eb774e5eb8739252e5a7e9cf07e0"},
+ {file = "pydantic-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b83ba3825bc91dfa989d4eed76865e71aea3a6ca1388b59fc801ee04c4d8d0d6"},
+ {file = "pydantic-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1dd8fecbad028cd89d04a46688d2fcc14423e8a196d5b0a5c65105664901f810"},
+ {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02eefd7087268b711a3ff4db528e9916ac9aa18616da7bca69c1871d0b7a091f"},
+ {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb57ba90929bac0b6cc2af2373893d80ac559adda6933e562dcfb375029acee"},
+ {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4ce9ae9e91f46c344bec3b03d6ee9612802682c1551aaf627ad24045ce090761"},
+ {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72ccb318bf0c9ab97fc04c10c37683d9eea952ed526707fabf9ac5ae59b701fd"},
+ {file = "pydantic-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:61b6760b08b7c395975d893e0b814a11cf011ebb24f7d869e7118f5a339a82e1"},
+ {file = "pydantic-1.9.1-py3-none-any.whl", hash = "sha256:4988c0f13c42bfa9ddd2fe2f569c9d54646ce84adc5de84228cfe83396f3bd58"},
+ {file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"},
]
pyee = [
{file = "pyee-8.1.0-py2.py3-none-any.whl", hash = "sha256:383973b63ad7ed5e3c0311f8b179c52981f9e7b3eaea0e9a830d13ec34dde65f"},
@@ -2177,13 +2482,63 @@ soupsieve = [
{file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"},
{file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"},
]
+sqlalchemy = [
+ {file = "SQLAlchemy-1.4.35-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:093b3109c2747d5dc0fa4314b1caf4c7ca336d5c8c831e3cfbec06a7e861e1e6"},
+ {file = "SQLAlchemy-1.4.35-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6fb6b9ed1d0be7fa2c90be8ad2442c14cbf84eb0709dd1afeeff1e511550041"},
+ {file = "SQLAlchemy-1.4.35-cp27-cp27m-win32.whl", hash = "sha256:d38a49aa75a5759d0d118e26701d70c70a37b896379115f8386e91b0444bfa70"},
+ {file = "SQLAlchemy-1.4.35-cp27-cp27m-win_amd64.whl", hash = "sha256:70e571ae9ee0ff36ed37e2b2765445d54981e4d600eccdf6fe3838bc2538d157"},
+ {file = "SQLAlchemy-1.4.35-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:48036698f20080462e981b18d77d574631a3d1fc2c33b416c6df299ec1d10b99"},
+ {file = "SQLAlchemy-1.4.35-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:4ba2c1f368bcf8551cdaa27eac525022471015633d5bdafbc4297e0511f62f51"},
+ {file = "SQLAlchemy-1.4.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17316100fcd0b6371ac9211351cb976fd0c2e12a859c1a57965e3ef7f3ed2bc"},
+ {file = "SQLAlchemy-1.4.35-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9837133b89ad017e50a02a3b46419869cf4e9aa02743e911b2a9e25fa6b05403"},
+ {file = "SQLAlchemy-1.4.35-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4efb70a62cbbbc052c67dc66b5448b0053b509732184af3e7859d05fdf6223c"},
+ {file = "SQLAlchemy-1.4.35-cp310-cp310-win32.whl", hash = "sha256:1ff9f84b2098ef1b96255a80981ee10f4b5d49b6cfeeccf9632c2078cd86052e"},
+ {file = "SQLAlchemy-1.4.35-cp310-cp310-win_amd64.whl", hash = "sha256:48f0eb5bcc87a9b2a95b345ed18d6400daaa86ca414f6840961ed85c342af8f4"},
+ {file = "SQLAlchemy-1.4.35-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da25e75ba9f3fabc271673b6b413ca234994e6d3453424bea36bb5549c5bbaec"},
+ {file = "SQLAlchemy-1.4.35-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeea6ace30603ca9a8869853bb4a04c7446856d7789e36694cd887967b7621f6"},
+ {file = "SQLAlchemy-1.4.35-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5dbdbb39c1b100df4d182c78949158073ca46ba2850c64fe02ffb1eb5b70903"},
+ {file = "SQLAlchemy-1.4.35-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfd8e4c64c30a5219032e64404d468c425bdbc13b397da906fc9bee6591fc0dd"},
+ {file = "SQLAlchemy-1.4.35-cp36-cp36m-win32.whl", hash = "sha256:9dac1924611698f8fe5b2e58601156c01da2b6c0758ba519003013a78280cf4d"},
+ {file = "SQLAlchemy-1.4.35-cp36-cp36m-win_amd64.whl", hash = "sha256:e8b09e2d90267717d850f2e2323919ea32004f55c40e5d53b41267e382446044"},
+ {file = "SQLAlchemy-1.4.35-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:63c82c9e8ccc2fb4bfd87c24ffbac320f70b7c93b78f206c1f9c441fa3013a5f"},
+ {file = "SQLAlchemy-1.4.35-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:effadcda9a129cc56408dd5b2ea20ee9edcea24bd58e6a1489fa27672d733182"},
+ {file = "SQLAlchemy-1.4.35-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2c6c411d8c59afba95abccd2b418f30ade674186660a2d310d364843049fb2c1"},
+ {file = "SQLAlchemy-1.4.35-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2489e70bfa2356f2d421106794507daccf6cc8711753c442fc97272437fc606"},
+ {file = "SQLAlchemy-1.4.35-cp37-cp37m-win32.whl", hash = "sha256:186cb3bd77abf2ddcf722f755659559bfb157647b3fd3f32ea1c70e8311e8f6b"},
+ {file = "SQLAlchemy-1.4.35-cp37-cp37m-win_amd64.whl", hash = "sha256:babd63fb7cb6b0440abb6d16aca2be63342a6eea3dc7b613bb7a9357dc36920f"},
+ {file = "SQLAlchemy-1.4.35-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9e1a72197529ea00357640f21d92ffc7024e156ef9ac36edf271c8335facbc1a"},
+ {file = "SQLAlchemy-1.4.35-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e255a8dd5572b0c66d6ee53597d36157ad6cf3bc1114f61c54a65189f996ab03"},
+ {file = "SQLAlchemy-1.4.35-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9bec63b1e20ef69484f530fb4b4837e050450637ff9acd6dccc7003c5013abf8"},
+ {file = "SQLAlchemy-1.4.35-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95411abc0e36d18f54fa5e24d42960ea3f144fb16caaa5a8c2e492b5424cc82c"},
+ {file = "SQLAlchemy-1.4.35-cp38-cp38-win32.whl", hash = "sha256:28b17ebbaee6587013be2f78dc4f6e95115e1ec8dd7647c4e7be048da749e48b"},
+ {file = "SQLAlchemy-1.4.35-cp38-cp38-win_amd64.whl", hash = "sha256:9e7094cf04e6042c4210a185fa7b9b8b3b789dd6d1de7b4f19452290838e48bd"},
+ {file = "SQLAlchemy-1.4.35-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:1b4eac3933c335d7f375639885765722534bb4e52e51cdc01a667eea822af9b6"},
+ {file = "SQLAlchemy-1.4.35-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d8edfb09ed2b865485530c13e269833dab62ab2d582fde21026c9039d4d0e62"},
+ {file = "SQLAlchemy-1.4.35-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6204d06bfa85f87625e1831ca663f9dba91ac8aec24b8c65d02fb25cbaf4b4d7"},
+ {file = "SQLAlchemy-1.4.35-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28aa2ef06c904729620cc735262192e622db9136c26d8587f71f29ec7715628a"},
+ {file = "SQLAlchemy-1.4.35-cp39-cp39-win32.whl", hash = "sha256:ecc81336b46e31ae9c9bdfa220082079914e31a476d088d3337ecf531d861228"},
+ {file = "SQLAlchemy-1.4.35-cp39-cp39-win_amd64.whl", hash = "sha256:53c7469b86a60fe2babca4f70111357e6e3d5150373bc85eb3b914356983e89a"},
+ {file = "SQLAlchemy-1.4.35.tar.gz", hash = "sha256:2ffc813b01dc6473990f5e575f210ca5ac2f5465ace3908b78ffd6d20058aab5"},
+]
+sqlalchemy-stubs = [
+ {file = "sqlalchemy-stubs-0.4.tar.gz", hash = "sha256:c665d6dd4482ef642f01027fa06c3d5e91befabb219dc71fc2a09e7d7695f7ae"},
+ {file = "sqlalchemy_stubs-0.4-py3-none-any.whl", hash = "sha256:5eec7aa110adf9b957b631799a72fef396b23ff99fe296df726645d01e312aa5"},
+]
+sqlalchemy2-stubs = [
+ {file = "sqlalchemy2-stubs-0.0.2a23.tar.gz", hash = "sha256:a13d94e23b5b0da8ee21986ef8890788a1f2eb26c2a9f39424cc933e4e7e87ff"},
+ {file = "sqlalchemy2_stubs-0.0.2a23-py3-none-any.whl", hash = "sha256:6011d2219365d4e51f3e9d83ffeb5b904964ef1d143dc1298d8a70ce8641014d"},
+]
+sqlmodel = [
+ {file = "sqlmodel-0.0.6-py3-none-any.whl", hash = "sha256:c5fd8719e09da348cd32ce2a5b6a44f289d3029fa8f1c9818229b6f34f1201b4"},
+ {file = "sqlmodel-0.0.6.tar.gz", hash = "sha256:3b4f966b9671b24d85529d274e6c4dbc7753b468e35d2d6a40bd75cad1f66813"},
+]
stack-data = [
{file = "stack_data-0.2.0-py3-none-any.whl", hash = "sha256:999762f9c3132308789affa03e9271bbbe947bf78311851f4d485d8402ed858e"},
{file = "stack_data-0.2.0.tar.gz", hash = "sha256:45692d41bd633a9503a5195552df22b583caf16f0b27c4e58c98d88c8b648e12"},
]
starlette = [
- {file = "starlette-0.17.1-py3-none-any.whl", hash = "sha256:26a18cbda5e6b651c964c12c88b36d9898481cd428ed6e063f5f29c418f73050"},
- {file = "starlette-0.17.1.tar.gz", hash = "sha256:57eab3cc975a28af62f6faec94d355a410634940f10b30d68d31cb5ec1b44ae8"},
+ {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
+ {file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"},
]
text-unidecode = [
{file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
@@ -2202,12 +2557,12 @@ tomli = [
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
tomlkit = [
- {file = "tomlkit-0.9.2-py3-none-any.whl", hash = "sha256:daf4f9c5f2fbf6b861d6adfc51940b98dee36c13e1d88749a6dc9fb280fff304"},
- {file = "tomlkit-0.9.2.tar.gz", hash = "sha256:ebd982d61446af95a1e082b103e250cb9e6d152eae2581d4a07d31a70b34ab0f"},
+ {file = "tomlkit-0.10.2-py3-none-any.whl", hash = "sha256:905cf92c2111ef80d355708f47ac24ad1b6fc2adc5107455940088c9bbecaedb"},
+ {file = "tomlkit-0.10.2.tar.gz", hash = "sha256:30d54c0b914e595f3d10a87888599eab5321a2a69abc773bbefff51599b72db6"},
]
traitlets = [
- {file = "traitlets-5.2.1.post0-py3-none-any.whl", hash = "sha256:f44b708d33d98b0addb40c29d148a761f44af740603a8fd0e2f8b5b27cf0f087"},
- {file = "traitlets-5.2.1.post0.tar.gz", hash = "sha256:70815ecb20ec619d1af28910ade523383be13754283aef90528eb3d47b77c5db"},
+ {file = "traitlets-5.2.2.post1-py3-none-any.whl", hash = "sha256:1530d04badddc6a73d50b7ee34667d4b96914da352109117b4280cb56523a51b"},
+ {file = "traitlets-5.2.2.post1.tar.gz", hash = "sha256:74803a1baa59af70f023671d86d5c7a834c931186df26d50d362ee6a1ff021fd"},
]
typing-extensions = [
{file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
diff --git a/pyproject.toml b/pyproject.toml
index cc5ef7b..a133c13 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -37,6 +37,8 @@ aiofiles = "^0.8.0"
python-socketio = "^5.4.0"
nonebot-adapter-onebot = "^2.0.0-beta.1"
nonebot-plugin-htmlrender = "^0.0.4"
+nonebot-plugin-datastore = "^0.3.0"
+alembic = "^1.7.6"
[tool.poetry.dev-dependencies]
ipdb = "^0.13.4"
@@ -50,6 +52,7 @@ isort = "^5.10.1"
pre-commit = "^2.17.0"
nb-cli = "^0.6.6"
flaky = "^3.7.0"
+sqlalchemy-stubs = "^0.4"
[build-system]
requires = ["poetry>=0.12"]
diff --git a/src/plugins/nonebot_bison/__init__.py b/src/plugins/nonebot_bison/__init__.py
index 7ec0799..46c621b 100644
--- a/src/plugins/nonebot_bison/__init__.py
+++ b/src/plugins/nonebot_bison/__init__.py
@@ -1,5 +1,8 @@
+from nonebot.plugin import require
+
from . import (
admin_page,
+ bootstrap,
config,
config_manager,
platform,
@@ -11,6 +14,20 @@ from . import (
)
from .plugin_config import plugin_config
+require("nonebot_plugin_localstore")
+
__help__version__ = "0.4.3"
__help__plugin__name__ = "nonebot_bison"
__usage__ = f"本bot可以提供b站、微博等社交媒体的消息订阅,详情请查看本bot文档,或者{'at本bot' if plugin_config.bison_to_me else '' }发送“添加订阅”订阅第一个帐号,发送“查询订阅”或“删除订阅”管理订阅"
+
+__all__ = [
+ "admin_page",
+ "config",
+ "config_manager",
+ "post",
+ "scheduler",
+ "send",
+ "platform",
+ "types",
+ "utils",
+]
diff --git a/src/plugins/nonebot_bison/admin_page/api.py b/src/plugins/nonebot_bison/admin_page/api.py
index bb9edf4..07d6b23 100644
--- a/src/plugins/nonebot_bison/admin_page/api.py
+++ b/src/plugins/nonebot_bison/admin_page/api.py
@@ -1,7 +1,7 @@
import nonebot
from nonebot.adapters.onebot.v11.bot import Bot
-from ..config import Config, NoSuchSubscribeException, NoSuchUserException
+from ..config import NoSuchSubscribeException, NoSuchUserException, config
from ..platform import check_sub_target, platform_manager
from .jwt import pack_jwt
from .token_manager import token_manager
@@ -83,7 +83,6 @@ async def get_subs_info(jwt_obj: dict):
res = {}
for group in groups:
group_id = group["id"]
- config = Config()
subs = list(
map(
lambda sub: {
@@ -112,7 +111,6 @@ async def add_group_sub(
cats: list[int],
tags: list[str],
):
- config = Config()
config.add_subscribe(
int(group_number), "group", target, target_name, platform_name, cats, tags
)
@@ -120,7 +118,6 @@ async def add_group_sub(
async def del_group_sub(group_number: str, platform_name: str, target: str):
- config = Config()
try:
config.del_subscribe(int(group_number), "group", target, platform_name)
except (NoSuchUserException, NoSuchSubscribeException):
@@ -136,7 +133,6 @@ async def update_group_sub(
cats: list[int],
tags: list[str],
):
- config = Config()
try:
config.update_subscribe(
int(group_number), "group", target, target_name, platform_name, cats, tags
diff --git a/src/plugins/nonebot_bison/bootstrap.py b/src/plugins/nonebot_bison/bootstrap.py
new file mode 100644
index 0000000..c1325ab
--- /dev/null
+++ b/src/plugins/nonebot_bison/bootstrap.py
@@ -0,0 +1,20 @@
+from nonebot import get_driver
+from nonebot.log import logger
+
+from .config.config_legacy import start_up as legacy_db_startup
+from .config.db import upgrade_db
+from .scheduler.aps import start_scheduler
+from .scheduler.manager import init_scheduler
+
+
+@get_driver().on_startup
+async def bootstrap():
+ # legacy db
+ legacy_db_startup()
+ # new db
+ await upgrade_db()
+ # init scheduler
+ await init_scheduler()
+ # start scheduler
+ start_scheduler()
+ logger.info("nonebot-bison bootstrap done")
diff --git a/src/plugins/nonebot_bison/config/__init__.py b/src/plugins/nonebot_bison/config/__init__.py
new file mode 100644
index 0000000..0544e51
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/__init__.py
@@ -0,0 +1,3 @@
+from .config_legacy import NoSuchSubscribeException, NoSuchUserException
+from .db import DATA
+from .db_config import config
diff --git a/src/plugins/nonebot_bison/config.py b/src/plugins/nonebot_bison/config/config_legacy.py
similarity index 84%
rename from src/plugins/nonebot_bison/config.py
rename to src/plugins/nonebot_bison/config/config_legacy.py
index 815c672..1c1a06c 100644
--- a/src/plugins/nonebot_bison/config.py
+++ b/src/plugins/nonebot_bison/config/config_legacy.py
@@ -1,16 +1,18 @@
import os
from collections import defaultdict
from os import path
+from pathlib import Path
from typing import DefaultDict, Literal, Mapping, TypedDict
import nonebot
from nonebot.log import logger
from tinydb import Query, TinyDB
-from .platform import platform_manager
-from .plugin_config import plugin_config
-from .types import Target, User
-from .utils import Singleton
+from ..platform import platform_manager
+from ..plugin_config import plugin_config
+from ..types import Target, User
+from ..utils import Singleton
+from .utils import NoSuchSubscribeException, NoSuchUserException
supported_target_type = platform_manager.keys()
@@ -30,12 +32,20 @@ def get_config_path() -> str:
return new_path
-class NoSuchUserException(Exception):
- pass
-
-
-class NoSuchSubscribeException(Exception):
- pass
+def drop():
+ if plugin_config.bison_config_path:
+ data_dir = plugin_config.bison_config_path
+ else:
+ working_dir = os.getcwd()
+ data_dir = path.join(working_dir, "data")
+ old_path = path.join(data_dir, "bison.json")
+ new_path = path.join(data_dir, "bison-legacy.json")
+ if os.path.exists(old_path):
+ config.db.close()
+ config.available = False
+ os.rename(old_path, new_path)
+ return True
+ return False
class SubscribeContent(TypedDict):
@@ -47,24 +57,33 @@ class SubscribeContent(TypedDict):
class ConfigContent(TypedDict):
- user: str
+ user: int
user_type: Literal["group", "private"]
subs: list[SubscribeContent]
class Config(metaclass=Singleton):
+ "Dropping it!"
migrate_version = 2
def __init__(self):
- self.db = TinyDB(get_config_path(), encoding="utf-8")
- self.kv_config = self.db.table("kv")
- self.user_target = self.db.table("user_target")
- self.target_user_cache: dict[str, defaultdict[Target, list[User]]] = {}
- self.target_user_cat_cache = {}
- self.target_user_tag_cache = {}
- self.target_list = {}
- self.next_index: DefaultDict[str, int] = defaultdict(lambda: 0)
+ self._do_init()
+
+ def _do_init(self):
+ path = get_config_path()
+ if Path(path).exists():
+ self.available = True
+ self.db = TinyDB(get_config_path(), encoding="utf-8")
+ self.kv_config = self.db.table("kv")
+ self.user_target = self.db.table("user_target")
+ self.target_user_cache: dict[str, defaultdict[Target, list[User]]] = {}
+ self.target_user_cat_cache = {}
+ self.target_user_tag_cache = {}
+ self.target_list = {}
+ self.next_index: DefaultDict[str, int] = defaultdict(lambda: 0)
+ else:
+ self.available = False
def add_subscribe(
self, user, user_type, target, target_name, target_type, cats, tags
@@ -220,6 +239,8 @@ class Config(metaclass=Singleton):
def start_up():
config = Config()
+ if not config.available:
+ return
if not (search_res := config.kv_config.search(Query().name == "version")):
config.kv_config.insert({"name": "version", "value": config.migrate_version})
elif search_res[0].get("value") < config.migrate_version:
@@ -240,4 +261,4 @@ def start_up():
config.update_send_cache()
-nonebot.get_driver().on_startup(start_up)
+config = Config()
diff --git a/src/plugins/nonebot_bison/config/db.py b/src/plugins/nonebot_bison/config/db.py
new file mode 100644
index 0000000..8e151c6
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/db.py
@@ -0,0 +1,101 @@
+from pathlib import Path
+
+import nonebot
+from alembic.config import Config
+from alembic.runtime.environment import EnvironmentContext
+from alembic.script.base import ScriptDirectory
+from nonebot.log import logger
+from nonebot_plugin_datastore import PluginData, create_session, db
+from nonebot_plugin_datastore.db import get_engine
+from sqlalchemy.engine.base import Connection
+from sqlalchemy.ext.asyncio.session import AsyncSession
+
+from .config_legacy import ConfigContent, config, drop
+from .db_model import Base, Subscribe, Target, User
+
+DATA = PluginData("bison")
+
+
+async def data_migrate():
+ if config.available:
+ logger.warning("You are still using legacy db, migrating to sqlite")
+ all_subs: list[ConfigContent] = list(
+ map(lambda item: ConfigContent(**item), config.get_all_subscribe().all())
+ )
+ async with AsyncSession(get_engine()) as sess:
+ user_to_create = []
+ subscribe_to_create = []
+ platform_target_map: dict[str, tuple[Target, str, int]] = {}
+ for user in all_subs:
+ db_user = User(uid=user["user"], type=user["user_type"])
+ user_to_create.append(db_user)
+ for sub in user["subs"]:
+ target = sub["target"]
+ platform_name = sub["target_type"]
+ target_name = sub["target_name"]
+ key = f"{target}-{platform_name}"
+ if key in platform_target_map.keys():
+ target_obj, ext_user_type, ext_user = platform_target_map[key]
+ if target_obj.target_name != target_name:
+ # GG
+ logger.error(
+ f"你的旧版本数据库中存在数据不一致问题,请完成迁移后执行重新添加{platform_name}平台的{target}"
+ f"它的名字可能为{target_obj.target_name}或{target_name}"
+ )
+
+ else:
+ target_obj = Target(
+ platform_name=platform_name,
+ target_name=target_name,
+ target=target,
+ )
+ platform_target_map[key] = (
+ target_obj,
+ user["user_type"],
+ user["user"],
+ )
+ subscribe_obj = Subscribe(
+ user=db_user,
+ target=target_obj,
+ categories=sub["cats"],
+ tags=sub["tags"],
+ )
+ subscribe_to_create.append(subscribe_obj)
+ sess.add_all(
+ user_to_create
+ + list(map(lambda x: x[0], platform_target_map.values()))
+ + subscribe_to_create
+ )
+ await sess.commit()
+ drop()
+ logger.info("migrate success")
+
+
+async def upgrade_db():
+ alembic_cfg = Config()
+ alembic_cfg.set_main_option(
+ "script_location", str(Path(__file__).parent.joinpath("migrate"))
+ )
+
+ script = ScriptDirectory.from_config(alembic_cfg)
+ engine = db.get_engine()
+ env = EnvironmentContext(alembic_cfg, script)
+
+ def migrate_fun(revision, context):
+ return script._upgrade_revs("head", revision)
+
+ def do_run_migration(connection: Connection):
+ env.configure(
+ connection,
+ target_metadata=Base.metadata,
+ fn=migrate_fun,
+ render_as_batch=True,
+ )
+ with env.begin_transaction():
+ env.run_migrations()
+ logger.info("Finish auto migrate")
+
+ async with engine.connect() as connection:
+ await connection.run_sync(do_run_migration)
+
+ await data_migrate()
diff --git a/src/plugins/nonebot_bison/config/db_config.py b/src/plugins/nonebot_bison/config/db_config.py
new file mode 100644
index 0000000..1548553
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/db_config.py
@@ -0,0 +1,266 @@
+from dataclasses import dataclass
+from datetime import datetime, time
+from typing import Any, Awaitable, Callable, Optional
+
+from nonebot_plugin_datastore.db import get_engine
+from sqlalchemy.ext.asyncio.session import AsyncSession
+from sqlalchemy.orm import selectinload
+from sqlalchemy.sql.expression import delete, select
+from sqlalchemy.sql.functions import func
+
+from ..types import Category, Tag
+from ..types import Target as T_Target
+from ..types import User as T_User
+from ..types import UserSubInfo
+from .db_model import ScheduleTimeWeight, Subscribe, Target, User
+
+
+def _get_time():
+ dt = datetime.now()
+ cur_time = time(hour=dt.hour, minute=dt.minute, second=dt.second)
+ return cur_time
+
+
+@dataclass
+class TimeWeightConfig:
+ start_time: time
+ end_time: time
+ weight: int
+
+
+@dataclass
+class WeightConfig:
+
+ default: int
+ time_config: list[TimeWeightConfig]
+
+
+class DBConfig:
+ def __init__(self):
+ self.add_target_hook: Optional[Callable[[str, T_Target], Awaitable]] = None
+ self.delete_target_hook: Optional[Callable[[str, T_Target], Awaitable]] = None
+
+ def register_add_target_hook(self, fun: Callable[[str, T_Target], Awaitable]):
+ self.add_target_hook = fun
+
+ def register_delete_target_hook(self, fun: Callable[[str, T_Target], Awaitable]):
+ self.delete_target_hook = fun
+
+ async def add_subscribe(
+ self,
+ user: int,
+ user_type: str,
+ target: T_Target,
+ target_name: str,
+ platform_name: str,
+ cats: list[Category],
+ tags: list[Tag],
+ ):
+ async with AsyncSession(get_engine()) as session:
+ db_user_stmt = (
+ select(User).where(User.uid == user).where(User.type == user_type)
+ )
+ db_user: Optional[User] = await session.scalar(db_user_stmt)
+ if not db_user:
+ db_user = User(uid=user, type=user_type)
+ session.add(db_user)
+ db_target_stmt = (
+ select(Target)
+ .where(Target.platform_name == platform_name)
+ .where(Target.target == target)
+ )
+ db_target: Optional[Target] = await session.scalar(db_target_stmt)
+ if not db_target:
+ db_target = Target(
+ target=target, platform_name=platform_name, target_name=target_name
+ )
+ if self.add_target_hook:
+ await self.add_target_hook(platform_name, target)
+ else:
+ db_target.target_name = target_name # type: ignore
+ subscribe = Subscribe(
+ categories=cats,
+ tags=tags,
+ user=db_user,
+ target=db_target,
+ )
+ session.add(subscribe)
+ await session.commit()
+
+ async def list_subscribe(self, user: int, user_type: str) -> list[Subscribe]:
+ async with AsyncSession(get_engine()) as session:
+ query_stmt = (
+ select(Subscribe)
+ .where(User.type == user_type, User.uid == user)
+ .join(User)
+ .options(selectinload(Subscribe.target)) # type:ignore
+ )
+ subs: list[Subscribe] = (await session.scalars(query_stmt)).all()
+ return subs
+
+ async def del_subscribe(
+ self, user: int, user_type: str, target: str, platform_name: str
+ ):
+ async with AsyncSession(get_engine()) as session:
+ user_obj = await session.scalar(
+ select(User).where(User.uid == user, User.type == user_type)
+ )
+ target_obj = await session.scalar(
+ select(Target).where(
+ Target.platform_name == platform_name, Target.target == target
+ )
+ )
+ await session.execute(
+ delete(Subscribe).where(
+ Subscribe.user == user_obj, Subscribe.target == target_obj
+ )
+ )
+ target_count = await session.scalar(
+ select(func.count())
+ .select_from(Subscribe)
+ .where(Subscribe.target == target_obj)
+ )
+ if target_count == 0:
+ # delete empty target
+ # await session.delete(target_obj)
+ if self.delete_target_hook:
+ await self.delete_target_hook(platform_name, T_Target(target))
+ await session.commit()
+
+ async def update_subscribe(
+ self,
+ user: int,
+ user_type: str,
+ target: str,
+ target_name: str,
+ platform_name: str,
+ cats: list,
+ tags: list,
+ ):
+ async with AsyncSession(get_engine()) as sess:
+ subscribe_obj: Subscribe = await sess.scalar(
+ select(Subscribe)
+ .where(
+ User.uid == user,
+ User.type == user_type,
+ Target.target == target,
+ Target.platform_name == platform_name,
+ )
+ .join(User)
+ .join(Target)
+ .options(selectinload(Subscribe.target)) # type:ignore
+ )
+ subscribe_obj.tags = tags # type:ignore
+ subscribe_obj.categories = cats # type:ignore
+ subscribe_obj.target.target_name = target_name
+ await sess.commit()
+
+ async def get_platform_target(self, platform_name: str) -> list[Target]:
+ async with AsyncSession(get_engine()) as sess:
+ subq = select(Subscribe.target_id).distinct().subquery()
+ query = (
+ select(Target).join(subq).where(Target.platform_name == platform_name)
+ )
+ return (await sess.scalars(query)).all()
+
+ async def get_time_weight_config(
+ self, target: T_Target, platform_name: str
+ ) -> WeightConfig:
+ async with AsyncSession(get_engine()) as sess:
+ time_weight_conf: list[ScheduleTimeWeight] = (
+ await sess.scalars(
+ select(ScheduleTimeWeight)
+ .where(
+ Target.platform_name == platform_name, Target.target == target
+ )
+ .join(Target)
+ )
+ ).all()
+ targetObj: Target = await sess.scalar(
+ select(Target).where(
+ Target.platform_name == platform_name, Target.target == target
+ )
+ )
+ return WeightConfig(
+ default=targetObj.default_schedule_weight,
+ time_config=[
+ TimeWeightConfig(
+ start_time=time_conf.start_time,
+ end_time=time_conf.end_time,
+ weight=time_conf.weight,
+ )
+ for time_conf in time_weight_conf
+ ],
+ )
+
+ async def update_time_weight_config(
+ self, target: T_Target, platform_name: str, conf: WeightConfig
+ ):
+ async with AsyncSession(get_engine()) as sess:
+ targetObj: Target = await sess.scalar(
+ select(Target).where(
+ Target.platform_name == platform_name, Target.target == target
+ )
+ )
+ target_id = targetObj.id
+ targetObj.default_schedule_weight = conf.default
+ delete(ScheduleTimeWeight).where(ScheduleTimeWeight.target_id == target_id)
+ for time_conf in conf.time_config:
+ new_conf = ScheduleTimeWeight(
+ start_time=time_conf.start_time,
+ end_time=time_conf.end_time,
+ weight=time_conf.weight,
+ target=targetObj,
+ )
+ sess.add(new_conf)
+
+ await sess.commit()
+
+ async def get_current_weight_val(self, platform_list: list[str]) -> dict[str, int]:
+ res = {}
+ cur_time = _get_time()
+ async with AsyncSession(get_engine()) as sess:
+ targets: list[Target] = (
+ await sess.scalars(
+ select(Target)
+ .where(Target.platform_name.in_(platform_list))
+ .options(selectinload(Target.time_weight))
+ )
+ ).all()
+ for target in targets:
+ key = f"{target.platform_name}-{target.target}"
+ weight = target.default_schedule_weight
+ for time_conf in target.time_weight:
+ if (
+ time_conf.start_time <= cur_time
+ and time_conf.end_time > cur_time
+ ):
+ weight = time_conf.weight
+ break
+ res[key] = weight
+ return res
+
+ async def get_platform_target_subscribers(
+ self, platform_name: str, target: T_Target
+ ) -> list[UserSubInfo]:
+ async with AsyncSession(get_engine()) as sess:
+ query = (
+ select(Subscribe)
+ .join(Target)
+ .where(Target.platform_name == platform_name, Target.target == target)
+ .options(selectinload(Subscribe.user))
+ )
+ subsribes: list[Subscribe] = (await sess.scalars(query)).all()
+ return list(
+ map(
+ lambda subscribe: UserSubInfo(
+ T_User(subscribe.user.uid, subscribe.user.type),
+ subscribe.categories,
+ subscribe.tags,
+ ),
+ subsribes,
+ )
+ )
+
+
+config = DBConfig()
diff --git a/src/plugins/nonebot_bison/config/db_model.py b/src/plugins/nonebot_bison/config/db_model.py
new file mode 100644
index 0000000..17a122c
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/db_model.py
@@ -0,0 +1,63 @@
+from datetime import datetime
+
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relationship
+from sqlalchemy.sql.schema import Column, ForeignKey, UniqueConstraint
+from sqlalchemy.sql.sqltypes import JSON, DateTime, Integer, String, Time
+
+Base = declarative_base()
+
+
+class User(Base):
+ __tablename__ = "user"
+ __table_args__ = (UniqueConstraint("type", "uid", name="unique-user-constraint"),)
+
+ id = Column(Integer, primary_key=True, autoincrement=True)
+ type = Column(String(20), nullable=False)
+ uid = Column(Integer, nullable=False)
+
+ subscribes = relationship("Subscribe", back_populates="user")
+
+
+class Target(Base):
+ __tablename__ = "target"
+ __table_args__ = (
+ UniqueConstraint("target", "platform_name", name="unique-target-constraint"),
+ )
+
+ id = Column(Integer, primary_key=True, autoincrement=True)
+ platform_name = Column(String(20), nullable=False)
+ target = Column(String(1024), nullable=False)
+ target_name = Column(String(1024), nullable=False)
+ default_schedule_weight = Column(Integer, default=10)
+
+ subscribes = relationship("Subscribe", back_populates="target")
+ time_weight = relationship("ScheduleTimeWeight", back_populates="target")
+
+
+class ScheduleTimeWeight(Base):
+ __tablename__ = "schedule_time_weight"
+
+ id = Column(Integer, primary_key=True, autoincrement=True)
+ target_id = Column(Integer, ForeignKey(Target.id))
+ start_time = Column(Time)
+ end_time = Column(Time)
+ weight = Column(Integer)
+
+ target = relationship("Target", back_populates="time_weight")
+
+
+class Subscribe(Base):
+ __tablename__ = "subscribe"
+ __table_args__ = (
+ UniqueConstraint("target_id", "user_id", name="unique-subscribe-constraint"),
+ )
+
+ id = Column(Integer, primary_key=True, autoincrement=True)
+ target_id = Column(Integer, ForeignKey(Target.id))
+ user_id = Column(Integer, ForeignKey(User.id))
+ categories = Column(JSON)
+ tags = Column(JSON)
+
+ target = relationship("Target", back_populates="subscribes")
+ user = relationship("User", back_populates="subscribes")
diff --git a/src/plugins/nonebot_bison/config/migrate/README b/src/plugins/nonebot_bison/config/migrate/README
new file mode 100644
index 0000000..98e4f9c
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/migrate/README
@@ -0,0 +1 @@
+Generic single-database configuration.
\ No newline at end of file
diff --git a/src/plugins/nonebot_bison/config/migrate/__init__.py b/src/plugins/nonebot_bison/config/migrate/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/plugins/nonebot_bison/config/migrate/env.py b/src/plugins/nonebot_bison/config/migrate/env.py
new file mode 100644
index 0000000..79790cc
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/migrate/env.py
@@ -0,0 +1,113 @@
+import asyncio
+from logging.config import fileConfig
+
+from alembic import context
+from sqlalchemy import engine_from_config, pool
+from sqlalchemy.engine.base import Connection
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+if config.config_file_name:
+ fileConfig(config.config_file_name) # type:ignore
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+
+import nonebot
+
+try:
+ nonebot.get_driver()
+ __as_plugin = True
+ target_metadata = None
+except:
+ __as_plugin = False
+ nonebot.init()
+ from nonebot_bison.config.db_model import Base
+
+ target_metadata = Base.metadata
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def run_migrations_offline():
+ """Run migrations in 'offline' mode.
+
+ This configures the context with just a URL
+ and not an Engine, though an Engine is acceptable
+ here as well. By skipping the Engine creation
+ we don't even need a DBAPI to be available.
+
+ Calls to context.execute() here emit the given string to the
+ script output.
+
+ """
+ url = config.get_main_option("sqlalchemy.url")
+ context.configure(
+ url=url,
+ target_metadata=target_metadata,
+ literal_binds=True,
+ dialect_opts={"paramstyle": "named"},
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+def do_run_migration(connection: Connection):
+ if __as_plugin:
+ context.configure(connection=connection)
+ else:
+ context.configure(
+ connection=connection,
+ target_metadata=target_metadata,
+ render_as_batch=True,
+ compare_type=True,
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+async def run_migrations_async():
+
+ from nonebot_plugin_datastore.db import get_engine
+
+ connectable = get_engine()
+ async with connectable.connect() as connection:
+ await connection.run_sync(do_run_migration)
+
+
+def run_migrations_online():
+ """Run migrations in 'online' mode.
+
+ In this scenario we need to create an Engine
+ and associate a connection with the context.
+
+ """
+ if not __as_plugin:
+ connectable = engine_from_config(
+ config.get_section(config.config_ini_section),
+ prefix="sqlalchemy.",
+ poolclass=pool.NullPool,
+ )
+
+ with connectable.connect() as connection:
+ do_run_migration(connection)
+ else:
+ # asyncio.run(run_migrations_async())
+ asyncio.create_task(run_migrations_async())
+
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ run_migrations_online()
diff --git a/src/plugins/nonebot_bison/config/migrate/script.py.mako b/src/plugins/nonebot_bison/config/migrate/script.py.mako
new file mode 100644
index 0000000..2c01563
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/migrate/script.py.mako
@@ -0,0 +1,24 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+
+def upgrade():
+ ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+ ${downgrades if downgrades else "pass"}
diff --git a/src/plugins/nonebot_bison/config/migrate/versions/0571870f5222_init_db.py b/src/plugins/nonebot_bison/config/migrate/versions/0571870f5222_init_db.py
new file mode 100644
index 0000000..d6e0c2c
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/migrate/versions/0571870f5222_init_db.py
@@ -0,0 +1,60 @@
+"""init db
+
+Revision ID: 0571870f5222
+Revises:
+Create Date: 2022-03-21 19:18:13.762626
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "0571870f5222"
+down_revision = None
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table(
+ "target",
+ sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
+ sa.Column("platform_name", sa.String(length=20), nullable=False),
+ sa.Column("target", sa.String(length=1024), nullable=False),
+ sa.Column("target_name", sa.String(length=1024), nullable=False),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ op.create_table(
+ "user",
+ sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
+ sa.Column("type", sa.String(length=20), nullable=False),
+ sa.Column("uid", sa.Integer(), nullable=False),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ op.create_table(
+ "subscribe",
+ sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
+ sa.Column("target_id", sa.Integer(), nullable=True),
+ sa.Column("user_id", sa.Integer(), nullable=True),
+ sa.Column("categories", sa.String(length=1024), nullable=True),
+ sa.Column("tags", sa.String(length=1024), nullable=True),
+ sa.ForeignKeyConstraint(
+ ["target_id"],
+ ["target.id"],
+ ),
+ sa.ForeignKeyConstraint(
+ ["user_id"],
+ ["user.id"],
+ ),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table("subscribe")
+ op.drop_table("user")
+ op.drop_table("target")
+ # ### end Alembic commands ###
diff --git a/src/plugins/nonebot_bison/config/migrate/versions/4a46ba54a3f3_alter_type.py b/src/plugins/nonebot_bison/config/migrate/versions/4a46ba54a3f3_alter_type.py
new file mode 100644
index 0000000..0c3a602
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/migrate/versions/4a46ba54a3f3_alter_type.py
@@ -0,0 +1,53 @@
+"""alter type
+
+Revision ID: 4a46ba54a3f3
+Revises: c97c445e2bdb
+Create Date: 2022-03-27 21:50:10.911649
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "4a46ba54a3f3"
+down_revision = "c97c445e2bdb"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("subscribe", schema=None) as batch_op:
+ batch_op.alter_column(
+ "categories",
+ existing_type=sa.VARCHAR(length=1024),
+ type_=sa.JSON(),
+ existing_nullable=True,
+ )
+ batch_op.alter_column(
+ "tags",
+ existing_type=sa.VARCHAR(length=1024),
+ type_=sa.JSON(),
+ existing_nullable=True,
+ )
+
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("subscribe", schema=None) as batch_op:
+ batch_op.alter_column(
+ "tags",
+ existing_type=sa.JSON(),
+ type_=sa.VARCHAR(length=1024),
+ existing_nullable=True,
+ )
+ batch_op.alter_column(
+ "categories",
+ existing_type=sa.JSON(),
+ type_=sa.VARCHAR(length=1024),
+ existing_nullable=True,
+ )
+
+ # ### end Alembic commands ###
diff --git a/src/plugins/nonebot_bison/config/migrate/versions/5f3370328e44_add_time_weight_table.py b/src/plugins/nonebot_bison/config/migrate/versions/5f3370328e44_add_time_weight_table.py
new file mode 100644
index 0000000..1b873b3
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/migrate/versions/5f3370328e44_add_time_weight_table.py
@@ -0,0 +1,51 @@
+"""add time-weight table
+
+Revision ID: 5f3370328e44
+Revises: a333d6224193
+Create Date: 2022-05-31 22:05:13.235981
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "5f3370328e44"
+down_revision = "a333d6224193"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table(
+ "schedule_time_weight",
+ sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
+ sa.Column("target_id", sa.Integer(), nullable=True),
+ sa.Column("start_time", sa.Time(), nullable=True),
+ sa.Column("end_time", sa.Time(), nullable=True),
+ sa.Column("weight", sa.Integer(), nullable=True),
+ sa.ForeignKeyConstraint(
+ ["target_id"],
+ ["target.id"],
+ ),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ with op.batch_alter_table("target", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column("default_schedule_weight", sa.Integer(), nullable=True)
+ )
+ batch_op.drop_column("last_schedule_time")
+
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("target", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column("last_schedule_time", sa.DATETIME(), nullable=True)
+ )
+ batch_op.drop_column("default_schedule_weight")
+
+ op.drop_table("schedule_time_weight")
+ # ### end Alembic commands ###
diff --git a/src/plugins/nonebot_bison/config/migrate/versions/a333d6224193_add_last_scheduled_time.py b/src/plugins/nonebot_bison/config/migrate/versions/a333d6224193_add_last_scheduled_time.py
new file mode 100644
index 0000000..43848e5
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/migrate/versions/a333d6224193_add_last_scheduled_time.py
@@ -0,0 +1,33 @@
+"""add last scheduled time
+
+Revision ID: a333d6224193
+Revises: 4a46ba54a3f3
+Create Date: 2022-03-29 21:01:38.213153
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "a333d6224193"
+down_revision = "4a46ba54a3f3"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("target", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column("last_schedule_time", sa.DateTime(timezone=True), nullable=True)
+ )
+
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("target", schema=None) as batch_op:
+ batch_op.drop_column("last_schedule_time")
+
+ # ### end Alembic commands ###
diff --git a/src/plugins/nonebot_bison/config/migrate/versions/c97c445e2bdb_add_constraint.py b/src/plugins/nonebot_bison/config/migrate/versions/c97c445e2bdb_add_constraint.py
new file mode 100644
index 0000000..9119d3b
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/migrate/versions/c97c445e2bdb_add_constraint.py
@@ -0,0 +1,47 @@
+"""add constraint
+
+Revision ID: c97c445e2bdb
+Revises: 0571870f5222
+Create Date: 2022-03-26 19:46:50.910721
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "c97c445e2bdb"
+down_revision = "0571870f5222"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("subscribe", schema=None) as batch_op:
+ batch_op.create_unique_constraint(
+ "unique-subscribe-constraint", ["target_id", "user_id"]
+ )
+
+ with op.batch_alter_table("target", schema=None) as batch_op:
+ batch_op.create_unique_constraint(
+ "unique-target-constraint", ["target", "platform_name"]
+ )
+
+ with op.batch_alter_table("user", schema=None) as batch_op:
+ batch_op.create_unique_constraint("unique-user-constraint", ["type", "uid"])
+
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("user", schema=None) as batch_op:
+ batch_op.drop_constraint("unique-user-constraint", type_="unique")
+
+ with op.batch_alter_table("target", schema=None) as batch_op:
+ batch_op.drop_constraint("unique-target-constraint", type_="unique")
+
+ with op.batch_alter_table("subscribe", schema=None) as batch_op:
+ batch_op.drop_constraint("unique-subscribe-constraint", type_="unique")
+
+ # ### end Alembic commands ###
diff --git a/src/plugins/nonebot_bison/config/utils.py b/src/plugins/nonebot_bison/config/utils.py
new file mode 100644
index 0000000..1a8c19f
--- /dev/null
+++ b/src/plugins/nonebot_bison/config/utils.py
@@ -0,0 +1,6 @@
+class NoSuchUserException(Exception):
+ pass
+
+
+class NoSuchSubscribeException(Exception):
+ pass
diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py
index ec4c4f8..771e48b 100644
--- a/src/plugins/nonebot_bison/config_manager.py
+++ b/src/plugins/nonebot_bison/config_manager.py
@@ -1,6 +1,6 @@
import asyncio
from datetime import datetime
-from typing import Optional, Type
+from typing import Optional, Type, cast
from nonebot import on_command
from nonebot.adapters.onebot.v11 import Bot, Event, MessageEvent
@@ -9,14 +9,13 @@ from nonebot.adapters.onebot.v11.message import Message
from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER
from nonebot.internal.params import ArgStr
from nonebot.internal.rule import Rule
-from nonebot.log import logger
from nonebot.matcher import Matcher
from nonebot.params import Depends, EventPlainText, EventToMe
from nonebot.permission import SUPERUSER
from nonebot.rule import to_me
from nonebot.typing import T_State
-from .config import Config
+from .config import config
from .platform import Platform, check_sub_target, platform_manager
from .plugin_config import plugin_config
from .types import Category, Target, User
@@ -197,17 +196,16 @@ def do_add_sub(add_sub: Type[Matcher]):
@add_sub.got("tags", _gen_prompt_template("{_prompt}"), [Depends(parser_tags)])
async def add_sub_process(event: Event, state: T_State):
- config = Config()
- user = state.get("target_user_info")
+ user = cast(User, state.get("target_user_info"))
assert isinstance(user, User)
- config.add_subscribe(
+ await config.add_subscribe(
# state.get("_user_id") or event.group_id,
# user_type="group",
user=user.user,
user_type=user.user_type,
target=state["id"],
target_name=state["name"],
- target_type=state["platform"],
+ platform_name=state["platform"],
cats=state.get("cats", []),
tags=state.get("tags", []),
)
@@ -219,10 +217,9 @@ def do_query_sub(query_sub: Type[Matcher]):
@query_sub.handle()
async def _(state: T_State):
- config: Config = Config()
user_info = state["target_user_info"]
assert isinstance(user_info, User)
- sub_list = config.list_subscribe(
+ sub_list = await config.list_subscribe(
# state.get("_user_id") or event.group_id, "group"
user_info.user,
user_info.user_type,
@@ -230,17 +227,20 @@ def do_query_sub(query_sub: Type[Matcher]):
res = "订阅的帐号为:\n"
for sub in sub_list:
res += "{} {} {}".format(
- sub["target_type"], sub["target_name"], sub["target"]
+ # sub["target_type"], sub["target_name"], sub["target"]
+ sub.target.platform_name,
+ sub.target.target_name,
+ sub.target.target,
)
- platform = platform_manager[sub["target_type"]]
+ platform = platform_manager[sub.target.platform_name]
if platform.categories:
res += " [{}]".format(
", ".join(
- map(lambda x: platform.categories[Category(x)], sub["cats"])
+ map(lambda x: platform.categories[Category(x)], sub.categories)
)
)
if platform.enable_tag:
- res += " {}".format(", ".join(sub["tags"]))
+ res += " {}".format(", ".join(sub.tags))
res += "\n"
await query_sub.finish(Message(await parse_text(res)))
@@ -250,11 +250,10 @@ def do_del_sub(del_sub: Type[Matcher]):
@del_sub.handle()
async def send_list(bot: Bot, event: Event, state: T_State):
- config: Config = Config()
user_info = state["target_user_info"]
assert isinstance(user_info, User)
try:
- sub_list = config.list_subscribe(
+ sub_list = await config.list_subscribe(
# state.get("_user_id") or event.group_id, "group"
user_info.user,
user_info.user_type,
@@ -267,21 +266,27 @@ def do_del_sub(del_sub: Type[Matcher]):
state["sub_table"] = {}
for index, sub in enumerate(sub_list, 1):
state["sub_table"][index] = {
- "target_type": sub["target_type"],
- "target": sub["target"],
+ "platform_name": sub.target.platform_name,
+ "target": sub.target.target,
}
res += "{} {} {} {}\n".format(
- index, sub["target_type"], sub["target_name"], sub["target"]
+ index,
+ sub.target.platform_name,
+ sub.target.target_name,
+ sub.target.target,
)
- platform = platform_manager[sub["target_type"]]
+ platform = platform_manager[sub.target.platform_name]
if platform.categories:
res += " [{}]".format(
", ".join(
- map(lambda x: platform.categories[Category(x)], sub["cats"])
+ map(
+ lambda x: platform.categories[Category(x)],
+ sub.categories,
+ )
)
)
if platform.enable_tag:
- res += " {}".format(", ".join(sub["tags"]))
+ res += " {}".format(", ".join(sub.tags))
res += "\n"
res += "请输入要删除的订阅的序号\n输入'取消'中止"
await bot.send(event=event, message=Message(await parse_text(res)))
@@ -293,10 +298,9 @@ def do_del_sub(del_sub: Type[Matcher]):
await del_sub.finish("删除中止")
try:
index = int(user_msg)
- config = Config()
user_info = state["target_user_info"]
assert isinstance(user_info, User)
- config.del_subscribe(
+ await config.del_subscribe(
# state.get("_user_id") or event.group_id,
# "group",
user_info.user,
diff --git a/src/plugins/nonebot_bison/platform/arknights.py b/src/plugins/nonebot_bison/platform/arknights.py
index 721a425..1434a9f 100644
--- a/src/plugins/nonebot_bison/platform/arknights.py
+++ b/src/plugins/nonebot_bison/platform/arknights.py
@@ -7,9 +7,16 @@ from nonebot.plugin import require
from ..post import Post
from ..types import Category, RawPost, Target
from ..utils import http_client
+from ..utils.scheduler_config import SchedulerConfig
from .platform import CategoryNotSupport, NewMessage, StatusChange
+class ArknightsSchedConf(SchedulerConfig, name="arknights"):
+
+ schedule_type = "interval"
+ schedule_setting = {"seconds": 30}
+
+
class Arknights(NewMessage):
categories = {1: "游戏公告"}
@@ -18,8 +25,7 @@ class Arknights(NewMessage):
enable_tag = False
enabled = True
is_common = False
- schedule_type = "interval"
- schedule_kw = {"seconds": 30}
+ scheduler_class = "arknights"
has_target = False
async def get_target_name(self, _: Target) -> str:
@@ -91,8 +97,7 @@ class AkVersion(StatusChange):
enable_tag = False
enabled = True
is_common = False
- schedule_type = "interval"
- schedule_kw = {"seconds": 30}
+ scheduler_class = "arknights"
has_target = False
async def get_target_name(self, _: Target) -> str:
@@ -147,8 +152,7 @@ class MonsterSiren(NewMessage):
enable_tag = False
enabled = True
is_common = False
- schedule_type = "interval"
- schedule_kw = {"seconds": 30}
+ scheduler_class = "arknights"
has_target = False
async def get_target_name(self, _: Target) -> str:
@@ -199,8 +203,7 @@ class TerraHistoricusComic(NewMessage):
enable_tag = False
enabled = True
is_common = False
- schedule_type = "interval"
- schedule_kw = {"seconds": 30}
+ scheduler_class = "arknights"
has_target = False
async def get_target_name(self, _: Target) -> str:
diff --git a/src/plugins/nonebot_bison/platform/bilibili.py b/src/plugins/nonebot_bison/platform/bilibili.py
index 56afd42..523f4b3 100644
--- a/src/plugins/nonebot_bison/platform/bilibili.py
+++ b/src/plugins/nonebot_bison/platform/bilibili.py
@@ -4,10 +4,16 @@ from typing import Any, Optional
from ..post import Post
from ..types import Category, RawPost, Tag, Target
-from ..utils import http_client
+from ..utils import SchedulerConfig, http_client
from .platform import CategoryNotSupport, NewMessage, StatusChange
+class BilibiliSchedConf(SchedulerConfig, name="bilibili.com"):
+
+ schedule_type = "interval"
+ schedule_setting = {"seconds": 10}
+
+
class Bilibili(NewMessage):
categories = {
@@ -22,8 +28,7 @@ class Bilibili(NewMessage):
enable_tag = True
enabled = True
is_common = True
- schedule_type = "interval"
- schedule_kw = {"seconds": 10}
+ scheduler_class = "bilibili.com"
name = "B站"
has_target = True
parse_target_promot = "请输入用户主页的链接"
@@ -167,8 +172,7 @@ class Bilibililive(StatusChange):
enable_tag = True
enabled = True
is_common = True
- schedule_type = "interval"
- schedule_kw = {"seconds": 10}
+ scheduler_class = "bilibili.com"
name = "Bilibili直播"
has_target = True
diff --git a/src/plugins/nonebot_bison/platform/ff14.py b/src/plugins/nonebot_bison/platform/ff14.py
index 0cbc92e..9f67b28 100644
--- a/src/plugins/nonebot_bison/platform/ff14.py
+++ b/src/plugins/nonebot_bison/platform/ff14.py
@@ -2,10 +2,16 @@ from typing import Any
from ..post import Post
from ..types import RawPost, Target
-from ..utils import http_client
+from ..utils import SchedulerConfig, http_client
from .platform import NewMessage
+class FF14SchedConf(SchedulerConfig, name="ff14"):
+
+ schedule_type = "interval"
+ schedule_setting = {"seconds": 60}
+
+
class FF14(NewMessage):
categories = {}
@@ -14,8 +20,7 @@ class FF14(NewMessage):
enable_tag = False
enabled = True
is_common = False
- schedule_type = "interval"
- schedule_kw = {"seconds": 60}
+ scheduler_class = "ff14"
has_target = False
async def get_target_name(self, _: Target) -> str:
diff --git a/src/plugins/nonebot_bison/platform/mcbbsnews.py b/src/plugins/nonebot_bison/platform/mcbbsnews.py
index f5d5844..8fc5c78 100644
--- a/src/plugins/nonebot_bison/platform/mcbbsnews.py
+++ b/src/plugins/nonebot_bison/platform/mcbbsnews.py
@@ -7,9 +7,16 @@ from bs4 import BeautifulSoup, NavigableString, Tag
from ..post import Post
from ..types import Category, RawPost, Target
+from ..utils import SchedulerConfig
from .platform import CategoryNotSupport, NewMessage
+class McbbsSchedConf(SchedulerConfig, name="mcbbs"):
+
+ schedule_type = "interval"
+ schedule_setting = {"hours": 1}
+
+
def _format_text(rawtext: str, mode: int) -> str:
"""处理BeautifulSoup生成的string中奇怪的回车+连续空格
mode 0:处理标题
@@ -38,12 +45,11 @@ class McbbsNews(NewMessage):
name = "MCBBS幻翼块讯"
enabled = True
is_common = False
- schedule_type = "interval"
- schedule_kw = {"hours": 1}
+ scheduler_class = "mcbbs"
has_target = False
async def get_target_name(self, _: Target) -> str:
- return f"{self.name} {self.categories[1]}"
+ return self.name
async def get_sub_list(self, _: Target) -> list[RawPost]:
url = "https://www.mcbbs.net/forum-news-1.html"
@@ -62,12 +68,37 @@ class McbbsNews(NewMessage):
return post_list
+ @staticmethod
+ def _format_text(rawtext: str, mode: int) -> str:
+ """处理BeautifulSoup生成的string中奇怪的回车+连续空格
+ mode 0:处理标题
+ mode 1:处理版本资讯类推文
+ mode 2:处理快讯类推文"""
+ if mode == 0:
+ ftext = re.sub(r"\n\s*", " ", rawtext)
+ elif mode == 1:
+ ftext = re.sub(r"[\n\s*]", "", rawtext)
+ elif mode == 2:
+ ftext = re.sub(r"\r\n", "", rawtext)
+ else:
+ raise NotImplementedError
+ return ftext
+
+ @staticmethod
+ def _stamp_date(rawdate: str) -> int:
+ """将时间转化为时间戳yyyy-mm-dd->timestamp"""
+ time_stamp = int(time.mktime(time.strptime(rawdate, "%Y-%m-%d")))
+ return time_stamp
+
def _gen_post_list(self, raw_post_list) -> list[RawPost]:
+ """解析生成推文列表"""
post_list = []
for raw_post in raw_post_list:
post = {}
post["url"] = raw_post.find("a", class_="s xst")["href"]
- post["title"] = _format_text(raw_post.find("a", class_="s xst").string, 0)
+ post["title"] = self._format_text(
+ raw_post.find("a", class_="s xst").string, 0
+ )
post["category"] = raw_post.select("th em a")[0].string
post["author"] = raw_post.select("td:nth-of-type(2) cite a")[0].string
post["id"] = raw_post["id"]
@@ -76,7 +107,7 @@ class McbbsNews(NewMessage):
if raw_post.select("td:nth-of-type(2) em span span")
else raw_post.select("td:nth-of-type(2) em span")[0].string
)
- post["date"] = _stamp_date(rawdate)
+ post["date"] = self._stamp_date(rawdate)
post_list.append(post)
return post_list
@@ -84,19 +115,20 @@ class McbbsNews(NewMessage):
return post["id"]
def get_date(self, post: RawPost) -> int:
+ # 获取datetime精度只到日期,故暂时舍弃
# return post["date"]
return None
def get_category(self, post: RawPost) -> Category:
- match post["category"]:
- case "Java版本资讯":
- return Category(1)
- case "基岩版本资讯":
- return Category(2)
- case _:
- raise CategoryNotSupport("McbbsNews订阅暂不支持 `{}".format(post["category"]))
+ if post["category"] == "Java版本资讯":
+ return Category(1)
+ elif post["category"] == "基岩版本资讯":
+ return Category(2)
+ else:
+ raise CategoryNotSupport("McbbsNews订阅暂不支持 `{}".format(post["category"]))
- def _check_str_chinese(self, check_str: str) -> bool:
+ @staticmethod
+ def _check_str_chinese(check_str: str) -> bool:
"""检测字符串是否含有中文(有一个就算)"""
for ch in check_str:
if "\u4e00" <= ch <= "\u9fff":
@@ -107,40 +139,40 @@ class McbbsNews(NewMessage):
"""提取Java/Bedrock版本资讯的推送消息"""
raw_soup = BeautifulSoup(raw_text.replace("
", ""), "html.parser")
# 获取头图
- match news_type:
- case "Java版本资讯":
- # 获取头图
- pic_tag = raw_soup.find(
- "img", file=re.compile(r"https://www.minecraft.net/\S*header.jpg")
- )
- pic_url: list[str] = (
- [pic_tag.get("src", pic_tag.get("file"))] if pic_tag else []
- )
- # 获取blockquote标签下的内容
- soup = raw_soup.find(
- "td", id=re.compile(r"postmessage_[0-9]*")
- ).blockquote.blockquote
- case "基岩版本资讯":
- # 获取头图
- pic_tag_0 = raw_soup.find(
- "img", file=re.compile(r"https://www.minecraft.net/\S*header.jpg")
- )
- pic_tag_1 = raw_soup.find(
- "img",
- file=re.compile(r"https://feedback.minecraft.net/\S*beta\S*.jpg"),
- )
- pic_url: list[str] = [
- pic_tag_0.get("src", pic_tag_0.get("file")) if pic_tag_0 else None,
- pic_tag_1.get("src", pic_tag_1.get("file")) if pic_tag_1 else None,
- ]
- # 获取blockquote标签下的内容
- soup = (
- raw_soup.find("td", id=re.compile(r"postmessage_[0-9]*"))
- .select("blockquote:nth-of-type(2)")[0]
- .blockquote
- )
- case _:
- raise CategoryNotSupport(f"该函数不支持处理{news_type}")
+ if news_type == "Java版本资讯":
+ # 获取头图
+ pic_tag = raw_soup.find(
+ "img", file=re.compile(r"https://www.minecraft.net/\S*header.jpg")
+ )
+ pic_url: list[str] = (
+ [pic_tag.get("src", pic_tag.get("file"))] if pic_tag else []
+ )
+ # 获取blockquote标签下的内容
+ soup = raw_soup.find(
+ "td", id=re.compile(r"postmessage_[0-9]*")
+ ).blockquote.blockquote
+ elif news_type == "基岩版本资讯":
+ # 获取头图
+ pic_tag_0 = raw_soup.find(
+ "img", file=re.compile(r"https://www.minecraft.net/\S*header.jpg")
+ )
+ pic_tag_1 = raw_soup.find(
+ "img",
+ file=re.compile(r"https://feedback.minecraft.net/\S*beta\S*.jpg"),
+ )
+ pic_url: list[str] = [
+ pic_tag_0.get("src", pic_tag_0.get("file")) if pic_tag_0 else None,
+ pic_tag_1.get("src", pic_tag_1.get("file")) if pic_tag_1 else None,
+ ]
+ # 获取blockquote标签下的内容
+ soup = (
+ raw_soup.find("td", id=re.compile(r"postmessage_[0-9]*"))
+ .select("blockquote:nth-of-type(2)")[0]
+ .blockquote
+ )
+ else:
+ raise CategoryNotSupport(f"该函数不支持处理{news_type}")
+
# 通用步骤
# 删除无用的div和span段内容
for del_tag in soup.find_all(["div", "span"]):
@@ -150,45 +182,43 @@ class McbbsNews(NewMessage):
# orig_info[0].extract()
# 展开所有的a,u和strong标签,展开ul,font标签里的font标签
for unwrap_tag in soup.find_all(["a", "strong", "u", "ul", "font"]):
- match unwrap_tag.name:
- case "a" | "strong" | "u": # 展开所有的a,u和strong标签
- unwrap_tag.unwrap()
- case "ul" | "font": # 展开ul,font里的font标签
- for font_tag in unwrap_tag.find_all("font"):
- font_tag.unwrap()
+ if unwrap_tag.name in ["a", "strong", "u"]: # 展开所有的a,u和strong标签
+ unwrap_tag.unwrap()
+ elif unwrap_tag.name in ["ul", "font"]: # 展开ul,font里的font标签
+ for font_tag in unwrap_tag.find_all("font"):
+ font_tag.unwrap()
# 获取所有的中文句子
post_text = ""
last_is_empty_line = True
for element in soup.contents:
if isinstance(element, Tag):
- match element.name:
- case "font":
+ if element.name == "font":
+ text = ""
+ for sub in element.contents:
+ if isinstance(sub, NavigableString):
+ text += sub
+ if self._check_str_chinese(text):
+ post_text += "{}\n".format(self._format_text(text, 1))
+ last_is_empty_line = False
+ elif element.name == "ul":
+ for li_tag in element.find_all("li"):
text = ""
- for sub in element.contents:
+ for sub in li_tag.contents:
if isinstance(sub, NavigableString):
text += sub
if self._check_str_chinese(text):
- post_text += "{}\n".format(_format_text(text, 1))
+ post_text += "{}\n".format(self._format_text(text, 1))
last_is_empty_line = False
- case "ul":
- for li_tag in element.find_all("li"):
- text = ""
- for sub in li_tag.contents:
- if isinstance(sub, NavigableString):
- text += sub
- if self._check_str_chinese(text):
- post_text += "{}\n".format(_format_text(text, 1))
- last_is_empty_line = False
- case _:
- continue
+ else:
+ continue
elif isinstance(element, NavigableString):
if str(element) == "\n":
if not last_is_empty_line:
post_text += "\n"
last_is_empty_line = True
else:
- post_text += "{}\n".format(_format_text(element, 1))
+ post_text += "{}\n".format(self._format_text(element, 1))
last_is_empty_line = False
else:
continue
@@ -211,7 +241,8 @@ class McbbsNews(NewMessage):
# 删除无用的span,div段内容
for del_tag in soup.find_all("i"):
del_tag.extract()
- soup.find(class_="attach_nopermission attach_tips").extract()
+ if extag := soup.find(class_="attach_nopermission attach_tips"):
+ extag.extract()
# 展开所有的a,strong标签
for unwrap_tag in soup.find_all(["a", "strong"]):
unwrap_tag.unwrap()
@@ -229,10 +260,11 @@ class McbbsNews(NewMessage):
else:
for string in soup.stripped_strings:
text += "{}\n".format(string)
- ftext = _format_text(text, 2)
+ ftext = self._format_text(text, 2)
return ftext, pic_urls
async def parse(self, raw_post: RawPost) -> Post:
+ """获取并分配正式推文交由相应的函数解析"""
post_url = "https://www.mcbbs.net/{}".format(raw_post["url"])
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) "
@@ -242,20 +274,14 @@ class McbbsNews(NewMessage):
async with httpx.AsyncClient() as client:
html = await client.get(post_url, headers=headers)
- match raw_post["category"]:
- case "Java版本资讯":
- # 事先删除不需要的尾部
- raw_text = re.sub(r"【本文排版借助了:[\s\S]*】", "", html.text)
- text, pic_urls = self._news_parser(raw_text, raw_post["category"])
- case "基岩版本资讯":
- raw_text = re.sub(r"【本文排版借助了:[\s\S]*】", "", html.text)
- text, pic_urls = self._news_parser(raw_text, raw_post["category"])
- case "快讯" | "基岩快讯" | "周边消息":
- text, pic_urls = self._express_parser(html.text, raw_post["category"])
- case _:
- raise CategoryNotSupport(
- "McbbsNews订阅暂不支持 `{}".format(raw_post["category"])
- )
+ if raw_post["category"] in ["Java版本资讯", "基岩版本资讯"]:
+ # 事先删除不需要的尾部
+ raw_text = re.sub(r"【本文排版借助了:[\s\S]*】", "", html.text)
+ text, pic_urls = self._news_parser(raw_text, raw_post["category"])
+ elif raw_post["category"] in ["快讯", "基岩快讯", "周边消息"]:
+ text, pic_urls = self._express_parser(html.text, raw_post["category"])
+ else:
+ raise CategoryNotSupport("McbbsNews订阅暂不支持 `{}".format(raw_post["category"]))
return Post(
self.name,
diff --git a/src/plugins/nonebot_bison/platform/ncm_artist.py b/src/plugins/nonebot_bison/platform/ncm_artist.py
index 00d329e..c98d4eb 100644
--- a/src/plugins/nonebot_bison/platform/ncm_artist.py
+++ b/src/plugins/nonebot_bison/platform/ncm_artist.py
@@ -3,10 +3,16 @@ from typing import Any, Optional
from ..post import Post
from ..types import RawPost, Target
-from ..utils import http_client
+from ..utils import SchedulerConfig, http_client
from .platform import NewMessage
+class NcmSchedConf(SchedulerConfig, name="music.163.com"):
+
+ schedule_type = "interval"
+ schedule_setting = {"minutes": 1}
+
+
class NcmArtist(NewMessage):
categories = {}
@@ -14,8 +20,7 @@ class NcmArtist(NewMessage):
enable_tag = False
enabled = True
is_common = True
- schedule_type = "interval"
- schedule_kw = {"minutes": 1}
+ scheduler_class = "music.163.com"
name = "网易云-歌手"
has_target = True
parse_target_promot = "请输入歌手主页(包含数字ID)的链接"
diff --git a/src/plugins/nonebot_bison/platform/ncm_radio.py b/src/plugins/nonebot_bison/platform/ncm_radio.py
index 14b439e..38d6967 100644
--- a/src/plugins/nonebot_bison/platform/ncm_radio.py
+++ b/src/plugins/nonebot_bison/platform/ncm_radio.py
@@ -14,8 +14,7 @@ class NcmRadio(NewMessage):
enable_tag = False
enabled = True
is_common = False
- schedule_type = "interval"
- schedule_kw = {"minutes": 10}
+ scheduler_class = "music.163.com"
name = "网易云-电台"
has_target = True
parse_target_promot = "请输入主播电台主页(包含数字ID)的链接"
diff --git a/src/plugins/nonebot_bison/platform/platform.py b/src/plugins/nonebot_bison/platform/platform.py
index 22c1200..4b19df5 100644
--- a/src/plugins/nonebot_bison/platform/platform.py
+++ b/src/plugins/nonebot_bison/platform/platform.py
@@ -39,8 +39,7 @@ class RegistryABCMeta(RegistryMeta, ABC):
class Platform(metaclass=RegistryABCMeta, base=True):
- schedule_type: Literal["date", "interval", "cron"]
- schedule_kw: dict
+ scheduler_class: str
is_common: bool
enabled: bool
name: str
@@ -136,9 +135,7 @@ class Platform(metaclass=RegistryABCMeta, base=True):
self, target: Target, new_posts: list[RawPost], users: list[UserSubInfo]
) -> list[tuple[User, list[Post]]]:
res: list[tuple[User, list[Post]]] = []
- for user, category_getter, tag_getter in users:
- required_tags = tag_getter(target) if self.enable_tag else []
- cats = category_getter(target)
+ for user, cats, required_tags in users:
user_raw_post = await self.filter_user_custom(
new_posts, cats, required_tags
)
@@ -332,11 +329,11 @@ class NoTargetGroup(Platform, abstract=True):
def __init__(self, platform_list: list[Platform]):
self.platform_list = platform_list
+ self.platform_name = platform_list[0].platform_name
name = self.DUMMY_STR
self.categories = {}
categories_keys = set()
- self.schedule_type = platform_list[0].schedule_type
- self.schedule_kw = platform_list[0].schedule_kw
+ self.scheduler_class = platform_list[0].scheduler_class
for platform in platform_list:
if platform.has_target:
raise RuntimeError(
@@ -355,10 +352,7 @@ class NoTargetGroup(Platform, abstract=True):
)
categories_keys |= platform_category_key_set
self.categories.update(platform.categories)
- if (
- platform.schedule_kw != self.schedule_kw
- or platform.schedule_type != self.schedule_type
- ):
+ if platform.scheduler_class != self.scheduler_class:
raise RuntimeError(
"Platform scheduler for {} not fit".format(self.platform_name)
)
diff --git a/src/plugins/nonebot_bison/platform/rss.py b/src/plugins/nonebot_bison/platform/rss.py
index ed09e8a..b5e7cc0 100644
--- a/src/plugins/nonebot_bison/platform/rss.py
+++ b/src/plugins/nonebot_bison/platform/rss.py
@@ -6,10 +6,16 @@ from bs4 import BeautifulSoup as bs
from ..post import Post
from ..types import RawPost, Target
-from ..utils import http_client
+from ..utils import SchedulerConfig, http_client
from .platform import NewMessage
+class RssSchedConf(SchedulerConfig, name="rss"):
+
+ schedule_type = "interval"
+ schedule_setting = {"seconds": 30}
+
+
class Rss(NewMessage):
categories = {}
@@ -18,8 +24,7 @@ class Rss(NewMessage):
name = "Rss"
enabled = True
is_common = True
- schedule_type = "interval"
- schedule_kw = {"seconds": 30}
+ scheduler_class = "rss"
has_target = True
async def get_target_name(self, target: Target) -> Optional[str]:
diff --git a/src/plugins/nonebot_bison/platform/weibo.py b/src/plugins/nonebot_bison/platform/weibo.py
index f7973a3..0749f28 100644
--- a/src/plugins/nonebot_bison/platform/weibo.py
+++ b/src/plugins/nonebot_bison/platform/weibo.py
@@ -8,10 +8,15 @@ from nonebot.log import logger
from ..post import Post
from ..types import *
-from ..utils import http_client
+from ..utils import SchedulerConfig, http_client
from .platform import NewMessage
+class WeiboSchedConf(SchedulerConfig, name="weibo.com"):
+ schedule_type = "interval"
+ schedule_setting = {"seconds": 3}
+
+
class Weibo(NewMessage):
categories = {
@@ -25,8 +30,7 @@ class Weibo(NewMessage):
name = "新浪微博"
enabled = True
is_common = True
- schedule_type = "interval"
- schedule_kw = {"seconds": 3}
+ scheduler_class = "weibo.com"
has_target = True
parse_target_promot = "请输入用户主页(包含数字UID)的链接"
diff --git a/src/plugins/nonebot_bison/scheduler/__init__.py b/src/plugins/nonebot_bison/scheduler/__init__.py
new file mode 100644
index 0000000..4fe6284
--- /dev/null
+++ b/src/plugins/nonebot_bison/scheduler/__init__.py
@@ -0,0 +1 @@
+from .manager import *
diff --git a/src/plugins/nonebot_bison/scheduler/aps.py b/src/plugins/nonebot_bison/scheduler/aps.py
new file mode 100644
index 0000000..fea4ff1
--- /dev/null
+++ b/src/plugins/nonebot_bison/scheduler/aps.py
@@ -0,0 +1,31 @@
+import logging
+
+from apscheduler.schedulers.asyncio import AsyncIOScheduler
+from nonebot.log import LoguruHandler
+
+from ..plugin_config import plugin_config
+from ..send import do_send_msgs
+
+aps = AsyncIOScheduler(timezone="Asia/Shanghai")
+
+
+class CustomLogHandler(LoguruHandler):
+ def filter(self, record: logging.LogRecord):
+ return record.msg != (
+ 'Execution of job "%s" '
+ "skipped: maximum number of running instances reached (%d)"
+ )
+
+
+if plugin_config.bison_use_queue:
+ aps.add_job(do_send_msgs, "interval", seconds=0.3, coalesce=True)
+
+ aps_logger = logging.getLogger("apscheduler")
+ aps_logger.setLevel(30)
+ aps_logger.handlers.clear()
+ aps_logger.addHandler(CustomLogHandler())
+
+
+def start_scheduler():
+ aps.configure({"apscheduler.timezone": "Asia/Shanghai"})
+ aps.start()
diff --git a/src/plugins/nonebot_bison/scheduler/manager.py b/src/plugins/nonebot_bison/scheduler/manager.py
new file mode 100644
index 0000000..7f332c4
--- /dev/null
+++ b/src/plugins/nonebot_bison/scheduler/manager.py
@@ -0,0 +1,43 @@
+from nonebot.log import logger
+
+from ..config import config
+from ..config.db_model import Target
+from ..platform import platform_manager
+from ..types import Target as T_Target
+from ..utils import SchedulerConfig
+from .scheduler import Scheduler
+
+scheduler_dict: dict[str, Scheduler] = {}
+_schedule_class_dict: dict[str, list[Target]] = {}
+
+
+async def init_scheduler():
+ for platform in platform_manager.values():
+ scheduler_class = platform.scheduler_class
+ platform_name = platform.platform_name
+ targets = await config.get_platform_target(platform_name)
+ if scheduler_class not in _schedule_class_dict:
+ _schedule_class_dict[scheduler_class] = targets
+ else:
+ _schedule_class_dict[scheduler_class].extend(targets)
+ for scheduler_class, target_list in _schedule_class_dict.items():
+ schedulable_args = []
+ for target in target_list:
+ schedulable_args.append((target.platform_name, T_Target(target.target)))
+ scheduler_dict[scheduler_class] = Scheduler(scheduler_class, schedulable_args)
+
+
+async def handle_insert_new_target(platform_name: str, target: T_Target):
+ platform = platform_manager[platform_name]
+ scheduler_obj = scheduler_dict[platform.scheduler_class]
+ scheduler_obj.insert_new_schedulable(platform_name, target)
+
+
+async def handle_delete_target(platform_name: str, target: T_Target):
+ platform = platform_manager[platform_name]
+ scheduler_obj = scheduler_dict[platform.scheduler_class]
+ scheduler_obj.delete_schedulable(platform_name, target)
+
+
+config.register_add_target_hook(handle_delete_target)
+config.register_delete_target_hook(handle_delete_target)
diff --git a/src/plugins/nonebot_bison/scheduler.py b/src/plugins/nonebot_bison/scheduler/old_sched.py
similarity index 98%
rename from src/plugins/nonebot_bison/scheduler.py
rename to src/plugins/nonebot_bison/scheduler/old_sched.py
index cf976c8..64ec4e4 100644
--- a/src/plugins/nonebot_bison/scheduler.py
+++ b/src/plugins/nonebot_bison/scheduler/old_sched.py
@@ -6,7 +6,7 @@ from nonebot import get_driver
from nonebot.adapters.onebot.v11.bot import Bot
from nonebot.log import LoguruHandler, logger
-from .config import Config
+from .config import config
from .platform import platform_manager
from .plugin_config import plugin_config
from .send import do_send_msgs, send_msgs
@@ -37,7 +37,6 @@ async def _start():
async def fetch_and_send(target_type: str):
- config = Config()
target = config.get_next_target(target_type)
if not target:
return
diff --git a/src/plugins/nonebot_bison/scheduler/scheduler.py b/src/plugins/nonebot_bison/scheduler/scheduler.py
new file mode 100644
index 0000000..39c703c
--- /dev/null
+++ b/src/plugins/nonebot_bison/scheduler/scheduler.py
@@ -0,0 +1,128 @@
+from dataclasses import dataclass
+from typing import Optional
+
+import nonebot
+from nonebot.adapters.onebot.v11.bot import Bot
+from nonebot.log import logger
+
+from ..config import config
+from ..platform import platform_manager
+from ..platform.platform import Platform
+from ..send import send_msgs
+from ..types import Target
+from ..utils import SchedulerConfig
+from .aps import aps
+
+
+@dataclass
+class Schedulable:
+ platform_name: str
+ target: Target
+ current_weight: int
+
+
+class Scheduler:
+
+ schedulable_list: list[Schedulable]
+
+ def __init__(self, name: str, schedulables: list[tuple[str, Target]]):
+ conf = SchedulerConfig.registry.get(name)
+ self.name = name
+ if not conf:
+ logger.error(f"scheduler config [{name}] not found, exiting")
+ raise RuntimeError(f"{name} not found")
+ self.scheduler_config = conf
+ self.schedulable_list = []
+ platform_name_set = set()
+ for platform_name, target in schedulables:
+ self.schedulable_list.append(
+ Schedulable(
+ platform_name=platform_name, target=target, current_weight=0
+ )
+ )
+ platform_name_set.add(platform_name)
+ self.platform_name_list = list(platform_name_set)
+ self.pre_weight_val = 0 # 轮调度中“本轮”增加权重和的初值
+ logger.info(
+ f"register scheduler for {name} with {self.scheduler_config.schedule_type} {self.scheduler_config.schedule_setting}"
+ )
+ aps.add_job(
+ self.exec_fetch,
+ self.scheduler_config.schedule_type,
+ **self.scheduler_config.schedule_setting,
+ )
+
+ async def get_next_schedulable(self) -> Optional[Schedulable]:
+ if not self.schedulable_list:
+ return None
+ cur_weight = await config.get_current_weight_val(self.platform_name_list)
+ weight_sum = self.pre_weight_val
+ self.pre_weight_val = 0
+ cur_max_schedulable = None
+ for schedulable in self.schedulable_list:
+ schedulable.current_weight += cur_weight[
+ f"{schedulable.platform_name}-{schedulable.target}"
+ ]
+ weight_sum += cur_weight[
+ f"{schedulable.platform_name}-{schedulable.target}"
+ ]
+ if (
+ not cur_max_schedulable
+ or cur_max_schedulable.current_weight < schedulable.current_weight
+ ):
+ cur_max_schedulable = schedulable
+ assert cur_max_schedulable
+ cur_max_schedulable.current_weight -= weight_sum
+ return cur_max_schedulable
+
+ async def exec_fetch(self):
+ if not (schedulable := await self.get_next_schedulable()):
+ return
+ logger.debug(
+ f"scheduler {self.name} fetching next target: [{schedulable.platform_name}]{schedulable.target}"
+ )
+ send_userinfo_list = await config.get_platform_target_subscribers(
+ schedulable.platform_name, schedulable.target
+ )
+ to_send = await platform_manager[schedulable.platform_name].do_fetch_new_post(
+ schedulable.target, send_userinfo_list
+ )
+ if not to_send:
+ return
+ bot = nonebot.get_bot()
+ assert isinstance(bot, Bot)
+ for user, send_list in to_send:
+ for send_post in send_list:
+ logger.info("send to {}: {}".format(user, send_post))
+ if not bot:
+ logger.warning("no bot connected")
+ else:
+ await send_msgs(
+ bot,
+ user.user,
+ user.user_type,
+ await send_post.generate_messages(),
+ )
+
+ def insert_new_schedulable(self, platform_name: str, target: Target):
+ self.pre_weight_val += 1000
+ self.schedulable_list.append(Schedulable(platform_name, target, 1000))
+ logger.info(
+ f"insert [{platform_name}]{target} to Schduler({self.scheduler_config.name})"
+ )
+
+ def delete_schedulable(self, platform_name, target: Target):
+ if not self.schedulable_list:
+ return
+ to_find_idx = None
+ for idx, schedulable in enumerate(self.schedulable_list):
+ if (
+ schedulable.platform_name == platform_name
+ and schedulable.target == target
+ ):
+ to_find_idx = idx
+ break
+ if to_find_idx is not None:
+ deleted_schdulable = self.schedulable_list.pop(to_find_idx)
+ self.pre_weight_val -= deleted_schdulable.current_weight
+ return
diff --git a/src/plugins/nonebot_bison/types.py b/src/plugins/nonebot_bison/types.py
index 734d877..d2f2b9a 100644
--- a/src/plugins/nonebot_bison/types.py
+++ b/src/plugins/nonebot_bison/types.py
@@ -13,7 +13,14 @@ class User:
user_type: Literal["group", "private"]
+@dataclass(eq=True, frozen=True)
+class PlatformTarget:
+ target: str
+ platform_name: str
+ target_name: str
+
+
class UserSubInfo(NamedTuple):
user: User
- category_getter: Callable[[Target], list[Category]]
- tag_getter: Callable[[Target], list[Tag]]
+ categories: list[Category]
+ tags: list[Tag]
diff --git a/src/plugins/nonebot_bison/utils/__init__.py b/src/plugins/nonebot_bison/utils/__init__.py
index 73c8baa..56b61e7 100644
--- a/src/plugins/nonebot_bison/utils/__init__.py
+++ b/src/plugins/nonebot_bison/utils/__init__.py
@@ -10,8 +10,9 @@ from nonebot.plugin import require
from ..plugin_config import plugin_config
from .http import http_client
+from .scheduler_config import SchedulerConfig
-__all__ = ["http_client", "Singleton", "parse_text", "html_to_text"]
+__all__ = ["http_client", "Singleton", "parse_text", "html_to_text", "SchedulerConfig"]
class Singleton(type):
diff --git a/src/plugins/nonebot_bison/utils/scheduler_config.py b/src/plugins/nonebot_bison/utils/scheduler_config.py
new file mode 100644
index 0000000..ce01c0b
--- /dev/null
+++ b/src/plugins/nonebot_bison/utils/scheduler_config.py
@@ -0,0 +1,17 @@
+from typing import Literal, Type
+
+
+class SchedulerConfig:
+
+ schedule_type: Literal["date", "interval", "cron"]
+ schedule_setting: dict
+ registry: dict[str, Type["SchedulerConfig"]] = {}
+ name: str
+
+ def __init_subclass__(cls, *, name, **kwargs):
+ super().__init_subclass__(**kwargs)
+ cls.registry[name] = cls
+ cls.name = name
+
+ def __str__(self):
+ return f"[{self.name}]-{self.name}-{self.schedule_setting}"
diff --git a/tests/test_config.py b/tests/config/test_config_legacy.py
similarity index 58%
rename from tests/test_config.py
rename to tests/config/test_config_legacy.py
index 1673309..42a430d 100644
--- a/tests/test_config.py
+++ b/tests/config/test_config_legacy.py
@@ -7,24 +7,24 @@ if typing.TYPE_CHECKING:
import sys
sys.path.append("./src/plugins")
- import nonebot_bison
- from nonebot_bison.config import Config
+ from nonebot_bison.config.config_legacy import Config
@pytest.fixture
-def config(app: App):
+def config_legacy(app: App, use_legacy_config):
from nonebot_bison import config
+ from nonebot_bison.config import config_legacy as config
config.start_up()
return config.Config()
-def test_create_and_get(config: "Config", app: App):
+def test_create_and_get(config_legacy: "Config", app: App):
from nonebot_bison import types
from nonebot_bison.types import Target
- config.add_subscribe(
- user="123",
+ config_legacy.add_subscribe(
+ user=123,
user_type="group",
target="weibo_id",
target_name="weibo_name",
@@ -32,14 +32,14 @@ def test_create_and_get(config: "Config", app: App):
cats=[],
tags=[],
)
- confs = config.list_subscribe("123", "group")
+ confs = config_legacy.list_subscribe(123, "group")
assert len(confs) == 1
- assert config.target_user_cache["weibo"][Target("weibo_id")] == [
- types.User("123", "group")
+ assert config_legacy.target_user_cache["weibo"][Target("weibo_id")] == [
+ types.User(123, "group")
]
assert confs[0]["cats"] == []
- config.update_subscribe(
- user="123",
+ config_legacy.update_subscribe(
+ user=123,
user_type="group",
target="weibo_id",
target_name="weibo_name",
@@ -47,6 +47,6 @@ def test_create_and_get(config: "Config", app: App):
cats=["1"],
tags=[],
)
- confs = config.list_subscribe("123", "group")
+ confs = config_legacy.list_subscribe(123, "group")
assert len(confs) == 1
assert confs[0]["cats"] == ["1"]
diff --git a/tests/config/test_config_operation.py b/tests/config/test_config_operation.py
new file mode 100644
index 0000000..b42dbf7
--- /dev/null
+++ b/tests/config/test_config_operation.py
@@ -0,0 +1,131 @@
+from nonebug.app import App
+from sqlalchemy.ext.asyncio.session import AsyncSession
+from sqlalchemy.sql.functions import func
+from sqlmodel.sql.expression import select
+
+
+async def test_add_subscribe(app: App, init_scheduler):
+
+ from nonebot_bison.config.db_config import config
+ from nonebot_bison.config.db_model import Subscribe, Target, User
+ from nonebot_bison.types import Target as TTarget
+ from nonebot_plugin_datastore.db import get_engine
+
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=TTarget("weibo_id"),
+ target_name="weibo_name",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+ await config.add_subscribe(
+ user=234,
+ user_type="group",
+ target=TTarget("weibo_id"),
+ target_name="weibo_name",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+ confs = await config.list_subscribe(123, "group")
+ assert len(confs) == 1
+ conf: Subscribe = confs[0]
+ async with AsyncSession(get_engine()) as sess:
+ related_user_obj = await sess.scalar(
+ select(User).where(User.id == conf.user_id)
+ )
+ related_target_obj = await sess.scalar(
+ select(Target).where(Target.id == conf.target_id)
+ )
+ assert related_user_obj.uid == 123
+ assert related_target_obj.target_name == "weibo_name"
+ assert related_target_obj.target == "weibo_id"
+ assert conf.target.target == "weibo_id"
+ assert conf.categories == []
+
+ await config.update_subscribe(
+ user=123,
+ user_type="group",
+ target=TTarget("weibo_id"),
+ platform_name="weibo",
+ target_name="weibo_name2",
+ cats=[1],
+ tags=["tag"],
+ )
+ confs = await config.list_subscribe(123, "group")
+ assert len(confs) == 1
+ conf: Subscribe = confs[0]
+ async with AsyncSession(get_engine()) as sess:
+ related_user_obj = await sess.scalar(
+ select(User).where(User.id == conf.user_id)
+ )
+ related_target_obj = await sess.scalar(
+ select(Target).where(Target.id == conf.target_id)
+ )
+ assert related_user_obj.uid == 123
+ assert related_target_obj.target_name == "weibo_name2"
+ assert related_target_obj.target == "weibo_id"
+ assert conf.target.target == "weibo_id"
+ assert conf.categories == [1]
+ assert conf.tags == ["tag"]
+
+
+async def test_del_subsribe(init_scheduler):
+ from nonebot_bison.config.db_config import config
+ from nonebot_bison.config.db_model import Subscribe, Target, User
+ from nonebot_bison.types import Target as TTarget
+ from nonebot_plugin_datastore.db import get_engine
+
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=TTarget("weibo_id"),
+ target_name="weibo_name",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+ await config.del_subscribe(
+ user=123,
+ user_type="group",
+ target=TTarget("weibo_id"),
+ platform_name="weibo",
+ )
+ async with AsyncSession(get_engine()) as sess:
+ assert (await sess.scalar(select(func.count()).select_from(Subscribe))) == 0
+ assert (await sess.scalar(select(func.count()).select_from(Target))) == 1
+
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=TTarget("weibo_id"),
+ target_name="weibo_name",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+
+ await config.add_subscribe(
+ user=124,
+ user_type="group",
+ target=TTarget("weibo_id"),
+ target_name="weibo_name_new",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+
+ await config.del_subscribe(
+ user=123,
+ user_type="group",
+ target=TTarget("weibo_id"),
+ platform_name="weibo",
+ )
+
+ async with AsyncSession(get_engine()) as sess:
+ assert (await sess.scalar(select(func.count()).select_from(Subscribe))) == 1
+ assert (await sess.scalar(select(func.count()).select_from(Target))) == 1
+ target: Target = await sess.scalar(select(Target))
+ assert target.target_name == "weibo_name_new"
diff --git a/tests/config/test_data_migration.py b/tests/config/test_data_migration.py
new file mode 100644
index 0000000..15732d0
--- /dev/null
+++ b/tests/config/test_data_migration.py
@@ -0,0 +1,56 @@
+import pytest
+
+
+async def test_migration(use_legacy_config):
+ from nonebot_bison.config.config_legacy import config as config_legacy
+ from nonebot_bison.config.db import data_migrate, upgrade_db
+ from nonebot_bison.config.db_config import config
+
+ config_legacy.add_subscribe(
+ user=123,
+ user_type="group",
+ target="weibo_id",
+ target_name="weibo_name",
+ target_type="weibo",
+ cats=[2, 3],
+ tags=[],
+ )
+ config_legacy.add_subscribe(
+ user=123,
+ user_type="group",
+ target="weibo_id2",
+ target_name="weibo_name2",
+ target_type="weibo",
+ cats=[1, 2],
+ tags=["tag"],
+ )
+ config_legacy.add_subscribe(
+ user=234,
+ user_type="group",
+ target="weibo_id",
+ target_name="weibo_name",
+ target_type="weibo",
+ cats=[1],
+ tags=[],
+ )
+ # await data_migrate()
+ await upgrade_db()
+ user123_config = await config.list_subscribe(123, "group")
+ assert len(user123_config) == 2
+ for c in user123_config:
+ if c.target.target == "weibo_id":
+ assert c.categories == [2, 3]
+ assert c.target.target_name == "weibo_name"
+ assert c.target.platform_name == "weibo"
+ assert c.tags == []
+ elif c.target.target == "weibo_id2":
+ assert c.categories == [1, 2]
+ assert c.target.target_name == "weibo_name2"
+ assert c.target.platform_name == "weibo"
+ assert c.tags == ["tag"]
+ user234_config = await config.list_subscribe(234, "group")
+ assert len(user234_config) == 1
+ assert user234_config[0].categories == [1]
+ assert user234_config[0].target.target == "weibo_id"
+ assert user234_config[0].target.target_name == "weibo_name"
+ assert user234_config[0].tags == []
diff --git a/tests/config/test_scheduler_conf.py b/tests/config/test_scheduler_conf.py
new file mode 100644
index 0000000..2dabf53
--- /dev/null
+++ b/tests/config/test_scheduler_conf.py
@@ -0,0 +1,218 @@
+from datetime import time
+
+from nonebug import App
+
+
+async def test_create_config(app: App, init_scheduler):
+ from nonebot_bison.config.db_config import TimeWeightConfig, WeightConfig, config
+ from nonebot_bison.config.db_model import Subscribe, Target, User
+ from nonebot_bison.types import Target as T_Target
+ from nonebot_plugin_datastore.db import get_engine
+
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=T_Target("weibo_id"),
+ target_name="weibo_name",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=T_Target("weibo_id1"),
+ target_name="weibo_name1",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+ await config.update_time_weight_config(
+ target=T_Target("weibo_id"),
+ platform_name="weibo",
+ conf=WeightConfig(
+ default=10,
+ time_config=[
+ TimeWeightConfig(start_time=time(1, 0), end_time=time(2, 0), weight=20)
+ ],
+ ),
+ )
+
+ test_config = await config.get_time_weight_config(
+ target=T_Target("weibo_id"), platform_name="weibo"
+ )
+ assert test_config.default == 10
+ assert test_config.time_config == [
+ TimeWeightConfig(start_time=time(1, 0), end_time=time(2, 0), weight=20)
+ ]
+ test_config1 = await config.get_time_weight_config(
+ target=T_Target("weibo_id1"), platform_name="weibo"
+ )
+ assert test_config1.default == 10
+ assert test_config1.time_config == []
+
+
+async def test_get_current_weight(app: App, init_scheduler):
+ from datetime import time
+
+ from nonebot_bison.config import db_config
+ from nonebot_bison.config.db_config import TimeWeightConfig, WeightConfig, config
+ from nonebot_bison.config.db_model import Subscribe, Target, User
+ from nonebot_bison.types import Target as T_Target
+ from nonebot_plugin_datastore.db import get_engine
+
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=T_Target("weibo_id"),
+ target_name="weibo_name",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=T_Target("weibo_id1"),
+ target_name="weibo_name1",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=T_Target("weibo_id1"),
+ target_name="weibo_name2",
+ platform_name="bilibili",
+ cats=[],
+ tags=[],
+ )
+ await config.update_time_weight_config(
+ target=T_Target("weibo_id"),
+ platform_name="weibo",
+ conf=WeightConfig(
+ default=10,
+ time_config=[
+ TimeWeightConfig(start_time=time(1, 0), end_time=time(2, 0), weight=20),
+ TimeWeightConfig(start_time=time(4, 0), end_time=time(5, 0), weight=30),
+ ],
+ ),
+ )
+ app.monkeypatch.setattr(db_config, "_get_time", lambda: time(1, 30))
+ weight = await config.get_current_weight_val(["weibo", "bilibili"])
+ assert len(weight) == 3
+ assert weight["weibo-weibo_id"] == 20
+ assert weight["weibo-weibo_id1"] == 10
+ assert weight["bilibili-weibo_id1"] == 10
+ app.monkeypatch.setattr(db_config, "_get_time", lambda: time(4, 0))
+ weight = await config.get_current_weight_val(["weibo", "bilibili"])
+ assert len(weight) == 3
+ assert weight["weibo-weibo_id"] == 30
+ assert weight["weibo-weibo_id1"] == 10
+ assert weight["bilibili-weibo_id1"] == 10
+ app.monkeypatch.setattr(db_config, "_get_time", lambda: time(5, 0))
+ weight = await config.get_current_weight_val(["weibo", "bilibili"])
+ assert len(weight) == 3
+ assert weight["weibo-weibo_id"] == 10
+ assert weight["weibo-weibo_id1"] == 10
+ assert weight["bilibili-weibo_id1"] == 10
+
+
+async def test_get_platform_target(app: App, init_scheduler):
+ from nonebot_bison.config import db_config
+ from nonebot_bison.config.db_config import TimeWeightConfig, WeightConfig, config
+ from nonebot_bison.config.db_model import Subscribe, Target, User
+ from nonebot_bison.types import Target as T_Target
+ from nonebot_plugin_datastore.db import get_engine
+ from sqlalchemy.ext.asyncio.session import AsyncSession
+ from sqlalchemy.sql.expression import select
+
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=T_Target("weibo_id"),
+ target_name="weibo_name",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=T_Target("weibo_id1"),
+ target_name="weibo_name1",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+ await config.add_subscribe(
+ user=245,
+ user_type="group",
+ target=T_Target("weibo_id1"),
+ target_name="weibo_name1",
+ platform_name="weibo",
+ cats=[],
+ tags=[],
+ )
+ res = await config.get_platform_target("weibo")
+ assert len(res) == 2
+ await config.del_subscribe(123, "group", T_Target("weibo_id1"), "weibo")
+ res = await config.get_platform_target("weibo")
+ assert len(res) == 2
+ await config.del_subscribe(123, "group", T_Target("weibo_id"), "weibo")
+ res = await config.get_platform_target("weibo")
+ assert len(res) == 1
+
+ async with AsyncSession(get_engine()) as sess:
+ res = await sess.scalars(select(Target).where(Target.platform_name == "weibo"))
+ assert len(res.all()) == 2
+
+
+async def test_get_platform_target_subscribers(app: App, init_scheduler):
+ from nonebot_bison.config import db_config
+ from nonebot_bison.config.db_config import TimeWeightConfig, WeightConfig, config
+ from nonebot_bison.config.db_model import Subscribe, Target, User
+ from nonebot_bison.types import Target as T_Target
+ from nonebot_bison.types import User as T_User
+ from nonebot_bison.types import UserSubInfo
+ from nonebot_plugin_datastore.db import get_engine
+ from sqlalchemy.ext.asyncio.session import AsyncSession
+ from sqlalchemy.sql.expression import select
+
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=T_Target("weibo_id"),
+ target_name="weibo_name",
+ platform_name="weibo",
+ cats=[1],
+ tags=["tag1"],
+ )
+ await config.add_subscribe(
+ user=123,
+ user_type="group",
+ target=T_Target("weibo_id1"),
+ target_name="weibo_name1",
+ platform_name="weibo",
+ cats=[2],
+ tags=["tag2"],
+ )
+ await config.add_subscribe(
+ user=245,
+ user_type="group",
+ target=T_Target("weibo_id1"),
+ target_name="weibo_name1",
+ platform_name="weibo",
+ cats=[3],
+ tags=["tag3"],
+ )
+
+ res = await config.get_platform_target_subscribers("weibo", T_Target("weibo_id"))
+ assert len(res) == 1
+ assert res[0] == UserSubInfo(T_User(123, "group"), [1], ["tag1"])
+
+ res = await config.get_platform_target_subscribers("weibo", T_Target("weibo_id1"))
+ assert len(res) == 2
+ assert UserSubInfo(T_User(123, "group"), [2], ["tag2"]) in res
+ assert UserSubInfo(T_User(245, "group"), [3], ["tag3"]) in res
diff --git a/tests/conftest.py b/tests/conftest.py
index 18a0691..c76550b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -5,6 +5,8 @@ from pathlib import Path
import nonebot
import pytest
from nonebug.app import App
+from sqlalchemy.ext.asyncio.session import AsyncSession
+from sqlalchemy.sql.expression import delete
@pytest.fixture
@@ -12,7 +14,10 @@ async def app(nonebug_init: None, tmp_path: Path, monkeypatch: pytest.MonkeyPatc
import nonebot
config = nonebot.get_driver().config
- config.bison_config_path = str(tmp_path)
+ config.bison_config_path = str(tmp_path / "legacy_config")
+ config.datastore_config_dir = str(tmp_path / "config")
+ config.datastore_cache_dir = str(tmp_path / "cache")
+ config.datastore_data_dir = str(tmp_path / "data")
config.command_start = {""}
config.superusers = {"10001"}
config.log_level = "TRACE"
@@ -25,23 +30,37 @@ def dummy_user_subinfo(app: App):
from nonebot_bison.types import User, UserSubInfo
user = User(123, "group")
- return UserSubInfo(user=user, category_getter=lambda _: [], tag_getter=lambda _: [])
+ return UserSubInfo(user=user, categories=[], tags=[])
@pytest.fixture
-def task_watchdog(request):
- def cancel_test_on_exception(task: asyncio.Task):
- def maybe_cancel_clbk(t: asyncio.Task):
- exception = t.exception()
- if exception is None:
- return
+async def db_migration(app: App):
+ from nonebot_bison.config.db import upgrade_db
+ from nonebot_bison.config.db_model import Subscribe, Target, User
+ from nonebot_plugin_datastore.db import get_engine
- for task in asyncio.all_tasks():
- coro = task.get_coro()
- if coro.__qualname__ == request.function.__qualname__:
- task.cancel()
- return
+ await upgrade_db()
+ async with AsyncSession(get_engine()) as sess:
+ await sess.execute(delete(User))
+ await sess.execute(delete(Subscribe))
+ await sess.execute(delete(Target))
+ await sess.commit()
+ await sess.close()
- task.add_done_callback(maybe_cancel_clbk)
- return cancel_test_on_exception
+@pytest.fixture
+async def init_scheduler(db_migration):
+ from nonebot_bison.scheduler.manager import init_scheduler
+
+ await init_scheduler()
+
+
+@pytest.fixture
+async def use_legacy_config(app: App):
+ import aiofiles
+ from nonebot_bison.config.config_legacy import config, get_config_path
+
+ async with aiofiles.open(get_config_path(), "w") as f:
+ await f.write("{}")
+
+ config._do_init()
diff --git a/tests/platforms/static/mcbbsnews/mcbbsnews_raw_post_list.json b/tests/platforms/static/mcbbsnews/mcbbsnews_raw_post_list.json
new file mode 100644
index 0000000..7c2f166
--- /dev/null
+++ b/tests/platforms/static/mcbbsnews/mcbbsnews_raw_post_list.json
@@ -0,0 +1,218 @@
+[
+ {
+ "url": "thread-1340080-1-1.html",
+ "title": "Mojang Status:服务器出现一些小问题",
+ "category": "快讯",
+ "author": "DreamVoid",
+ "id": "normalthread_1340080",
+ "date": 1652630400
+ },
+ {
+ "url": "thread-1339940-1-1.html",
+ "title": "kinbdogz 就近期荒野更新的风波发表看法",
+ "category": "快讯",
+ "author": "卡狗",
+ "id": "normalthread_1339940",
+ "date": 1652630400
+ },
+ {
+ "url": "thread-1339097-1-1.html",
+ "title": "Minecraft 基岩版 1.18.33 发布(仅 Switch)",
+ "category": "基岩版本资讯",
+ "author": "电量量",
+ "id": "normalthread_1339097",
+ "date": 1652457600
+ },
+ {
+ "url": "thread-1338607-1-1.html",
+ "title": "Minecraft Java版 22w19a 发布",
+ "category": "Java版本资讯",
+ "author": "寂华",
+ "id": "normalthread_1338607",
+ "date": 1652371200
+ },
+ {
+ "url": "thread-1338592-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.32/33 发布",
+ "category": "基岩版本资讯",
+ "author": "苦力怕553",
+ "id": "normalthread_1338592",
+ "date": 1652371200
+ },
+ {
+ "url": "thread-1338588-1-1.html",
+ "title": "请给我们一个真正的“荒野更新”",
+ "category": "时评",
+ "author": "斯乌",
+ "id": "normalthread_1338588",
+ "date": 1652371200
+ },
+ {
+ "url": "thread-1338496-1-1.html",
+ "title": "slicedlime:周三无快照,推迟至周四",
+ "category": "快讯",
+ "author": "橄榄Chan",
+ "id": "normalthread_1338496",
+ "date": 1652198400
+ },
+ {
+ "url": "thread-1336371-1-1.html",
+ "title": "Minecraft 基岩版 1.18.32 发布(仅 Android、NS)【新增 NS 平台】",
+ "category": "基岩版本资讯",
+ "author": "电量量",
+ "id": "normalthread_1336371",
+ "date": 1651766400
+ },
+ {
+ "url": "thread-1335897-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.30/31 发布",
+ "category": "基岩版本资讯",
+ "author": "AzureZeng",
+ "id": "normalthread_1335897",
+ "date": 1651680000
+ },
+ {
+ "url": "thread-1335891-1-1.html",
+ "title": "Minecraft Java版 22w18a 发布",
+ "category": "Java版本资讯",
+ "author": "Aurora_Feather",
+ "id": "normalthread_1335891",
+ "date": 1651680000
+ },
+ {
+ "url": "thread-1333196-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.28/29 发布",
+ "category": "基岩版本资讯",
+ "author": "希铁石z",
+ "id": "normalthread_1333196",
+ "date": 1651161600
+ },
+ {
+ "url": "thread-1332834-1-1.html",
+ "title": "Minecraft 基岩版 1.18.31 发布",
+ "category": "基岩版本资讯",
+ "author": "希铁石z",
+ "id": "normalthread_1332834",
+ "date": 1651075200
+ },
+ {
+ "url": "thread-1332811-1-1.html",
+ "title": "Minecraft Java版 22w17a 发布",
+ "category": "Java版本资讯",
+ "author": "卡狗",
+ "id": "normalthread_1332811",
+ "date": 1651075200
+ },
+ {
+ "url": "thread-1332424-1-1.html",
+ "title": "Mojang Status:正在寻找1.18.30更新问题的解决方案",
+ "category": "基岩快讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1332424",
+ "date": 1650988800
+ },
+ {
+ "url": "thread-1329712-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.26/27 发布",
+ "category": "基岩版本资讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1329712",
+ "date": 1650470400
+ },
+ {
+ "url": "thread-1329651-1-1.html",
+ "title": "Minecraft Java版 22w16b 发布",
+ "category": "Java版本资讯",
+ "author": "卡狗",
+ "id": "normalthread_1329651",
+ "date": 1650470400
+ },
+ {
+ "url": "thread-1329644-1-1.html",
+ "title": "Minecraft Java版 22w16a 发布",
+ "category": "Java版本资讯",
+ "author": "希铁石z",
+ "id": "normalthread_1329644",
+ "date": 1650470400
+ },
+ {
+ "url": "thread-1329335-1-1.html",
+ "title": "Minecraft 基岩版 1.18.30 发布",
+ "category": "基岩版本资讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1329335",
+ "date": 1650384000
+ },
+ {
+ "url": "thread-1328892-1-1.html",
+ "title": "“海王” 杰森·莫玛 有望主演《我的世界》大电影",
+ "category": "快讯",
+ "author": "广药",
+ "id": "normalthread_1328892",
+ "date": 1650297600
+ },
+ {
+ "url": "thread-1327089-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.24/25 发布",
+ "category": "基岩版本资讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1327089",
+ "date": 1649952000
+ },
+ {
+ "url": "thread-1326640-1-1.html",
+ "title": "Minecraft Java版 22w15a 发布",
+ "category": "Java版本资讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1326640",
+ "date": 1649865600
+ },
+ {
+ "url": "thread-1323762-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.20 发布",
+ "category": "基岩版本资讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1323762",
+ "date": 1649260800
+ },
+ {
+ "url": "thread-1323662-1-1.html",
+ "title": "Minecraft Java版 22w14a 发布",
+ "category": "Java版本资讯",
+ "author": "卡狗",
+ "id": "normalthread_1323662",
+ "date": 1649260800
+ },
+ {
+ "url": "thread-1321419-1-1.html",
+ "title": "[愚人节] Minecraft Java版 22w13oneBlockAtATime 发布",
+ "category": "Java版本资讯",
+ "author": "希铁石z",
+ "id": "normalthread_1321419",
+ "date": 1648742400
+ },
+ {
+ "url": "thread-1320986-1-1.html",
+ "title": "Minecraft:近期没有为主机平台添加光线追踪的计划",
+ "category": "基岩快讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1320986",
+ "date": 1648742400
+ },
+ {
+ "url": "thread-1320931-1-1.html",
+ "title": "Minecraft Java版 22w13a 发布",
+ "category": "Java版本资讯",
+ "author": "卡狗",
+ "id": "normalthread_1320931",
+ "date": 1648742400
+ },
+ {
+ "url": "thread-1342236-1-1.html",
+ "title": "Minecraft: 加入Microsoft Rewards赢取限量Xbox Series S",
+ "category": "周边消息",
+ "author": "ETW_Derp",
+ "id": "normalthread_1342236",
+ "date": 1648742400
+ }
+]
diff --git a/tests/platforms/static/mcbbsnews/mcbbsnews_raw_post_list_update.json b/tests/platforms/static/mcbbsnews/mcbbsnews_raw_post_list_update.json
new file mode 100644
index 0000000..6229393
--- /dev/null
+++ b/tests/platforms/static/mcbbsnews/mcbbsnews_raw_post_list_update.json
@@ -0,0 +1,218 @@
+[
+ {
+ "url": "thread-1340927-1-1.html",
+ "title": "Minecraft Java版 1.19-pre1 发布",
+ "category": "Java版本资讯",
+ "author": "希铁石z",
+ "id": "normalthread_1340927",
+ "date": 1652889600
+ },
+ {
+ "url": "thread-1340080-1-1.html",
+ "title": "Mojang Status:服务器出现一些小问题",
+ "category": "快讯",
+ "author": "DreamVoid",
+ "id": "normalthread_1340080",
+ "date": 1652630400
+ },
+ {
+ "url": "thread-1339940-1-1.html",
+ "title": "kinbdogz 就近期荒野更新的风波发表看法",
+ "category": "快讯",
+ "author": "卡狗",
+ "id": "normalthread_1339940",
+ "date": 1652630400
+ },
+ {
+ "url": "thread-1339097-1-1.html",
+ "title": "Minecraft 基岩版 1.18.33 发布(仅 Switch)",
+ "category": "基岩版本资讯",
+ "author": "电量量",
+ "id": "normalthread_1339097",
+ "date": 1652457600
+ },
+ {
+ "url": "thread-1338607-1-1.html",
+ "title": "Minecraft Java版 22w19a 发布",
+ "category": "Java版本资讯",
+ "author": "寂华",
+ "id": "normalthread_1338607",
+ "date": 1652371200
+ },
+ {
+ "url": "thread-1338592-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.32/33 发布",
+ "category": "基岩版本资讯",
+ "author": "苦力怕553",
+ "id": "normalthread_1338592",
+ "date": 1652371200
+ },
+ {
+ "url": "thread-1338588-1-1.html",
+ "title": "请给我们一个真正的“荒野更新”",
+ "category": "时评",
+ "author": "斯乌",
+ "id": "normalthread_1338588",
+ "date": 1652371200
+ },
+ {
+ "url": "thread-1338496-1-1.html",
+ "title": "slicedlime:周三无快照,推迟至周四",
+ "category": "快讯",
+ "author": "橄榄Chan",
+ "id": "normalthread_1338496",
+ "date": 1652198400
+ },
+ {
+ "url": "thread-1336371-1-1.html",
+ "title": "Minecraft 基岩版 1.18.32 发布(仅 Android、NS)【新增 NS 平台】",
+ "category": "基岩版本资讯",
+ "author": "电量量",
+ "id": "normalthread_1336371",
+ "date": 1651766400
+ },
+ {
+ "url": "thread-1335897-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.30/31 发布",
+ "category": "基岩版本资讯",
+ "author": "AzureZeng",
+ "id": "normalthread_1335897",
+ "date": 1651680000
+ },
+ {
+ "url": "thread-1335891-1-1.html",
+ "title": "Minecraft Java版 22w18a 发布",
+ "category": "Java版本资讯",
+ "author": "Aurora_Feather",
+ "id": "normalthread_1335891",
+ "date": 1651680000
+ },
+ {
+ "url": "thread-1333196-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.28/29 发布",
+ "category": "基岩版本资讯",
+ "author": "希铁石z",
+ "id": "normalthread_1333196",
+ "date": 1651161600
+ },
+ {
+ "url": "thread-1332834-1-1.html",
+ "title": "Minecraft 基岩版 1.18.31 发布",
+ "category": "基岩版本资讯",
+ "author": "希铁石z",
+ "id": "normalthread_1332834",
+ "date": 1651075200
+ },
+ {
+ "url": "thread-1332811-1-1.html",
+ "title": "Minecraft Java版 22w17a 发布",
+ "category": "Java版本资讯",
+ "author": "卡狗",
+ "id": "normalthread_1332811",
+ "date": 1651075200
+ },
+ {
+ "url": "thread-1332424-1-1.html",
+ "title": "Mojang Status:正在寻找1.18.30更新问题的解决方案",
+ "category": "基岩快讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1332424",
+ "date": 1650988800
+ },
+ {
+ "url": "thread-1329712-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.26/27 发布",
+ "category": "基岩版本资讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1329712",
+ "date": 1650470400
+ },
+ {
+ "url": "thread-1329651-1-1.html",
+ "title": "Minecraft Java版 22w16b 发布",
+ "category": "Java版本资讯",
+ "author": "卡狗",
+ "id": "normalthread_1329651",
+ "date": 1650470400
+ },
+ {
+ "url": "thread-1329644-1-1.html",
+ "title": "Minecraft Java版 22w16a 发布",
+ "category": "Java版本资讯",
+ "author": "希铁石z",
+ "id": "normalthread_1329644",
+ "date": 1650470400
+ },
+ {
+ "url": "thread-1329335-1-1.html",
+ "title": "Minecraft 基岩版 1.18.30 发布",
+ "category": "基岩版本资讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1329335",
+ "date": 1650384000
+ },
+ {
+ "url": "thread-1328892-1-1.html",
+ "title": "“海王” 杰森·莫玛 有望主演《我的世界》大电影",
+ "category": "快讯",
+ "author": "广药",
+ "id": "normalthread_1328892",
+ "date": 1650297600
+ },
+ {
+ "url": "thread-1327089-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.24/25 发布",
+ "category": "基岩版本资讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1327089",
+ "date": 1649952000
+ },
+ {
+ "url": "thread-1326640-1-1.html",
+ "title": "Minecraft Java版 22w15a 发布",
+ "category": "Java版本资讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1326640",
+ "date": 1649865600
+ },
+ {
+ "url": "thread-1323762-1-1.html",
+ "title": "Minecraft 基岩版 Beta & Preview 1.19.0.20 发布",
+ "category": "基岩版本资讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1323762",
+ "date": 1649260800
+ },
+ {
+ "url": "thread-1323662-1-1.html",
+ "title": "Minecraft Java版 22w14a 发布",
+ "category": "Java版本资讯",
+ "author": "卡狗",
+ "id": "normalthread_1323662",
+ "date": 1649260800
+ },
+ {
+ "url": "thread-1321419-1-1.html",
+ "title": "[愚人节] Minecraft Java版 22w13oneBlockAtATime 发布",
+ "category": "Java版本资讯",
+ "author": "希铁石z",
+ "id": "normalthread_1321419",
+ "date": 1648742400
+ },
+ {
+ "url": "thread-1320986-1-1.html",
+ "title": "Minecraft:近期没有为主机平台添加光线追踪的计划",
+ "category": "基岩快讯",
+ "author": "ArmorRush",
+ "id": "normalthread_1320986",
+ "date": 1648742400
+ },
+ {
+ "url": "thread-1320931-1-1.html",
+ "title": "Minecraft Java版 22w13a 发布",
+ "category": "Java版本资讯",
+ "author": "卡狗",
+ "id": "normalthread_1320931",
+ "date": 1648742400
+ }
+]
diff --git a/tests/platforms/static/mcbbsnews/mock/mcbbsnews_bedrock_express.html b/tests/platforms/static/mcbbsnews/mock/mcbbsnews_bedrock_express.html
new file mode 100644
index 0000000..669c750
--- /dev/null
+++ b/tests/platforms/static/mcbbsnews/mock/mcbbsnews_bedrock_express.html
@@ -0,0 +1,4406 @@
+
+
+
+
+
+ 1406
+
+
+ |
+
+
+
+ 12
+
+
+
+ |
+
+
+ + + [基岩快讯] + + + Mojang Status:正在寻找1.18.30更新问题的解决方案 + ++ + |
+
+ | ++ | +
+
+
+
+
+
+
+ ArmorRush
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ArmorRush
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+
+
+
+
+
+
+ + + + 评分 ++
|
+ ||||||||||||||
+
+
+
+
+
+ 帖子永久链接:
+
+
+
+
+
+ |
+ |||||||||||||||
+ | ++ + | +||||||||||||||
+ | ++ | +
+
+
+
+
+
+
+ xq23455
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xq23455
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ ArmorRush
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ArmorRush
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ moshui662
+
+
+
+
+
+
+
+
+
+
+
+
+
+ moshui662
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 5
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ Mplan_
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mplan_
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 6
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ AzureZeng
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AzureZeng
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 7
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ wosun117
+
+
+
+
+
+
+
+
+
+
+
+
+
+ wosun117
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ TS_剑雨星辰
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TS_剑雨星辰
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 9
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ TS_剑雨星辰
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TS_剑雨星辰
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+ + | +
+
+
+
+
+ 12
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+ + | +
+
+
+
+
+ 13
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+ Archiver
+
+
+ |
+
+
+ 小黑屋
+
+
+ |
+
+
+
+ Mcbbs.net
+
+
+ (
+
+ 京ICP备15023768号-1
+
+ ) |
+
+
+ 京公网安备 11010502037624号
+
+ |
+
+
+ 手机版
+
+
+
+ GMT+8, 2022-5-22 22:58 + + , Processed in 0.049426 second(s), Total 25, Slave 24 queries, Release: Build.2022.05.20 1436, Gzip On, Redis On. + + +
++ " + + Minecraft + + "以及"我的世界"为美国微软公司的商标 本站与微软公司没有从属关系 +
++ © 2010-2022 + + 我的世界中文论坛 + + 版权所有 本站内原创内容版权属于其原创作者,除作者或版规特别声明外未经许可不得转载 +
+
+
+ 2940| 7
+
+ |
+
+
+ + [基岩版本资讯] + Minecraft 基岩版 Beta & Preview 1.19.0.32/33 + 发布 ++ + |
+
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 评分+ + |
+ |||||||||
+
+
+
+
+ 帖子永久链接:
+ + + 呐呐,
+ + 要不要来看看我的 + B站主页 + |
+ ||||||||||
+ | + + | +|||||||||
+ | ++ | +
+ + | ++ + + + | +
+ | +|
+ | + + | +
+ | ++ | +
+ + | ++ + + + | +
+ | +|
+ | + + | +
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+ 评分+ + |
+ |
+ 呐呐,
+ + 要不要来看看我的 + B站主页 + |
+ ||
+ | + + | +|
+ | ++ | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ a760243820
+ 当前离线
+
+
+
+
+
+
+
+ 头像被屏蔽
+ + + + +
|
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | + + | +|
+ | ++ | +
+ + | ++ + + + | +
+ + | +|
+ | + + | +
+ | ++ | +
+ Archiver|小黑屋|Mcbbs.net
+ ( 京ICP备15023768号-1 ) | 京公网安备
+ 11010502037624号 |
+ 手机版
+
+
+
+ GMT+8, 2022-6-5 14:01 + , Processed in 0.145592 second(s), Total 34, Slave 33 queries, Release: Build.2022.05.30 1905, + Gzip On, Redis On. + + +
+"Minecraft"以及"我的世界"为美国微软公司的商标 本站与微软公司没有从属关系 +
+© 2010-2022 我的世界中文论坛 版权所有 + 本站内原创内容版权属于其原创作者,除作者或版规特别声明外未经许可不得转载
+
+
+1612| 3
+
+ |
+
+
++[快讯] +(已恢复)Mojang Status:服务器出现一些小问题 ++ + |
+
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+评分+
| |||||||||||||||
+ 帖子永久链接: |
+||||||||||||||||
+ | + + | +|||||||||||||||
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+ | |
+ | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+ | |
+ | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+ | |
+ | +||
+ | + + | +|
+ | ++ | +
+Archiver|小黑屋|Mcbbs.net
+( 京ICP备15023768号-1 ) | 京公网安备 11010502037624号 | 手机版
+
+
+
+GMT+8, 2022-6-5 15:00 +, Processed in 0.086316 second(s), Total 31, Slave 29 queries, Release: Build.2022.05.30 1905, Gzip On, Redis On. + + +
+"Minecraft"以及"我的世界"为美国微软公司的商标 本站与微软公司没有从属关系
+© 2010-2022 我的世界中文论坛 版权所有 本站内原创内容版权属于其原创作者,除作者或版规特别声明外未经许可不得转载
+
+
+ 5774| 47
+
+ |
+
+
+ + [Java版本资讯] + Minecraft Java版 22w19a 发布 ++ + |
+
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 评分+
|
+ |||||||||||||||||||||||||||||||||||||||||||||
+
+
+
+
+ 帖子永久链接:
+ + + |
+ ||||||||||||||||||||||||||||||||||||||||||||||
+ | + + | +|||||||||||||||||||||||||||||||||||||||||||||
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | + + | +|
+ | ++ | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Burning_snow
+ 当前离线
+
+
+ + + + + ++ + +
|
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ ![]() + + 本帖最后由 阴阳师元素祭祀 于 1分钟前 关闭 + + [1.14.4/MOD]噔噔咚MOD + + + + [1.15x~1.16.x | 64x] + Simple 3D + / + Shark + + + ![]() |
+ ||
+ | + + | +|
+ | ++ | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + +
|
+
+
+
+
+
+
+
+
+
+
+
+
+ 评分+
|
+ |||||||
+ + | +||||||||
+ | + + | +|||||||
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+
+ 起床战争守床老艺术家
+ + 著名阴间学家,阴乐霉术大师 ![]() + + 我的wiki + |我的空间 + 累计挖矿消耗金粒:600|累计致负卡消耗金粒:1500 + |累计挖掘卡消耗金粒:1060 + + + |举 报净获得金粒:1005 + + 拒绝以做任务为理由的好友申请 + + + 拒绝六级及以下不加理由的好友申请 + + ![]() |
+ ||
+ | + + | +|
+ | ++ | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ miller8887090
+ 当前在线
+
+
+ + + + + + + +
|
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ + | +||
+ | + + | +|
+ | ++ | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xiao_qi_zi
+ 当前离线
+
+
+ + + + + + + +
|
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ ![]() ![]() |
+ ||
+ | + + | +|
+ | ++ | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + +
|
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ + | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ + | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+ 评分+ + |
+ |
+ + | +||
+ | + + | +|
+ | ++ | +
+ Archiver|小黑屋|Mcbbs.net
+ ( 京ICP备15023768号-1 ) | 京公网安备
+ 11010502037624号 |
+ 手机版
+
+
+
+ GMT+8, 2022-6-5 13:58 + , Processed in 0.163479 second(s), Total 44, Slave 42 queries, Release: Build.2022.05.30 1905, + Gzip On, Redis On. + + +
+"Minecraft"以及"我的世界"为美国微软公司的商标 本站与微软公司没有从属关系 +
+© 2010-2022 我的世界中文论坛 版权所有 + 本站内原创内容版权属于其原创作者,除作者或版规特别声明外未经许可不得转载
+
+
+996| 8
+
+ |
+
+
++[周边消息] +Minecraft: 加入Microsoft Rewards赢取限量Xbox Series S ++ + |
+
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+评分+ + | ||
+ 帖子永久链接:
+ ![]() + ![]() + |
+|||
+ | + + | +||
+ | ++ | +
+ + | ++ + + |
+ 搬运 | 资源包 | 这些萌萌哒的怪物你一定喜欢! > 在 MCBBS V4 查看 >
++搬运 | 汉化 | 地图 | 让我们去海岛游玩放松放松 > 在 MCBBS V4 查看 > + +访问我的个人 Blog > + + +以上文字都可以戳awa |
+|
+ | + + | +
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+ | |
+ | +||
+ | + + | +|
+ | ++ | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+a136569113
+当前离线
+
+
+ + + + + + + +
|
+
+
+
+
+
+
+
+
+
+ | |
+ 有开心的工作就做到死吧
+ |
+||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+ | |
+ 仰羡黄昏鸟,投林羽翮轻。
++ ——杜甫《独坐》 + + ![]() |
+||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+ | |
+ + | +||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+ | |
+ 有鸡的地方就会有影鼠捏,,,
+ |
+||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+ | |
+![]() + |
+||
+ | + + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+ | |
+ + | +||
+ | + + | +|
+ | ++ | +
+Archiver|小黑屋|Mcbbs.net
+( 京ICP备15023768号-1 ) | 京公网安备 11010502037624号 | 手机版
+
+
+
+GMT+8, 2022-6-5 14:28 +, Processed in 0.115639 second(s), Total 34, Slave 33 queries, Release: Build.2022.05.30 1905, Gzip On, Redis On. + + +
+"Minecraft"以及"我的世界"为美国微软公司的商标 本站与微软公司没有从属关系
+© 2010-2022 我的世界中文论坛 版权所有 本站内原创内容版权属于其原创作者,除作者或版规特别声明外未经许可不得转载
+
+
+
+ 1622
+
+
+ |
+
+
+
+ 26
+
+
+
+ |
+
+
+ + + [Java版本资讯] + + + Minecraft Java版 1.19-pre1 发布 + ++ + |
+
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+ + + + 评分 ++
|
+ |||||||||||||||||||||||||||||||||
+
+
+
+
+
+ 帖子永久链接:
+
+
+
+
+
+ |
+ ||||||||||||||||||||||||||||||||||
+ | ++ + | +|||||||||||||||||||||||||||||||||
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+ + + + 评分 ++
|
+ |||||||
+ | +||||||||
+ | ++ + | +|||||||
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+ + | +
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ AzureZeng
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AzureZeng
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 5
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ 10935336
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10935336
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 6
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ 164ebr
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 164ebr
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 7
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ 橄榄Chan
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 橄榄Chan
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 8
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ 春枫微微倾城
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 春枫微微倾城
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 9
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+ + | +
+
+
+
+
+ 10
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ 117779284
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 117779284
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 11
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ miller8887090
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 12
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ mztnql9gz
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mztnql9gz
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 13
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+ + | +
+
+
+
+
+ 14
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+
+
+
+
+
+ xiao_qi_zi
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xiao_qi_zi
+
+
+
+ 当前离线
+
+
+
+
+
+ ![]()
+
+
+
+
+
+
+
+
+ ![]() |
+
+
+
+
+
+ 15
+
+
+ #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ | +||
+ | ++ + | +|
+ | ++ | +
+
+ Archiver
+
+
+ |
+
+
+ 小黑屋
+
+
+ |
+
+
+
+ Mcbbs.net
+
+
+ (
+
+ 京ICP备15023768号-1
+
+ ) |
+
+
+ 京公网安备 11010502037624号
+
+ |
+
+
+ 手机版
+
+
+
+ GMT+8, 2022-5-20 00:11 + + , Processed in 0.065029 second(s), Total 28, Slave 27 queries, Release: Build.2022.05.18 1107, + Gzip On, Redis On. + + +
++ " + + Minecraft + + "以及"我的世界"为美国微软公司的商标 本站与微软公司没有从属关系 +
++ © 2010-2022 + + 我的世界中文论坛 + + 版权所有 本站内原创内容版权属于其原创作者,除作者或版规特别声明外未经许可不得转载 +
+
+ ![]() |
+
+ 识海漫谈+ + |
+ + 1789 + | ++ + | +
+ Archiver|小黑屋|Mcbbs.net
+ ( 京ICP备15023768号-1 ) | 京公网安备
+ 11010502037624号 |
+ 手机版
+
+
+
+ GMT+8, 2022-5-20 00:15 + , Processed in 0.057567 second(s), Total 10, Slave 10 queries, Release: Build.2022.05.18 1107, + Gzip On, Redis On. + + +
+"Minecraft"以及"我的世界"为美国微软公司的商标 本站与微软公司没有从属关系 +
+© 2010-2022 我的世界中文论坛 版权所有 + 本站内原创内容版权属于其原创作者,除作者或版规特别声明外未经许可不得转载
+
+ ![]() |
+
+ 识海漫谈+ + |
+ + 1789 + | ++ + | +
+ Archiver|小黑屋|Mcbbs.net
+ ( 京ICP备15023768号-1 ) | 京公网安备
+ 11010502037624号 |
+ 手机版
+
+
+
+ GMT+8, 2022-5-20 00:15 + , Processed in 0.057567 second(s), Total 10, Slave 10 queries, Release: Build.2022.05.18 1107, + Gzip On, Redis On. + + +
+"Minecraft"以及"我的世界"为美国微软公司的商标 本站与微软公司没有从属关系 +
+© 2010-2022 我的世界中文论坛 版权所有 + 本站内原创内容版权属于其原创作者,除作者或版规特别声明外未经许可不得转载
+