diff --git a/.github/workflows/docker-build-and-publish.yml b/.github/workflows/docker-build-and-publish.yml index 688ef7f642..1f35156357 100644 --- a/.github/workflows/docker-build-and-publish.yml +++ b/.github/workflows/docker-build-and-publish.yml @@ -1,9 +1,9 @@ -name: Docker Build and Publish +name: "Docker Build and Publish (3:00, UTC+8)" on: - push: - branches: [master] - + schedule: + - cron: "0 3 * * *" + workflow_dispatch: jobs: build-and-push: runs-on: ubuntu-latest diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml new file mode 100644 index 0000000000..c9ddb91a5d --- /dev/null +++ b/.github/workflows/nightly-build.yml @@ -0,0 +1,39 @@ +name: "Nightly Build and Release (3:00, UTC+8)" + +on: + schedule: + - cron: '0 3 * * *' + workflow_dispatch: + +permissions: + contents: write + +jobs: + nightly: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Delete and recreate nightly tag + run: | + if git rev-parse refs/tags/nightly >/dev/null 2>&1; then + git tag -d nightly + git push origin --delete nightly + fi + git tag nightly + git push origin nightly + + - name: Update release version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + release_exists=$(gh release list --json tagName --jq '.[] | select(.tagName=="nightly") | .tagName') + if [ -n "$release_exists" ]; then + gh release delete nightly -y + fi + gh release create nightly \ + --title "Nightly Release" \ + --notes "> 这是 Nightly 版本,由于无法及时同步代码和验证版本的稳定性,本项目已不再提供正式版本。请点击 Assets -> Source code 下载当前最新源代码。" diff --git a/.gitignore b/.gitignore index 698ac4df01..406968a9d0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,10 @@ config/*.toml* test* *.db cache/* +assets/arcaea* +assets/maimai* assets/modules/arcaea +assets/arc.apk assets/modules/maimai* !assets/modules/maimai/mai_utage_info.json !assets/modules/maimai/mai_grade_info.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b219f77a4a..d8544b3a44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,14 +8,4 @@ repos: - repo: https://github.com/pre-commit/mirrors-autopep8 rev: "v2.0.4" hooks: - - id: autopep8 - - - repo: local - hooks: - - id: auto-generate-config - name: Auto-generate config - entry: poetry run python ./core/scripts/config_generate.py - language: python - pass_filenames: false - - + - id: autopep8 \ No newline at end of file diff --git a/assets/config_store/en_us/bot_api.toml b/assets/config_store/en_us/bot_api.toml index b4a89e255c..2524ea76f6 100644 --- a/assets/config_store/en_us/bot_api.toml +++ b/assets/config_store/en_us/bot_api.toml @@ -4,7 +4,9 @@ [bot_api_secret] # The secret config section of the platform. The bot will try to intercept if the value here accidentally appears in the message sent, but be careful to prevent leakage. -jwt_secret = "" # The authentication key for the built-in API, it can be filled in at will. +api_access_token = "" # Access Token used to request the API service. +api_allow_origins = ["*"] # The API service allows CORS sources list. +jwt_secret = "" # The authentication key for the built-in API, for signing and verifying validity. [bot_api] # The basic config section of the platform. The value ​​filled in here can be displayed in the message. Please do not fill in sensitive information in this section. diff --git a/assets/config_store/en_us/bot_qqbot.toml b/assets/config_store/en_us/bot_qqbot.toml index 027492b99f..0bb4575a5b 100644 --- a/assets/config_store/en_us/bot_qqbot.toml +++ b/assets/config_store/en_us/bot_qqbot.toml @@ -6,7 +6,7 @@ # The basic config section of the platform. The value ​​filled in here can be displayed in the message. Please do not fill in sensitive information in this section. qq_bot_appid = "" # The QQ official robot AppID. enable = false # Whether to enable this platform. -qq_private_bot = false # Whether it is a private robot when using the QQ official robot. +qq_private_bot = false # Whether it is a private robot when using the QQ official bot. qq_bot_enable_send_url = false # Whether to enable sending URLs when using QQ official robot. qq_typing_emoji = 181 # When using QQ related protocol terminals, the response emoji ID attached to the message when processing the message. It needs to be supported by the protocol terminal. For details of emoji ID, see: https://bot.q.qq.com/wiki/develop/api/openapi/emoji/model.html#Emoji diff --git a/assets/config_store/en_us/config.toml b/assets/config_store/en_us/config.toml index 8c2af14049..d9644260ef 100644 --- a/assets/config_store/en_us/config.toml +++ b/assets/config_store/en_us/config.toml @@ -57,7 +57,7 @@ dice_output_len = 200 # The maximum length output of dice module. dice_roll_limit = 10 # The maximum number of dice rolled per command of dice module. dice_detail_count = 5 # The total number of dice rolled n times of dice module, it will no longer show details when this value is exceeded. dice_count_limit = 10 # The maximum number of items in dice expressions of dice module. -ncmusic_enable_card = false # Whether enables card messages of ncmusic module. (Only valid on QQ platform) +ncmusic_enable_card = false # Whether enables card messages of ncmusic module. (Only valid on QQ client) wiki_whitelist_url = "" # Wiki whitelist application URL. wordle_disable_image = false # Whether to disable images of wordle module. slower_schedule = false # Whether to enable a slower scheduled task scheduler. (To reduce request pressure) @@ -77,4 +77,4 @@ wolfram_alpha_appid = "" # WolframAlpha AppID. (For w diving_fish_developer_token = "" # Developer token for Diving Fish prober. (For maimai module) curseforge_api_key = "" # CurseForge API key. (For mod_dl module) ncmusic_api = "" # API address of NetEase Cloud Music API. (For ncmusic module) -osu_api_key = "" # osu! API Key (for osu module) +osu_api_key = "" # Osu! API Key (for osu module) diff --git a/assets/config_store/zh_cn/bot_api.toml b/assets/config_store/zh_cn/bot_api.toml index c879b46dee..d7e48ff102 100644 --- a/assets/config_store/zh_cn/bot_api.toml +++ b/assets/config_store/zh_cn/bot_api.toml @@ -4,7 +4,9 @@ [bot_api_secret] # 平台端的密钥配置部分,此处的值若意外出现在发送的消息中,机器人会尝试拦截。但请务必提防泄露。 -jwt_secret = "" # 内置 API 的认证密钥,随意填写即可。 +api_access_token = "" # 请求 API 服务的 Access Token。 +api_allow_origins = ["*"] # API 服务允许 CORS 的源列表。 +jwt_secret = "" # 内置 API 的身份认证密钥,用于签名和验证有效性。 [bot_api] # 平台端的配置部分,此处填写的值可在消息中以明文形式展示。请不要在此部分填写敏感信息。 diff --git a/assets/config_store/zh_cn/config.toml b/assets/config_store/zh_cn/config.toml index 77136e9b2a..d1292b7c86 100644 --- a/assets/config_store/zh_cn/config.toml +++ b/assets/config_store/zh_cn/config.toml @@ -57,7 +57,7 @@ dice_output_len = 200 # dice 模块输出最大长度。 dice_roll_limit = 10 # dice 模块一次命令投掷次数上限。 dice_detail_count = 5 # dice 模块 n 次投掷的骰子的总量超过该值时将不再显示详细信息。 dice_count_limit = 10 # dice 模块骰子表达式项数上限。 -ncmusic_enable_card = false # ncmusic 模块是否启用卡片消息。(仅在 QQ 平台有效) +ncmusic_enable_card = false # ncmusic 模块是否启用卡片消息。(仅在 QQ 客户端有效) wiki_whitelist_url = "" # Wiki 白名单申请网址。 wordle_disable_image = false # wordle 模块是否禁用图片。 slower_schedule = false # 是否启用更慢的计划任务调度器。(减少请求压力用) diff --git a/assets/config_store/zh_tw/bot_api.toml b/assets/config_store/zh_tw/bot_api.toml index abd835d404..d0c3eb84ad 100644 --- a/assets/config_store/zh_tw/bot_api.toml +++ b/assets/config_store/zh_tw/bot_api.toml @@ -4,7 +4,9 @@ [bot_api_secret] # 平台端的金鑰設定部分,此處的值若意外出現在傳送的訊息中,機器人會嘗試攔截。但請務必提防洩漏。 -jwt_secret = "" # 內建 API 的認證金鑰,隨意填寫即可。 +api_access_token = "" # 請求 API 服務的 Access Token。 +api_allow_origins = ["*"] # API 服務允许 CORS 的來源列表。 +jwt_secret = "" # 內建 API 的身份認證金鑰,用於簽署和驗證有效性。 [bot_api] # 平台端的設定部分,此處填寫的值可在訊息中以明文顯示。請不要在此部分填寫敏感資訊。 diff --git a/assets/config_store/zh_tw/config.toml b/assets/config_store/zh_tw/config.toml index ea0a47f760..685898be1b 100644 --- a/assets/config_store/zh_tw/config.toml +++ b/assets/config_store/zh_tw/config.toml @@ -35,7 +35,7 @@ enable_get_petal = false # 是否允許取得花瓣。 gained_petal_limit = 0 # 單日取得花瓣上限。 lost_petal_limit = 0 # 單日失去花瓣上限。 use_secrets_random = false # 是否使用基於 secrets 庫的隨機數產生器。 -web_render_local = "" # 本地 WebRender 位址。 +web_render_local = "" # 本地 WebRender 服務位址。 enable_langsmith = "" langsmith_endpoint = "" langsmith_project = "" @@ -57,7 +57,7 @@ dice_output_len = 200 # dice 模組輸出最大長度。 dice_roll_limit = 10 # dice 模組一次指令投擲次數上限。 dice_detail_count = 5 # dice 模組 n 次投擲的骰子的總量超過該值時將不再顯示詳細資訊。 dice_count_limit = 10 # dice 模組骰子表示式項數上限。 -ncmusic_enable_card = false # ncmusic 模組是否啟用卡片訊息。(僅在 QQ 平台有效) +ncmusic_enable_card = false # ncmusic 模組是否啟用卡片訊息。(僅在 QQ 用戶端有效) wiki_whitelist_url = "" # Wiki 白名單申請網址。 wordle_disable_image = false # wordle 模組是否停用圖片。 slower_schedule = false # 是否啟用更慢的排程任務調度器。(減少請求壓力用) diff --git a/assets/config_store_packed/en_us.zip b/assets/config_store_packed/en_us.zip index 55b1ec5619..02bdd7841c 100644 Binary files a/assets/config_store_packed/en_us.zip and b/assets/config_store_packed/en_us.zip differ diff --git a/assets/config_store_packed/zh_cn.zip b/assets/config_store_packed/zh_cn.zip index b1bc15648d..cfbbe2c9e9 100644 Binary files a/assets/config_store_packed/zh_cn.zip and b/assets/config_store_packed/zh_cn.zip differ diff --git a/assets/config_store_packed/zh_tw.zip b/assets/config_store_packed/zh_tw.zip index 90e69be32b..4d03492158 100644 Binary files a/assets/config_store_packed/zh_tw.zip and b/assets/config_store_packed/zh_tw.zip differ diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000000..6ef8eee3d9 Binary files /dev/null and b/assets/favicon.ico differ diff --git a/bot.py b/bot.py index 932470e948..5eb267f14b 100644 --- a/bot.py +++ b/bot.py @@ -26,7 +26,7 @@ "aiogram": ["telegram_token"], "kook": ["kook_token"], "matrix": ["matrix_homeserver", "matrix_user", "matrix_device_id", "matrix_token"], - "api": [], + "api": ["jwt_secret", "api_allow_origins"], "qqbot": ["qq_bot_appid", "qq_bot_secret"], } @@ -98,7 +98,7 @@ def multiprocess_run_until_complete(func): p.close() -def go(bot_name: str = None, subprocess: bool = False, binary_mode: bool = False): +def go(bot_name: str, subprocess: bool = False, binary_mode: bool = False): from core.logger import Logger # noqa from core.utils.info import Info # noqa diff --git a/bots/api/bot.py b/bots/api/bot.py index 25d64fa228..8be6ea3cc9 100644 --- a/bots/api/bot.py +++ b/bots/api/bot.py @@ -1,11 +1,18 @@ import os +import platform import sys import time +import uuid +from datetime import datetime, timedelta, UTC import jwt import uvicorn -from fastapi import FastAPI, Request -from fastapi.responses import JSONResponse +from argon2 import PasswordHasher +from fastapi import FastAPI, Request, HTTPException, Depends +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import FileResponse +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from fastapi.staticfiles import StaticFiles from bots.api.info import client_name from core.queue import JobQueue @@ -15,7 +22,9 @@ sys.path.append(os.getcwd()) from core.bot_init import init_async # noqa: E402 +from core.builtins import PrivateAssets # noqa: E402 from core.config import Config # noqa: E402 +from core.constants.path import assets_path # noqa: E402 from core.database import BotDBUtil # noqa: E402 from core.extra.scheduler import load_extra_schedulers # noqa: E402 from core.loader import ModulesManager # noqa: E402 @@ -23,9 +32,38 @@ from core.utils.i18n import Locale # noqa: E402 from modules.wiki.utils.dbutils import WikiTargetInfo # noqa: E402 +started_time = datetime.now() +PrivateAssets.set(os.path.join(assets_path, "private", "api")) +ACCESS_TOKEN = Config("api_access_token", cfg_type=str, secret=True, table_name="bot_api") +ALLOW_ORIGINS = Config("api_allow_origins", default=['*'], cfg_type=(str, list), secret=True, table_name="bot_api") +JWT_SECRET = Config("jwt_secret", cfg_type=str, secret=True, table_name="bot_api") +PASSWORD_PATH = os.path.join(PrivateAssets.path, ".password") -app = FastAPI() -jwt_secret = Config("jwt_secret", cfg_type=str, secret=True, table_name="bot_api") + +def verify_access_token(request: Request): + if request.url.path == "/favicon.ico": + return + if not ACCESS_TOKEN: + return + + auth_header = request.headers.get("Authorization") + if not auth_header or auth_header != f"Bearer {ACCESS_TOKEN}": + raise HTTPException(status_code=403, detail="forbidden") + + +app = FastAPI(dependencies=[Depends(verify_access_token)]) +ph = PasswordHasher() + + +app.add_middleware( + CORSMiddleware, + allow_origins=ALLOW_ORIGINS, + allow_credentials=True, + allow_methods=["GET", "POST"], + allow_headers=["*"], +) + +app.mount("/assets", StaticFiles(directory="assets"), name="assets") @app.on_event("startup") @@ -37,19 +75,106 @@ async def startup_event(): await JobQueue.web_render_status() -@app.get("/auth/{token}") -async def auth(token: str): +@app.get("/favicon.ico", response_class=FileResponse) +async def favicon(): + favicon_path = os.path.join(assets_path, "favicon.ico") + return FileResponse(favicon_path) + + +@app.get("/") +async def main(): + return {"message": "Hello AkariBot!"} + + +@app.post("/auth") +async def auth(request: Request): try: - return jwt.decode(token, jwt_secret, algorithms=["HS256"]) - except jwt.InvalidSignatureError: - return JSONResponse(status_code=403, content={"token": token, "invalid": True}) + if not os.path.exists(PASSWORD_PATH): + return {"message": "success"} + + body = await request.json() + password = body["password"] + remember = body.get("remember", False) + + with open(PASSWORD_PATH, "r") as file: + stored_password = file.read().strip() + + try: + ph.verify(stored_password, password) # 验证输入的密码是否与存储的哈希匹配 + except Exception: + raise HTTPException(status_code=401, detail="invalid password") + + payload = { + "device_id": str(uuid.uuid4()), + "exp": datetime.now(UTC) + (timedelta(days=365) if remember else timedelta(hours=24)), # 过期时间 + "iat": datetime.now(UTC), # 签发时间 + "iss": "auth-api" # 签发者 + } + jwt_token = jwt.encode(payload, JWT_SECRET, algorithm='HS256') + + return {"message": "success", "deviceToken": jwt_token} + + except HTTPException as e: + raise e + except Exception as e: + Logger.error(str(e)) + raise HTTPException(status_code=400, detail="bad request") + + +@app.post("/verify-token") +async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(HTTPBearer())): + try: + token = credentials.credentials + payload = jwt.decode(token, JWT_SECRET, algorithm='HS256') + return payload + except jwt.ExpiredSignatureError: + raise HTTPException(status_code=401, detail="token expired") + except jwt.InvalidTokenError: + raise HTTPException(status_code=401, detail="invalid token") + + +@app.post("/change-password") +async def change_password(request: Request): + try: + body = await request.json() + new_password = body.get("new_password", "") + password = body.get("password", "") + + # 读取旧密码 + if not os.path.exists(PASSWORD_PATH): + if new_password == "": + raise HTTPException(status_code=400, detail="new password required") + new_password_hashed = ph.hash(new_password) + with open(PASSWORD_PATH, "w") as file: + file.write(new_password_hashed) + return {"message": "success"} + + with open(PASSWORD_PATH, "r") as file: + stored_password = file.read().strip() + + try: + ph.verify(stored_password, password) + except Exception: + raise HTTPException(status_code=401, detail="invalid password") + + # 设置新密码 + new_password_hashed = ph.hash(new_password) + with open(PASSWORD_PATH, "w") as file: + file.write(new_password_hashed) + + return {"message": "success"} + except HTTPException as e: + raise e + except Exception as e: + Logger.error(str(e)) + raise HTTPException(status_code=400, detail="bad request") @app.get("/target/{target_id}") async def get_target(target_id: str): target = BotDBUtil.TargetInfo(target_id) if not target.query: - return JSONResponse(status_code=404, content={"detail": "Not Found"}) + return HTTPException(status_code=404, detail="not found") enabled_modules = target.enabled_modules is_muted = target.is_muted custom_admins = target.custom_admins @@ -80,7 +205,7 @@ async def get_target(target_id: str): async def get_sender(sender_id: str): sender = BotDBUtil.SenderInfo(sender_id) if not sender.query: - return JSONResponse(status_code=404, content={"detail": "Not Found"}) + return HTTPException(status_code=404, detail="not found") return { "senderId": sender_id, @@ -102,7 +227,7 @@ async def get_module_list(): async def get_target_modules(target_id: str): target_data = BotDBUtil.TargetInfo(target_id) if not target_data.query: - return JSONResponse(status_code=404, content={"detail": "Not Found"}) + return HTTPException(status_code=404, detail="not found") target_from = "|".join(target_id.split("|")[:-2]) modules = ModulesManager.return_modules_list(target_from=target_from) enabled_modules = target_data.enabled_modules @@ -117,7 +242,7 @@ async def enable_modules(target_id: str, request: Request): try: target_data = BotDBUtil.TargetInfo(target_id) if not target_data.query: - return JSONResponse(status_code=404, content={"detail": "Not Found"}) + return HTTPException(status_code=404, detail="not found") target_from = "|".join(target_id.split("|")[:-2]) body = await request.json() @@ -130,18 +255,19 @@ async def enable_modules(target_id: str, request: Request): ] target_data.enable(modules) return {"message": "success"} - except Exception: - return JSONResponse( - status_code=400, content={"detail": "Bad Request", "message": "error"} - ) + except HTTPException as e: + raise e + except Exception as e: + Logger.error(str(e)) + return HTTPException(status_code=400, detail="bad request") @app.post("/modules/{target_id}/disable") -async def enable_modules(target_id: str, request: Request): +async def disable_modules(target_id: str, request: Request): try: target_data = BotDBUtil.TargetInfo(target_id) if not target_data.query: - return JSONResponse(status_code=404, content={"detail": "Not Found"}) + return HTTPException(status_code=404, detail="not found") target_from = "|".join(target_id.split("|")[:-2]) body = await request.json() @@ -154,11 +280,11 @@ async def enable_modules(target_id: str, request: Request): ] target_data.disable(modules) return {"message": "success"} + except HTTPException as e: + raise e except Exception as e: Logger.error(str(e)) - return JSONResponse( - status_code=400, content={"detail": "Bad Request", "message": "error"} - ) + return HTTPException(status_code=400, detail="bad request") @app.get("/locale/{locale}/{string}") @@ -170,7 +296,7 @@ async def get_locale(locale: str, string: str): "translation": Locale(locale).t(string, False), } except TypeError: - return JSONResponse(status_code=404, content={"detail": "Not Found"}) + return HTTPException(status_code=404, detail="not found") if (__name__ == "__main__" or Info.subprocess) and Config( diff --git a/bots/discord/slash_message.py b/bots/discord/slash_message.py index 45ba32f364..ab8b7d8f11 100644 --- a/bots/discord/slash_message.py +++ b/bots/discord/slash_message.py @@ -101,18 +101,6 @@ async def send_message( return FinishedSession(self, msg_ids, send) - async def check_permission(self): - if ( - self.session.message.channel.permissions_for( - self.session.message.author - ).administrator - or isinstance(self.session.message.channel, discord.DMChannel) - or self.info.is_super_user - or self.info.check_TargetAdmin(self.target.target_id) - ): - return True - return False - async def check_native_permission(self): if self.session.message.channel.permissions_for( self.session.message.author diff --git a/core/builtins/message/__init__.py b/core/builtins/message/__init__.py index acf9ae7b74..dc24dcbd62 100644 --- a/core/builtins/message/__init__.py +++ b/core/builtins/message/__init__.py @@ -2,7 +2,8 @@ import asyncio from datetime import datetime, UTC as datetimeUTC -from typing import Any, Coroutine, Dict, List, Optional, Union +from re import Match +from typing import Any, Coroutine, Dict, List, Optional, Tuple, Union from core.builtins.message.chain import * from core.builtins.message.elements import MessageElement @@ -195,6 +196,7 @@ def __init__(self, target: MsgInfo, session: Session): self.session = session self.sent: List[MessageChain] = [] self.trigger_msg: Optional[str] = None + self.matched_msg: Optional[Union[Match[str], Tuple[Any]]] = None self.parsed_msg: Optional[dict] = None self.prefixes: List[str] = [] self.data = exports.get("BotDBUtil").TargetInfo(self.target.target_id) diff --git a/core/database/link.py b/core/database/link.py new file mode 100644 index 0000000000..01af3d4d87 --- /dev/null +++ b/core/database/link.py @@ -0,0 +1,19 @@ +import os + +from core.config import Config +from core.constants import db_path_default, database_path + +db_link = Config("db_path", default=db_path_default, secret=True) +db_path = database_path + +db_type = db_link.split("://")[0].split("+")[0] +if db_type == "sqlite": + db_path = os.path.dirname(db_link.replace("sqlite:///", "")) +os.makedirs(db_path, exist_ok=True) + + +def get_db_link(orm_type="sqlalchemy"): + if orm_type == "sqlalchemy": + return db_link + if orm_type == "tortoise": + return db_type + "://" + db_link.split("://")[1] diff --git a/core/database/orm.py b/core/database/orm.py index 1aec509257..c95653ea57 100644 --- a/core/database/orm.py +++ b/core/database/orm.py @@ -1,26 +1,15 @@ -import os - from sqlalchemy import create_engine from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker from sqlalchemy.orm import sessionmaker -from core.config import Config -from core.constants.default import db_path_default -from core.constants.path import database_path +from core.database.link import db_link from core.database.orm_base import Base -DB_LINK = Config("db_path", default=db_path_default, secret=True) - -db_path = database_path -if DB_LINK.startswith("sqlite:///"): - db_path = os.path.dirname(DB_LINK.replace("sqlite:///", "")) -os.makedirs(db_path, exist_ok=True) - class DBSession: def __init__(self): self.engine = create_engine( - DB_LINK, isolation_level="READ UNCOMMITTED", pool_pre_ping=True + db_link, isolation_level="READ UNCOMMITTED", pool_pre_ping=True ) self.Session = sessionmaker() self.Session.configure(bind=self.engine) @@ -35,7 +24,7 @@ def create(self): class AsyncDBSession: def __init__(self): - self.engine = create_async_engine(DB_LINK, isolation_level="READ UNCOMMITTED") + self.engine = create_async_engine(db_link, isolation_level="READ UNCOMMITTED") self.Session = async_sessionmaker() self.Session.configure(bind=self.engine) diff --git a/core/database/tables.py b/core/database/tables.py index 5e9c2939a3..69300949a1 100644 --- a/core/database/tables.py +++ b/core/database/tables.py @@ -2,10 +2,11 @@ from sqlalchemy.dialects.mysql import LONGTEXT from core.config import Config -from core.database.orm import Session, DB_LINK +from core.database.orm import Session +from core.database.link import db_type from core.database.orm_base import Base -is_mysql = DB_LINK.startswith("mysql") +is_mysql = db_type == "mysql" default_locale = Config("default_locale", cfg_type=str) diff --git a/core/locales/en_us.json b/core/locales/en_us.json index ee82afc94c..7bd83dbbed 100644 --- a/core/locales/en_us.json +++ b/core/locales/en_us.json @@ -4,6 +4,8 @@ "check.redacted.all": "", "config.comments.allow_reload_base": "Whether to allow reloading of the base modules.", "config.comments.allow_request_private_ip": "Whether to allow bot to request local private IP addresses. (Prevent possible leakage)", + "config.comments.api_access_token": "Access Token used to request the API service.", + "config.comments.api_allow_origins": "The API service allows CORS sources list.", "config.comments.api_port": "The port number of the API service.", "config.comments.base_superuser": "The bot's base superuser list. The values ​​filled in here will be loaded into the database when the bot starts.", "config.comments.bug_report_url": "Report error URL.", @@ -35,7 +37,7 @@ "config.comments.help_url": "Help document URL.", "config.comments.ignored_sender": "Ignore user list. Users matching this list will be ignored.", "config.comments.issue_url": "Issue feedback URL.", - "config.comments.jwt_secret": "The authentication key for the built-in API, it can be filled in at will.", + "config.comments.jwt_secret": "The authentication key for the built-in API, for signing and verifying validity.", "config.comments.kook_token": "KOOK bot token.", "config.comments.locale_url": "Localized project URL.", "config.comments.lost_petal_limit": "The maximum number of petals users can lose per day.", @@ -47,6 +49,7 @@ "config.comments.matrix_user": "Matrix bot fully restricted user ID.", "config.comments.no_confirm": "Whether to execute the command without the sender confirming the message.", "config.comments.proxy": "Proxy service address.", + "config.comments.qq_access_token": "Access Token for connecting to the WebSocket server when using the Onebot protocol.", "config.comments.qq_account": "Bot QQ number. (Required when using the Onebot Protocol side to deploy the QQ bot)", "config.comments.qq_allow_approve_friend": "Whether to automatically approve friend requests when using the Onebot protocol.", "config.comments.qq_allow_approve_group_invite": "Whether to automatically accept group invitations when using the Onebot protocol.", @@ -56,7 +59,6 @@ "config.comments.qq_disable_temp_session": "Whether to enable temp sessions when using the Onebot Protocol.", "config.comments.qq_enable_listening_self_message": "Whether to enable self-message monitoring when using the Onebot protocol.", "config.comments.qq_host": "The address of Aiocqhttp WebSocket main server. (The Onebot protocol end should use the reverse Websocket method to connect)", - "config.comments.qq_access_token": "Access Token for connecting to the WebSocket server when using the Onebot protocol.", "config.comments.qq_limited_emoji": "When using QQ related protocol terminals, the response emoji ID attached to the message when the message processing fails. It needs to be supported by the protocol terminal. For details of emoji ID, see: https://bot.q.qq.com/wiki/develop/api/openapi/emoji/model.html#Emoji", "config.comments.qq_private_bot": "Whether it is a private robot when using the QQ official bot.", "config.comments.qq_typing_emoji": "When using QQ related protocol terminals, the response emoji ID attached to the message when processing the message. It needs to be supported by the protocol terminal. For details of emoji ID, see: https://bot.q.qq.com/wiki/develop/api/openapi/emoji/model.html#Emoji", @@ -182,4 +184,4 @@ "tos.message.warning.count": "You have received ${current_warns} warnings. Your account will be blocked if you receive more than ${warn_counts} warnings.", "tos.message.warning.last": "This is the last warning.", "tos.message.warning.prompt": "Your account will be blocked if you receive more than ${warn_counts} warnings." -} +} \ No newline at end of file diff --git a/core/locales/zh_cn.json b/core/locales/zh_cn.json index f6b6ac94ca..a4c2c492c8 100644 --- a/core/locales/zh_cn.json +++ b/core/locales/zh_cn.json @@ -4,6 +4,8 @@ "check.redacted.all": "<全部吃掉了:${reason}>", "config.comments.allow_reload_base": "是否允许重载基础模块。", "config.comments.allow_request_private_ip": "是否允许机器人请求本地私有 IP 地址。(防止可能的信息泄露)", + "config.comments.api_access_token": "请求 API 服务的 Access Token。", + "config.comments.api_allow_origins": "API 服务允许 CORS 的源列表。", "config.comments.api_port": "API 服务的端口号。", "config.comments.base_superuser": "机器人的基础超级用户列表,此处填写的值将会在机器人启动时被加载进入数据库。", "config.comments.bug_report_url": "汇报错误网址。", @@ -35,7 +37,7 @@ "config.comments.help_url": "帮助文档网址。", "config.comments.ignored_sender": "无视的用户列表,匹配到此列表中的用户将会被忽略。", "config.comments.issue_url": "问题反馈网址。", - "config.comments.jwt_secret": "内置 API 的认证密钥,随意填写即可。", + "config.comments.jwt_secret": "内置 API 的身份认证密钥,用于签名和验证有效性。", "config.comments.kook_token": "KOOK 机器人令牌。", "config.comments.locale_url": "本地化项目网址。", "config.comments.lost_petal_limit": "单日失去花瓣上限。", @@ -47,6 +49,7 @@ "config.comments.matrix_user": "Matrix 机器人的完全限定用户 ID。", "config.comments.no_confirm": "是否无需发送者确认消息后再执行命令。", "config.comments.proxy": "代理服务地址。", + "config.comments.qq_access_token": "使用 Onebot 协议时,连接 WebSocket 服务器的 Access Token。", "config.comments.qq_account": "机器人 QQ 号。(使用 Onebot 协议端部署 QQ 机器人时需要)", "config.comments.qq_allow_approve_friend": "使用 Onebot 协议时,是否自动同意好友请求。", "config.comments.qq_allow_approve_group_invite": "使用 Onebot 协议时,是否自动同意群邀请。", @@ -56,7 +59,6 @@ "config.comments.qq_disable_temp_session": "使用 Onebot 协议时,是否启用临时会话。", "config.comments.qq_enable_listening_self_message": "使用 Onebot 协议时,是否启用自身消息监听。", "config.comments.qq_host": "Aiocqhttp WebSocket 主服务器的地址。(Onebot 协议端请使用反向 Websocket 方式进行连接)", - "config.comments.qq_access_token": "使用 Onebot 协议时,连接 WebSocket 服务器的 Access Token。", "config.comments.qq_limited_emoji": "使用 QQ 相关协议端时,消息处理失败时向消息挂上的回应表情 ID,需协议端支持。ID 详见:https://bot.q.qq.com/wiki/develop/api/openapi/emoji/model.html#Emoji", "config.comments.qq_private_bot": "使用 QQ 机器人(官方)时,是否为私域机器人。", "config.comments.qq_typing_emoji": "使用 QQ 相关协议端时,正在处理消息时向消息挂上的回应表情 ID,需协议端支持。ID 详见:https://bot.q.qq.com/wiki/develop/api/openapi/emoji/model.html#Emoji", @@ -182,4 +184,4 @@ "tos.message.warning.count": "这是第 ${current_warns} 次警告。", "tos.message.warning.last": "这是最后一次警告。", "tos.message.warning.prompt": "如超过 ${warn_counts} 次警告,我们将会封禁你的账户。" -} +} \ No newline at end of file diff --git a/core/locales/zh_tw.json b/core/locales/zh_tw.json index 521300fe07..5fc58fc683 100644 --- a/core/locales/zh_tw.json +++ b/core/locales/zh_tw.json @@ -4,6 +4,8 @@ "check.redacted.all": "<全部已編輯:${reason}>", "config.comments.allow_reload_base": "是否允許重載基礎模組。", "config.comments.allow_request_private_ip": "是否允許機器人請求本地私有 IP 位址。(防止可能的資訊外洩)", + "config.comments.api_access_token": "請求 API 服務的 Access Token。", + "config.comments.api_allow_origins": "API 服務允许 CORS 的來源列表。", "config.comments.api_port": "API 服務的連接埠號。", "config.comments.base_superuser": "機器人的基礎超級使用者列表,此處填寫的值將會在機器人啟動時載入資料庫。", "config.comments.bug_report_url": "回報錯誤網址。", @@ -35,7 +37,7 @@ "config.comments.help_url": "線上說明文件網址。", "config.comments.ignored_sender": "無視的使用者列表,匹配到此列表中的使用者將會被忽略。", "config.comments.issue_url": "問題報告網址。", - "config.comments.jwt_secret": "內建 API 的認證金鑰,隨意填寫即可。", + "config.comments.jwt_secret": "內建 API 的身份認證金鑰,用於簽署和驗證有效性。", "config.comments.kook_token": "KOOK 機器人令牌。", "config.comments.locale_url": "在地化專案網址。", "config.comments.lost_petal_limit": "單日失去花瓣上限。", @@ -47,6 +49,7 @@ "config.comments.matrix_user": "Matrix 機器人的完全限定使用者 ID。", "config.comments.no_confirm": "是否無需傳送者確認訊息後再執行指令。", "config.comments.proxy": "代理服務位址。", + "config.comments.qq_access_token": "使用 Onebot 協定時,連結 WebSocket 伺服器的 Access Token。", "config.comments.qq_account": "機器人 QQ 號。(使用 Onebot 協定端部署 QQ 機器人時需要)", "config.comments.qq_allow_approve_friend": "使用 Onebot 協定時,是否自動同意好友請求。", "config.comments.qq_allow_approve_group_invite": "使用 Onebot 協定時,是否自動同意群組邀請。", @@ -56,7 +59,6 @@ "config.comments.qq_disable_temp_session": "使用 Onebot 協定時,是否啟用臨時會話。", "config.comments.qq_enable_listening_self_message": "使用 Onebot 協定時,是否啟用自身訊息監聽。", "config.comments.qq_host": "Aiocqhttp WebSocket 主伺服器的位址。(Onebot 協定端請使用反向 Websocket 方式連線)", - "config.comments.qq_access_token": "使用 Onebot 協定時,連結 WebSocket 伺服器的 Access Token。", "config.comments.qq_limited_emoji": "使用 QQ 相關協定端時,訊息處理失敗時向訊息掛上的回應表情 ID,需協定端支援。 ID 詳見:https://bot.q.qq.com/wiki/develop/api/openapi/emoji/model.html#Emoji", "config.comments.qq_private_bot": "使用 QQ 機器人(官方)時,是否為私域機器人。", "config.comments.qq_typing_emoji": "使用 QQ 相關協定端時,正在處理訊息時向訊息掛上的回應表情 ID,需協定端支援。 ID 詳見:https://bot.q.qq.com/wiki/develop/api/openapi/emoji/model.html#Emoji", @@ -182,4 +184,4 @@ "tos.message.warning.count": "這是第 ${current_warns} 次警告。", "tos.message.warning.last": "這是最後一次警告。", "tos.message.warning.prompt": "如超過 ${warn_counts} 次警告,我們將會封鎖你的帳戶。" -} +} \ No newline at end of file diff --git a/core/parser/message.py b/core/parser/message.py index 1a4724c17a..ebeefcbc1e 100644 --- a/core/parser/message.py +++ b/core/parser/message.py @@ -489,11 +489,13 @@ async def execute_submodule(msg: Bot.MessageSession, command_first_word): errmsg = msg.locale.t('error.prompt.report', detail=str(e)) if Config('bug_report_url', bug_report_url_default, cfg_type=str): - errmsg += '\n' + msg.locale.t('error.prompt.address', - url=Url(Config('bug_report_url', - bug_report_url_default, - cfg_type=str), - use_mm=False)) + bug_report_url = Url( + Config( + 'bug_report_url', + bug_report_url_default, + cfg_type=str), + use_mm=False) + errmsg += '\n' + msg.locale.t('error.prompt.address', url=bug_report_url) await msg.send_message(errmsg) if not timeout and report_targets: @@ -539,16 +541,16 @@ async def execute_submodule(msg: Bot.MessageSession, command_first_word): for rfunc in regex_module.regex_list.set: # 遍历正则模块的表达式 time_start = datetime.now() try: - msg.matched_msg = False matched = False matched_hash = 0 + trigger_msg = msg.as_display(msg.trigger_msg) if rfunc.mode.upper() in ['M', 'MATCH']: - msg.matched_msg = re.match(rfunc.pattern, msg.trigger_msg, flags=rfunc.flags) + msg.matched_msg = re.match(rfunc.pattern, trigger_msg, flags=rfunc.flags) if msg.matched_msg: matched = True matched_hash = hash(msg.matched_msg.groups()) elif rfunc.mode.upper() in ['A', 'FINDALL']: - msg.matched_msg = re.findall(rfunc.pattern, msg.trigger_msg, flags=rfunc.flags) + msg.matched_msg = re.findall(rfunc.pattern, trigger_msg, flags=rfunc.flags) msg.matched_msg = tuple(set(msg.matched_msg)) if msg.matched_msg: matched = True @@ -633,11 +635,13 @@ async def execute_submodule(msg: Bot.MessageSession, command_first_word): errmsg = msg.locale.t('error.prompt.report', detail=str(e)) if Config('bug_report_url', bug_report_url_default, cfg_type=str): - errmsg += '\n' + msg.locale.t('error.prompt.address', - url=str(Url(Config('bug_report_url', - bug_report_url_default, - cfg_type=str), - use_mm=False))) + bug_report_url = Url( + Config( + 'bug_report_url', + bug_report_url_default, + cfg_type=str), + use_mm=False) + errmsg += '\n' + msg.locale.t('error.prompt.address', url=bug_report_url) await msg.send_message(errmsg) if not timeout and report_targets: diff --git a/core/utils/http.py b/core/utils/http.py index 1b22d16723..17ab9f21c1 100644 --- a/core/utils/http.py +++ b/core/utils/http.py @@ -47,7 +47,7 @@ def private_ip_check(url: str): async def get_url( url: str, - status_code: int = 200, + status_code: Optional[int] = 200, headers: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None, fmt: Optional[str] = None, @@ -124,7 +124,7 @@ async def get_(): async def post_url( url: str, data: Any = None, - status_code: int = 200, + status_code: Optional[int] = 200, headers: Optional[Dict[str, Any]] = None, fmt: Optional[str] = None, timeout: Optional[float] = 20, @@ -200,7 +200,7 @@ async def download( url: str, filename: Optional[str] = None, path: Optional[str] = None, - status_code: int = 200, + status_code: Optional[int] = 200, method: str = "GET", post_data: Any = None, headers: Optional[Dict[str, Any]] = None, diff --git a/core/utils/image.py b/core/utils/image.py index fbafa46dfc..ee66fe726a 100644 --- a/core/utils/image.py +++ b/core/utils/image.py @@ -90,6 +90,7 @@ async def msgchain2image(message_chain: Union[List, MessageChain], d = {'content': html_content, 'element': '.botbox'} html_ = json.dumps(d) + Logger.info("[WebRender] Converting message chain...") try: pic = await download(webrender('element_screenshot', use_local=use_local), status_code=200, @@ -100,17 +101,21 @@ async def msgchain2image(message_chain: Union[List, MessageChain], timeout=30, request_private_ip=True ) - except aiohttp.ClientConnectorError: + except Exception: if use_local: - pic = await download(webrender('element_screenshot', use_local=False), - status_code=200, - method='POST', - headers={'Content-Type': 'application/json'}, - post_data=html_, - request_private_ip=True - ) + try: + pic = await download(webrender('element_screenshot', use_local=False), + status_code=200, + method='POST', + headers={'Content-Type': 'application/json'}, + post_data=html_, + request_private_ip=True + ) + except Exception: + Logger.error('[WebRender] Generation Failed.') + return False else: - Logger.info('[WebRender] Generation Failed.') + Logger.error('[WebRender] Generation Failed.') return False with open(pic) as read: diff --git a/core/utils/image_table.py b/core/utils/image_table.py index 4c60f945a1..d84036e0c6 100644 --- a/core/utils/image_table.py +++ b/core/utils/image_table.py @@ -1,5 +1,6 @@ import base64 import re +import traceback from html import escape from io import BytesIO from typing import Any, List, Union @@ -100,19 +101,27 @@ async def image_table_render( "Content-Type": "application/json", }, ) - except aiohttp.ClientConnectorError: + except Exception: if use_local: - pic = await download( - webrender(use_local=False), - method="POST", - post_data=json.dumps(html), - request_private_ip=True, - headers={ - "Content-Type": "application/json", - }, - ) + try: + pic = await download( + webrender(use_local=False), + method="POST", + post_data=json.dumps(html), + request_private_ip=True, + headers={ + "Content-Type": "application/json", + }, + ) + except Exception: + Logger.error("Generation failed.") + return False + else: + Logger.error("Generation failed.") + return False except Exception: - Logger.exception("Error at image_table_render.") + Logger.error(traceback.format_exc()) + return False with open(pic) as read: load_img = json.loads(read.read()) img_lst = [] diff --git a/example/new_module/__init__.py b/example/new_module/__init__.py index db31e89762..98bc516bb6 100644 --- a/example/new_module/__init__.py +++ b/example/new_module/__init__.py @@ -49,14 +49,14 @@ async def _(msg: Bot.MessageSession): await msg.send_message([Plain("A picture:"), Image("https://http.cat/100.jpg")]) -@test.regex(re.compile(r"\{\{(.*)}}"), mode="M") # re.match +@test.regex(r"\{\{(.*)}}", mode="M") # re.match async def _(msg: Bot.MessageSession): # >>> {{Hello World!}} # <<< Hello World! await msg.finish(msg.matched_msg.group(1)) -@test.regex(re.compile(r"\[\[(.*)]]"), mode="A") # re.findall +@test.regex(r"\[\[(.*)]]", mode="A") # re.findall async def _(msg: Bot.MessageSession): # >>> [[Hello]] [[World]] # <<< Hello diff --git a/modules/bilibili/__init__.py b/modules/bilibili/__init__.py index c1a5ecf07f..b7945b3d77 100644 --- a/modules/bilibili/__init__.py +++ b/modules/bilibili/__init__.py @@ -36,34 +36,30 @@ async def _(msg: Bot.MessageSession, bid: str, get_detail=False): await msg.finish(msg.locale.t("message.cooldown", time=int(30 - res))) -@bili.regex( - re.compile(r"av(\d+)", flags=re.I), mode="M", desc="{bilibili.help.regex.av}" -) +@bili.regex(r"av(\d+)", flags=re.I, mode="A", desc="{bilibili.help.regex.av}") async def _(msg: Bot.MessageSession): - query = f"?aid={msg.matched_msg.group(1)}" - await get_video_info(msg, query) + matched = msg.matched_msg[:5] + for video in matched: + if video: + query = f"?aid={video}" + await get_video_info(msg, query) -@bili.regex( - re.compile(r"\bBV[a-zA-Z0-9]{10}\b"), mode="A", desc="{bilibili.help.regex.bv}" -) +@bili.regex(r"\bBV[a-zA-Z0-9]{10}\b", mode="A", desc="{bilibili.help.regex.bv}") async def _(msg: Bot.MessageSession): - matched = list(set(msg.matched_msg))[:5] + matched = msg.matched_msg[:5] for video in matched: - if video != "": + if video: query = f"?bvid={video}" await get_video_info(msg, query) -@bili.regex( - re.compile( - r"\b(?:http[s]?://)?(?:bili(?:22|33|2233)\.cn|b23\.tv)/([A-Za-z0-9]{7})(?:/.*?|)\b" - ), - mode="A", - desc="{bilibili.help.regex.url}", -) +@bili.regex(r"\b(?:http[s]?://)?(?:bili(?:22|33|2233)\.cn|b23\.tv)/([A-Za-z0-9]{7})(?:/.*?|)\b", + mode="A", + desc="{bilibili.help.regex.url}", + ) async def _(msg: Bot.MessageSession): - matched = list(set(msg.matched_msg))[:5] + matched = msg.matched_msg[:5] for video in matched: if video != "": query = await parse_shorturl(f"https://b23.tv/{video}") diff --git a/modules/bugtracker/__init__.py b/modules/bugtracker/__init__.py index aa8dd663f7..b4a4cd289c 100644 --- a/modules/bugtracker/__init__.py +++ b/modules/bugtracker/__init__.py @@ -39,7 +39,8 @@ async def _(msg: Bot.MessageSession, mojiraid: str): desc="{bugtracker.help.regex.desc}", ) async def _(msg: Bot.MessageSession): - titles = list(set(msg.matched_msg))[:5] + + titles = msg.matched_msg[:5] for title in titles: if title != "": await query_bugtracker(msg, title) diff --git a/modules/bugtracker/bugtracker.py b/modules/bugtracker/bugtracker.py index a31c917e24..cc6b81164a 100644 --- a/modules/bugtracker/bugtracker.py +++ b/modules/bugtracker/bugtracker.py @@ -43,14 +43,12 @@ async def make_screenshot(page_link, use_local=True): bimg = PILImage.open(bio) img_lst.append(bimg) return img_lst - Logger.info("[WebRender] Generation Failed.") + Logger.error("[WebRender] Generation Failed.") return False - except aiohttp.ClientConnectorError: + except Exception: if use_local: return await make_screenshot(page_link, use_local=False) - return False - except ValueError: - Logger.info("[WebRender] Generation Failed.") + Logger.error("[WebRender] Generation Failed.") return False diff --git a/modules/core/help.py b/modules/core/help.py index 43fb08baa4..af3cb99e89 100644 --- a/modules/core/help.py +++ b/modules/core/help.py @@ -137,7 +137,7 @@ async def _(msg: Bot.MessageSession, module: str): d = {'content': html_content, 'element': '.botbox'} html_ = json.dumps(d) - + Logger.info("[WebRender] Generating help document...") try: pic = await download(webrender('element_screenshot', use_local=use_local), status_code=200, @@ -148,7 +148,7 @@ async def _(msg: Bot.MessageSession, module: str): timeout=30, request_private_ip=True ) - except aiohttp.ClientConnectorError: + except Exception as e: if use_local: try: pic = await download(webrender('element_screenshot', use_local=False), @@ -158,12 +158,12 @@ async def _(msg: Bot.MessageSession, module: str): post_data=html_, request_private_ip=True ) - except aiohttp.ClientConnectorError: - Logger.info('[WebRender] Generation Failed.') - raise + except Exception as e: + Logger.error('[WebRender] Generation Failed.') + raise e else: - Logger.info('[WebRender] Generation Failed.') - raise + Logger.error('[WebRender] Generation Failed.') + raise e with open(pic) as read: load_img = json.loads(read.read()) img_lst = [] @@ -363,7 +363,7 @@ async def help_generator(msg: Bot.MessageSession, d = {'content': html_content, 'element': '.botbox'} html_ = json.dumps(d) - + Logger.info("[WebRender] Generating module list...") try: pic = await download(webrender('element_screenshot', use_local=use_local), status_code=200, @@ -374,17 +374,21 @@ async def help_generator(msg: Bot.MessageSession, timeout=30, request_private_ip=True ) - except aiohttp.ClientConnectorError: + except Exception: if use_local: - pic = await download(webrender('element_screenshot', use_local=False), - status_code=200, - method='POST', - headers={'Content-Type': 'application/json'}, - post_data=html_, - request_private_ip=True - ) + try: + pic = await download(webrender('element_screenshot', use_local=False), + status_code=200, + method='POST', + headers={'Content-Type': 'application/json'}, + post_data=html_, + request_private_ip=True + ) + except Exception: + Logger.error('[WebRender] Generation Failed.') + return False else: - Logger.info('[WebRender] Generation Failed.') + Logger.error('[WebRender] Generation Failed.') return False with open(pic) as read: load_img = json.loads(read.read()) diff --git a/modules/core/utils.py b/modules/core/utils.py index fa182012ba..d75942edd2 100644 --- a/modules/core/utils.py +++ b/modules/core/utils.py @@ -1,8 +1,7 @@ import platform import subprocess -from datetime import datetime, timedelta, UTC +from datetime import datetime -import jwt import psutil from cpuinfo import get_cpu_info @@ -14,8 +13,6 @@ from core.utils.i18n import get_available_locales, Locale, load_locale_file from core.utils.info import Info -jwt_secret = Config("jwt_secret", cfg_type=str, secret=True, table_name="bot_api") - ver = module("version", base=True, doc=True) @@ -345,23 +342,3 @@ async def _(msg: Bot.MessageSession): await msg.call_api("set_group_leave", group_id=msg.session.target) else: await msg.finish() - - -token = module("token", base=True, hidden=True) - - -@token.command(" {{core.help.token}}") -async def _(msg: Bot.MessageSession, code: str): - await msg.finish( - jwt.encode( - { - "exp": datetime.now(UTC) - + timedelta(seconds=60 * 60 * 24 * 7), # 7 days - "iat": datetime.now(UTC), - "senderId": msg.target.sender_id, - "code": code, - }, - bytes(jwt_secret, "utf-8"), - algorithm="HS256", - ) - ) diff --git a/modules/exchange_rate/__init__.py b/modules/exchange_rate/__init__.py index 30abfe05aa..0b9c08d3a5 100644 --- a/modules/exchange_rate/__init__.py +++ b/modules/exchange_rate/__init__.py @@ -1,10 +1,12 @@ import datetime +import re from core.builtins import Bot from core.component import module from core.config import Config from core.constants.exceptions import ConfigValueError from core.utils.http import get_url +from core.utils.text import isfloat api_key = Config("exchange_rate_api_key", cfg_type=str, secret=True) @@ -61,8 +63,6 @@ async def exchange(base_currency, target_currency, amount: float, msg): await msg.finish( f"{msg.locale.t('exchange_rate.message.invalid.unit')}{' '.join(unsupported_currencies)}" ) - else: - raise Exception(data["error-type"]) url = f"https://v6.exchangerate-api.com/v6/{api_key}/pair/{base_currency}/{target_currency}/{amount}" data = await get_url(url, 200, fmt="json") @@ -81,18 +81,18 @@ async def exchange(base_currency, target_currency, amount: float, msg): time=time, ) ) - else: - raise Exception(data["error-type"]) @excr.regex( - r"(\d+(\.\d+)?)?\s?([a-zA-Z]{3})\s?[兑换兌換]\s?([a-zA-Z]{3})", + r"(\d+(?:\.\d+)?)?\s?([a-zA-Z]{3})\s?[兑换兌換]\s?([a-zA-Z]{3})", + mode="M", + flags=re.I, desc="{exchange_rate.help.regex.desc}", ) async def _(msg: Bot.MessageSession): - groups = msg.matched_msg.groups() - amount = groups[0] if groups[0] else "1" - base = groups[2].upper() - target = groups[3].upper() + matched_msg = msg.matched_msg + amount = matched_msg.group(1) if matched_msg.group(1) and isfloat(matched_msg.group(1)) else 1 + base = matched_msg.group(2).upper() + target = matched_msg.group(3).upper() if base != target: - await msg.finish(await exchange(base, target, amount, msg)) + await msg.finish(await exchange(base, target, float(amount), msg)) diff --git a/modules/maimai/regex.py b/modules/maimai/regex.py index ad25953588..40190bf227 100644 --- a/modules/maimai/regex.py +++ b/modules/maimai/regex.py @@ -24,9 +24,7 @@ ) -@mai_regex.regex( - re.compile(r"(.+)\s?是什[么麼]歌"), desc="{maimai.help.maimai_regex.song}" -) +@mai_regex.regex(r"(.+)\s?是什[么麼]歌", desc="{maimai.help.maimai_regex.song}") async def _(msg: Bot.MessageSession): name = msg.matched_msg.groups()[0] if name[:2].lower() == "id": @@ -75,10 +73,7 @@ async def _(msg: Bot.MessageSession): await msg.finish(await get_info(music, Plain(res))) -@mai_regex.regex( - re.compile(r"(?:id)?(\d+)\s?有什(?:么别|麼別)[名称稱]", flags=re.I), - desc="{maimai.help.maimai_regex.alias}", -) +@mai_regex.regex(r"(?:id)?(\d+)\s?有什(?:么别|麼別)[名称稱]", flags=re.I, desc="{maimai.help.maimai_regex.alias}") async def _(msg: Bot.MessageSession): sid = msg.matched_msg.groups()[0] music = (await total_list.get()).by_id(sid) @@ -96,19 +91,14 @@ async def _(msg: Bot.MessageSession): await msg.finish([Plain(result.strip())]) -@mai_regex.regex( - re.compile(r"(.+)\s?有什[么麼]分\s?(.+)?"), desc="{maimai.help.maimai_regex.info}" -) +@mai_regex.regex(r"(.+)\s?有什[么麼]分\s?(.+)?", desc="{maimai.help.maimai_regex.info}") async def _(msg: Bot.MessageSession): songname = msg.matched_msg.groups()[0] username = msg.matched_msg.groups()[1] await query_song_info(msg, songname, username) -@mai_regex.regex( - re.compile(r"(\d+\+?)\s?([a-zA-Z]+\+?)\s?[进進]度\s?(.+)?"), - desc="{maimai.help.maimai_regex.process}", -) +@mai_regex.regex(r"(\d+\+?)\s?([a-zA-Z]+\+?)\s?[进進]度\s?(.+)?", desc="{maimai.help.maimai_regex.process}") async def _(msg: Bot.MessageSession): level = msg.matched_msg.groups()[0] goal = msg.matched_msg.groups()[1] @@ -116,22 +106,15 @@ async def _(msg: Bot.MessageSession): await query_process(msg, level, goal, username) -@mai_regex.regex( - re.compile(r"(.?)([極极将將舞神者]舞?)[进進]度\s?(.+)?"), - desc="{maimai.help.maimai_regex.plate}", -) +@mai_regex.regex(r"(.?)([極极将將舞神者]舞?)[进進]度\s?(.+)?", desc="{maimai.help.maimai_regex.plate}") async def _(msg: Bot.MessageSession): plate = msg.matched_msg.groups()[0] + msg.matched_msg.groups()[1] username = msg.matched_msg.groups()[2] await query_plate(msg, plate, username) -@mai_regex.regex( - re.compile( - r"(?:随个|隨個)\s?((?:dx|DX|sd|SD|标准|標準)\s?)?([绿綠黄黃红紅紫白]?)\s?([0-9]+\+?)" - ), - desc="{maimai.help.maimai_regex.random}", -) +@mai_regex.regex(r"(?:随个|隨個)\s?((?:dx|DX|sd|SD|标准|標準)\s?)?([绿綠黄黃红紅紫白]?)\s?([0-9]+\+?)", + desc="{maimai.help.maimai_regex.random}") async def _(msg: Bot.MessageSession): res = msg.matched_msg if res: @@ -162,10 +145,7 @@ async def _(msg: Bot.MessageSession): await msg.finish(msg.locale.t("maimai.message.random.failed")) -@mai_regex.regex( - re.compile(r"(.+)\s?段位(?:[认認]定)?列?表"), - desc="{maimai.help.maimai_regex.grade}", -) +@mai_regex.regex(r"(.+)\s?段位(?:[认認]定)?列?表", desc="{maimai.help.maimai_regex.grade}") async def _(msg: Bot.MessageSession): grade = msg.matched_msg.groups()[0] await get_grade_info(msg, grade) diff --git a/modules/mcserver/server.py b/modules/mcserver/server.py index e54807aaed..824661d544 100644 --- a/modules/mcserver/server.py +++ b/modules/mcserver/server.py @@ -13,7 +13,7 @@ async def query_java_server( ) -> str: match_object = re.match(r"(.*)[\s:](\d*)", address, re.M | re.I) serip = match_object.group(1) if match_object else address - port = int(match_object.group(2)) if match_object else 25565 + port = match_object.group(2)if match_object else 25565 servers = [] try: @@ -65,7 +65,7 @@ async def query_java_server( async def query_bedrock_server(msg, address, raw=False): match_object = re.match(r"(.*)[\s:](\d*)", address, re.M | re.I) serip = match_object.group(1) if match_object else address - port = int(match_object.group(2)) if match_object else 19132 + port = match_object.group(2) if match_object else 19132 servers = [] try: diff --git a/modules/ncmusic/locales/en_us.json b/modules/ncmusic/locales/en_us.json index 79ffe96efc..27b0e68c9f 100644 --- a/modules/ncmusic/locales/en_us.json +++ b/modules/ncmusic/locales/en_us.json @@ -16,5 +16,5 @@ "ncmusic.message.search.table.header.id": "序号", "ncmusic.message.search.table.header.name": "歌名", "config.comments.ncmusic_api": "API address of NetEase Cloud Music API. (For ncmusic module)", - "config.comments.ncmusic_enable_card": "Whether enables card messages of ncmusic module. (Only valid on QQ)" + "config.comments.ncmusic_enable_card": "Whether enables card messages of ncmusic module. (Only valid on QQ client)" } diff --git a/modules/ncmusic/locales/zh_cn.json b/modules/ncmusic/locales/zh_cn.json index 3aa1ce531c..63e1b0cf92 100644 --- a/modules/ncmusic/locales/zh_cn.json +++ b/modules/ncmusic/locales/zh_cn.json @@ -16,5 +16,5 @@ "ncmusic.message.search.table.header.id": "编号", "ncmusic.message.search.table.header.name": "歌名", "config.comments.ncmusic_api": "网易云音乐 API 的 API 地址。(用于 ncmusic 模块)", - "config.comments.ncmusic_enable_card": "ncmusic 模块是否启用卡片消息。(仅在 QQ 平台有效)" + "config.comments.ncmusic_enable_card": "ncmusic 模块是否启用卡片消息。(仅在 QQ 客户端有效)" } diff --git a/modules/ncmusic/locales/zh_tw.json b/modules/ncmusic/locales/zh_tw.json index 81b687321c..5aa4fecd82 100644 --- a/modules/ncmusic/locales/zh_tw.json +++ b/modules/ncmusic/locales/zh_tw.json @@ -16,5 +16,5 @@ "ncmusic.message.search.table.header.id": "編號", "ncmusic.message.search.table.header.name": "歌名", "config.comments.ncmusic_api": "網易雲音樂 API 的 API 位址。(用於 ncmusic 模組)", - "config.comments.ncmusic_enable_card": "ncmusic 模組是否啟用卡片訊息。(僅在 QQ 平台有效)" + "config.comments.ncmusic_enable_card": "ncmusic 模組是否啟用卡片訊息。(僅在 QQ 用戶端有效)" } diff --git a/modules/nintendo_err/__init__.py b/modules/nintendo_err/__init__.py index 20e01bc751..46cbaed48a 100644 --- a/modules/nintendo_err/__init__.py +++ b/modules/nintendo_err/__init__.py @@ -75,35 +75,22 @@ def is_hex(user_input): @staticmethod def check_meme(err: str) -> str: memes = { - "0xdeadbeef": "都坏掉了,不能吃了。", - "0xdeadbabe": "我觉得你有问题。", - "0x8badf00d": "记得垃圾分类。", + "0xdeadbeef": "nintendo_err.message.meme.0xdeadbeef", + "0xdeadbabe": "nintendo_err.message.meme.0xdeadbabe", + "0x8badf00d": "nintendo_err.message.meme.0xbadf00d", } return memes.get(err.casefold()) -e = module("err", developers=["OasisAkari", "kurisu"], doc=True) +e = module("nintendo_err", alias=["err"], developers=["OasisAkari", "kurisu"], doc=True) -@e.command(" {解析任天堂系列主机的报错码并给出原因。}") -async def _(msg: Bot.MessageSession): - """ - Displays information on game console result codes, with a fancy embed. - 0x prefix is not required for hex input. - - Examples: - .err 0xD960D02B - .err D960D02B - .err 022-2634 - .err 102-2804 - .err 2168-0002 - .err 2-ARVHA-0000 - """ +@e.command(" {{nintendo_err.help}}") +async def _(msg: Bot.MessageSession, err_code: str): results = Results() - err = msg.parsed_msg[""] - err = results.fixup_input(err) + err = results.fixup_input(err_code) if meme := results.check_meme(err): - await msg.finish(meme) + await msg.finish(msg.locale.t(meme)) try: ret = results.fetch(err) except ValueError: @@ -117,4 +104,4 @@ async def _(msg: Bot.MessageSession): embed.add_field(name=field.field_name, value=field.message, inline=False) await msg.finish(convert_discord_embed(embed)) else: - await msg.finish("你输入的代码是无效的,或者此功能不支持你使用的主机。") + await msg.finish(msg.locale.t("nintendo_err.message.invalid")) diff --git a/modules/nintendo_err/locales/zh_cn.json b/modules/nintendo_err/locales/zh_cn.json new file mode 100644 index 0000000000..d318354d52 --- /dev/null +++ b/modules/nintendo_err/locales/zh_cn.json @@ -0,0 +1,7 @@ +{ +"nintendo_err.help": "解析任天堂系列主机的报错码并给出原因。", +"nintendo_err.message.invalid": "你输入的代码是无效的,或者此功能不支持你使用的主机。", +"nintendo_err.message.meme.0xdeadbeef": "都坏掉了,不能吃了。", +"nintendo_err.message.meme.0xdeadbabe": "我觉得你有问题。", +"nintendo_err.message.meme.0xbadf00d": "记得垃圾分类。" +} \ No newline at end of file diff --git a/modules/osu/locales/zh_tw.json b/modules/osu/locales/zh_tw.json index dec4d3c8eb..cb190fed6d 100644 --- a/modules/osu/locales/zh_tw.json +++ b/modules/osu/locales/zh_tw.json @@ -9,5 +9,5 @@ "osu.message.unbind.success": "解绑成功。", "osu.message.not_found": "获取内容失败。", "osu.message.user_unbound": "未绑定用户,请使用“${prefix}osu bind”绑定一个用户。", - "config.comments.osu_api_key": "osu! 的 API Key。(用于 osu 模块)" + "config.comments.osu_api_key": "osu! 的 API Key。(用於 osu 模組)" } diff --git a/modules/wiki/inline.py b/modules/wiki/inline.py index 76cec2c06b..3b84a09207 100644 --- a/modules/wiki/inline.py +++ b/modules/wiki/inline.py @@ -31,11 +31,7 @@ ) -@wiki_inline.regex( - re.compile(r"\[\[(.*?)\]\]", flags=re.I), - mode="A", - desc="{wiki.help.wiki_inline.page}", -) +@wiki_inline.regex(r"\[\[(.*?)\]\]", flags=re.I, mode="A", desc="{wiki.help.wiki_inline.page}") async def _(msg: Bot.MessageSession): query_list = [] for x in msg.matched_msg: @@ -45,11 +41,7 @@ async def _(msg: Bot.MessageSession): await query_pages(msg, query_list[:5], inline_mode=True) -@wiki_inline.regex( - re.compile(r"\{\{(.*?)\}\}", flags=re.I), - mode="A", - desc="{wiki.help.wiki_inline.template}", -) +@wiki_inline.regex(r"\{\{(.*?)\}\}", flags=re.I, mode="A", desc="{wiki.help.wiki_inline.template}") async def _(msg: Bot.MessageSession): query_list = [] for x in msg.matched_msg: @@ -59,12 +51,11 @@ async def _(msg: Bot.MessageSession): await query_pages(msg, query_list[:5], template=True, inline_mode=True) -@wiki_inline.regex( - re.compile(r"≺(.*?)≻|⧼(.*?)⧽", flags=re.I), - mode="A", - show_typing=False, - desc="{wiki.help.wiki_inline.mediawiki}", -) +@wiki_inline.regex(r"≺(.*?)≻|⧼(.*?)⧽", + flags=re.I, + mode="A", + show_typing=False, + desc="{wiki.help.wiki_inline.mediawiki}") async def _(msg: Bot.MessageSession): query_list = [] for x in msg.matched_msg: @@ -75,16 +66,12 @@ async def _(msg: Bot.MessageSession): await query_pages(msg, query_list[:5], mediawiki=True, inline_mode=True) -@wiki_inline.regex( - re.compile( - r"(https?://[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,4}\b[-a-zA-Z0-9@:%_+.~#?&/=]*)", - flags=re.I, - ), - mode="A", - show_typing=False, - logging=False, - desc="{wiki.help.wiki_inline.url}", -) +@wiki_inline.regex(r"(https?://[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,4}\b[-a-zA-Z0-9@:%_+.~#?&/=]*)", + flags=re.I, + mode="A", + show_typing=False, + logging=False, + desc="{wiki.help.wiki_inline.url}") async def _(msg: Bot.MessageSession): match_msg = msg.matched_msg diff --git a/modules/wiki/utils/screenshot_image.py b/modules/wiki/utils/screenshot_image.py index 2f321275b7..aa8a98701a 100644 --- a/modules/wiki/utils/screenshot_image.py +++ b/modules/wiki/utils/screenshot_image.py @@ -52,7 +52,7 @@ async def generate_screenshot_v2( timeout=30, request_private_ip=True, ) - except aiohttp.ClientConnectorError: + except Exception: if use_local: return await generate_screenshot_v2( page_link, @@ -61,9 +61,7 @@ async def generate_screenshot_v2( content_mode, use_local=False, ) - return False - except ValueError: - Logger.info("[WebRender] Generation Failed.") + Logger.error("[WebRender] Generation Failed.") return False else: Logger.info("[WebRender] Generating section screenshot...") @@ -79,7 +77,7 @@ async def generate_screenshot_v2( timeout=30, request_private_ip=True, ) - except aiohttp.ClientConnectorError: + except Exception: if use_local: return await generate_screenshot_v2( page_link, @@ -88,9 +86,7 @@ async def generate_screenshot_v2( content_mode, use_local=False, ) - return False - except ValueError: - Logger.info("[WebRender] Generation Failed.") + Logger.error("[WebRender] Generation Failed.") return False with open(img) as read: load_img = json.loads(read.read()) @@ -119,7 +115,7 @@ async def generate_screenshot_v1( page_link, timeout=aiohttp.ClientTimeout(total=20) ) as req: html = await req.read() - except BaseException: + except Exception: Logger.error(traceback.format_exc()) return False soup = BeautifulSoup(html, "html.parser") diff --git a/modules/wiki/utils/wikilib.py b/modules/wiki/utils/wikilib.py index 856e8a5ab9..b35ec0d843 100644 --- a/modules/wiki/utils/wikilib.py +++ b/modules/wiki/utils/wikilib.py @@ -226,7 +226,7 @@ async def check_wiki_available(self): wiki_api_link = api_match.group(1) except Exception: try: - get_page = await get_url(self.url, fmt="text", headers=self.headers) + get_page = await get_url(self.url, status_code=None, fmt="text", headers=self.headers) if ( get_page.find("Attention Required! | Cloudflare") != -1 diff --git a/modules/wolframalpha/locales/en_us.json b/modules/wolframalpha/locales/en_us.json index 5862df696f..aa4d85f7af 100644 --- a/modules/wolframalpha/locales/en_us.json +++ b/modules/wolframalpha/locales/en_us.json @@ -3,5 +3,5 @@ "wolframalpha.help.ask": "Answer the question via WolframAlpha.", "wolframalpha.help.desc": "Use WolframAlpha.", "wolframalpha.message.incomprehensible": "WolframAlpha can't understand your question.", - "config.comments.wolfram_alpha_appid": "WolframAlpha 的 AppID。(用于 wolframalpha 模块)" -} + "config.comments.wolfram_alpha_appid": "WolframAlpha AppID. (For wolframalpha module)" +} diff --git a/modules/wordle/locales/zh_tw.json b/modules/wordle/locales/zh_tw.json index 621bbb729f..2345daddb7 100644 --- a/modules/wordle/locales/zh_tw.json +++ b/modules/wordle/locales/zh_tw.json @@ -12,5 +12,5 @@ "wordle.message.stop": "已停止,答案为:${answer}。\n查詢辭典:https://dictionary.cambridge.org/dictionary/english/${answer}", "wordle.message.theme.disable": "已禁用深色主題。", "wordle.message.theme.enable": "已啟用深色主題。", - "config.comments.wordle_disable_image": "wordle 模块是否禁用图片。" -} + "config.comments.wordle_disable_image": "wordle 模組是否停用圖片。" +} diff --git a/poetry.lock b/poetry.lock index 2176a95269..c3eeddd558 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "aiocqhttp" @@ -348,6 +348,63 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "argon2-cffi" +version = "23.1.0" +description = "Argon2 for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, + {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, +] + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["argon2-cffi[tests,typing]", "tox (>4)"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] +tests = ["hypothesis", "pytest"] +typing = ["mypy"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + [[package]] name = "asyncio-dgram" version = "2.2.0" @@ -1967,13 +2024,13 @@ reference = "mirrors" [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] @@ -3998,28 +4055,27 @@ PyYAML = "*" [[package]] name = "quart" -version = "0.19.8" -description = "A Python ASGI web microframework with the same API as Flask" +version = "0.20.0" +description = "A Python ASGI web framework with the same API as Flask" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "quart-0.19.8-py3-none-any.whl", hash = "sha256:34eeb559014f4e72e093d034cfe203b1a6262e9d3504f826f293090e230609f2"}, - {file = "quart-0.19.8.tar.gz", hash = "sha256:ef567d0be7677c99890d5c6ff30e679699fe7e5fca1a90fa3b6974edd8421794"}, + {file = "quart-0.20.0-py3-none-any.whl", hash = "sha256:003c08f551746710acb757de49d9b768986fd431517d0eb127380b656b98b8f1"}, + {file = "quart-0.20.0.tar.gz", hash = "sha256:08793c206ff832483586f5ae47018c7e40bdd75d886fee3fabbdaa70c2cf505d"}, ] [package.dependencies] aiofiles = "*" blinker = ">=1.6" -click = ">=8.0.0" -flask = ">=3.0.0" +click = ">=8.0" +flask = ">=3.0" hypercorn = ">=0.11.2" itsdangerous = "*" jinja2 = "*" markupsafe = "*" -werkzeug = ">=3.0.0" +werkzeug = ">=3.0" [package.extras] -docs = ["pydata_sphinx_theme"] dotenv = ["python-dotenv"] [package.source] @@ -5321,4 +5377,4 @@ reference = "mirrors" [metadata] lock-version = "2.0" python-versions = "^3.12.0" -content-hash = "78bd3610a1b3fc1005ea68cf7bf4f181df6a14bc9cfacef8ede379835fc559c5" +content-hash = "a4693a4a3853282a78998786d6d55f5b426f364d86b7baa789f1b98957cd78d4" diff --git a/pyproject.toml b/pyproject.toml index 7584ac6acf..e6df6b0b57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,15 +65,17 @@ emoji = "^2.12.1" langconv = {git = "https://github.com/OasisAkari/langconv.py.git"} ff3 = "^1.0.2" orjson = "^3.10.9" -jinja2 = "^3.1.4" +jinja2 = "^3.1.5" mcstatus = "^11.1.1" qq-botpy = "^1.2.1" botpy = {git = "https://github.com/Teahouse-Studios/botpy.git"} tomlkit = "^0.13.2" cattrs = "^24.1.2" +argon2-cffi = "^23.1.0" genshin = "^1.7.1" pydantic = "^2.7.4" + [tool.poetry.group.dev.dependencies] autopep8 = "^2.0.2" pycodestyle = "2.12.1" diff --git a/requirements.txt b/requirements.txt index 7eec73aea1..5cd718c3d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,8 @@ annotated-types==0.7.0 ; python_full_version >= "3.12.0" and python_version < "4 anyio==4.6.2.post1 ; python_full_version >= "3.12.0" and python_version < "4.0" appdirs==1.4.4 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" apscheduler==3.10.4 ; python_version >= "3.12" and python_version < "4.0" +argon2-cffi-bindings==21.2.0 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" +argon2-cffi==23.1.0 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" asyncio-dgram==2.2.0 ; python_full_version >= "3.12.0" and python_version < "4" attrs==24.2.0 ; python_version >= "3.12" and python_version < "4.0" backoff==2.2.1 ; python_full_version >= "3.12.0" and python_version < "4.0" @@ -21,7 +23,7 @@ botpy @ git+https://github.com/Teahouse-Studios/botpy.git@56c66983f36918fa433a71 caio==0.9.17 ; python_full_version >= "3.12.0" and python_version < "4" cattrs==24.1.2 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" certifi==2024.8.30 ; python_full_version >= "3.12.0" and python_version < "4.0" -cffi==1.17.1 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" and platform_python_implementation != "PyPy" +cffi==1.17.1 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" cfgv==3.4.0 ; python_version >= "3.12" and python_version < "4.0" charset-normalizer==3.4.0 ; python_full_version >= "3.12.0" and python_version < "4.0" click==8.1.7 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" @@ -65,7 +67,7 @@ inputimeout==1.0.4 ; python_full_version >= "3.12.0" and python_full_version < " iso639-lang==2.5.0 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" itsdangerous==2.2.0 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" jaraco-context==6.0.1 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" -jinja2==3.1.4 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" +jinja2==3.1.5 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" jiter==0.7.0 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" jsonpatch==1.33 ; python_full_version >= "3.12.0" and python_version < "4.0" jsonpointer==3.0.0 ; python_full_version >= "3.12.0" and python_version < "4.0" @@ -103,7 +105,7 @@ propcache==0.2.0 ; python_version >= "3.12" and python_version < "4.0" psutil==6.1.0 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" py-cord==2.6.1 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" py-cpuinfo==9.0.0 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" -pycparser==2.22 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" and platform_python_implementation != "PyPy" +pycparser==2.22 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" pycryptodome==3.21.0 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" pycryptodomex==3.21.0 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" pydantic-core==2.23.4 ; python_full_version >= "3.12.0" and python_version < "4.0" @@ -119,7 +121,7 @@ pytz==2024.2 ; python_version >= "3.12" and python_version < "4.0" pywin32==306 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" and sys_platform == "win32" pyyaml==6.0.2 ; python_version >= "3.12" and python_version < "4.0" qq-botpy==1.2.1 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" -quart==0.19.8 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" +quart==0.20.0 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" referencing==0.35.1 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" regex==2024.9.11 ; python_full_version >= "3.12.0" and python_full_version < "4.0.0" requests-toolbelt==1.0.0 ; python_full_version >= "3.12.0" and python_version < "4.0"