Compare commits

...
7 Commits
Author SHA1 Message Date
overwriter d90a6bd19a Merge e93fc1463e into da6d5b4be4 2025-07-24 15:59:34 +08:00
HibiKier da6d5b4be4 🐛 修复bot个人介绍重载后不重新读取个人介绍文件 (#1990)
CodeQL Code Security Analysis / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
CodeQL Code Security Analysis / Analyze (${{ matrix.language }}) (none, python) (push) Waiting to run
Sequential Lint and Type Check / ruff-call (push) Waiting to run
Sequential Lint and Type Check / pyright-call (push) Blocked by required conditions
Release Drafter / Update Release Draft (push) Waiting to run
Force Sync to Aliyun / sync (push) Waiting to run
检查bot是否运行正常 / bot check (push) Has been cancelled
Update Version / update-version (push) Has been cancelled
2025-07-24 15:59:28 +08:00
HibiKier 62fac483f2 ✨ feat(workflow): 新增阿里云强制同步工作流配置 (#1991) 2025-07-24 15:59:17 +08:00
molanp 61251ce137 fix(zhenxun): 修复群员昵称中包含特殊字符导致的更新异常 (#1988)
- 在更新群员信息时,使用正则表达式过滤掉昵称中的控制字符
- 优化了 MemberUpdateManage 类中的代码,提高了数据的兼容性和安全性
2025-07-17 19:49:17 +08:00
30fe5a5393 ✨ feat(aliyun): 添加阿里云相关配置和文件操作功能 (#1985)
* ✨ feat(aliyun): 添加阿里云相关配置和文件操作功能

* 🐛 fix bug

* 🎨 更新requirements

* ⬆️ Update poetry.lock

* ✨ feat(aliyun): 添加阿里云获取commit方法

* 更新env pyproject

---------

Co-authored-by: HibiKier <775757368@qq.com>
Co-authored-by: HibiKier <45528451+HibiKier@users.noreply.github.com>
2025-07-17 19:48:33 +08:00
molanpandHibiKier 3cf7c1d237 fix(plugin_store): 修复递代错误 (#1986)
- 在查找插件时使用 next() 函数的默认值 None,避免抛出 StopIteration 异常
- 增加对未找到插件的错误处理,返回相应的错误信息
- 优化了插件查找逻辑,提高了代码的健壮性和可读性

Co-authored-by: HibiKier <45528451+HibiKier@users.noreply.github.com>
2025-07-17 19:01:02 +08:00
HibiKier 91f35ad63a ✨ feat(exceptions): 将DbUrlIsNode异常类继承自HookPriorityException (#1987) 2025-07-17 18:58:22 +08:00
15 changed files with 5867 additions and 3739 deletions
+26
View File
@@ -0,0 +1,26 @@
name: Force Sync to Aliyun
on:
push:
branches: ["main"]
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global http.postBuffer 524288000
git config --global core.compression 0
- name: Add aliyun remote
run: |
git remote add aliyun https://${{secrets.ALIYUN_ACCOUNT}}:${{secrets.ALIYUN_PASSWORD}}@codeup.aliyun.com/67a361cf556e6cdab537117a/zhenxun-org/zhenxun_bot.git
git fetch aliyun main --force # 强制更新本地引用
- name: Force push
run: git push --progress --force aliyun HEAD:main
+1924 -1252
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -45,6 +45,7 @@ nonebot-plugin-alconna = "^0.54.0"
tenacity = "^9.0.0"
nonebot-plugin-uninfo = ">0.4.1"
pydantic = "1.10.18"
alibabacloud-devops20210625 = "^5.0.2"
[tool.poetry.group.dev.dependencies]
nonebug = "^0.4"
+1931 -1256
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -45,6 +45,7 @@ nonebot-plugin-alconna = "^0.54.0"
tenacity = "^9.0.0"
nonebot-plugin-uninfo = ">0.4.1"
pydantic = "2.10.6"
alibabacloud-devops20210625 = "^5.0.2"
[tool.poetry.group.dev.dependencies]
nonebug = "^0.4"
Generated
+1684 -1226
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -49,6 +49,7 @@ multidict = ">=6.0.0,!=6.3.2"
redis = { version = ">=5", optional = true }
asyncpg = { version = ">=0.20.0", optional = true }
alibabacloud-devops20210625 = "^5.0.2"
[tool.poetry.group.dev.dependencies]
nonebug = "^0.4"
+1
View File
@@ -2,6 +2,7 @@ aiocache==0.12.3 ; python_version >= "3.10" and python_version < "4.0"
aiofiles==23.2.1 ; python_version >= "3.10" and python_version < "4.0"
aiosqlite==0.17.0 ; python_version >= "3.10" and python_version < "4.0"
annotated-types==0.7.0 ; python_version >= "3.10" and python_version < "4.0"
alibabacloud-devops20210625==5.0.2 ; python_version >= "3.10" and python_version < "4.0"
anyio==4.8.0 ; python_version >= "3.10" and python_version < "4.0"
apscheduler==3.11.0 ; python_version >= "3.10" and python_version < "4.0"
arclet-alconna-tools==0.7.10 ; python_version >= "3.10" and python_version < "4.0"
@@ -1,4 +1,5 @@
from datetime import datetime
import re
import nonebot
from nonebot.adapters import Bot
@@ -32,7 +33,9 @@ class MemberUpdateManage:
"""
driver = nonebot.get_driver()
default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
nickname = member.nick or member.user.name or ""
nickname = re.sub(
r"[\x00-\x09\x0b-\x1f\x7f-\x9f]", "", member.nick or member.user.name or ""
)
role = member.role
db_user_uid = [u.user_id for u in db_user]
uid2name = {u.user_id: u.user_name for u in db_user}
@@ -198,7 +198,9 @@ class StoreManager:
except ValueError as e:
return str(e)
db_plugin_list = await cls.get_loaded_plugins("module")
plugin_info = next(p for p in plugin_list if p.module == plugin_key)
plugin_info = next((p for p in plugin_list if p.module == plugin_key), None)
if plugin_info is None:
return f"未找到插件 {plugin_key}"
if plugin_info.module in [p[0] for p in db_plugin_list]:
return f"插件 {plugin_info.name} 已安装,无需重复安装"
is_external = True
@@ -307,7 +309,9 @@ class StoreManager:
plugin_key = await cls._resolve_plugin_key(plugin_id)
except ValueError as e:
return str(e)
plugin_info = next(p for p in plugin_list if p.module == plugin_key)
plugin_info = next((p for p in plugin_list if p.module == plugin_key), None)
if plugin_info is None:
return f"未找到插件 {plugin_key}"
path = BASE_PATH
if plugin_info.github_url:
path = BASE_PATH / "plugins"
@@ -383,7 +387,9 @@ class StoreManager:
plugin_key = await cls._resolve_plugin_key(plugin_id)
except ValueError as e:
return str(e)
plugin_info = next(p for p in plugin_list if p.module == plugin_key)
plugin_info = next((p for p in plugin_list if p.module == plugin_key), None)
if plugin_info is None:
return f"未找到插件 {plugin_key}"
logger.info(f"尝试更新插件 {plugin_info.name}", LOG_COMMAND)
db_plugin_list = await cls.get_loaded_plugins("module", "version")
suc_plugin = {p[0]: (p[1] or "Unknown") for p in db_plugin_list}
+2
View File
@@ -1,3 +1,4 @@
import asyncio
from typing_extensions import Self
from nonebot.adapters import Bot
@@ -132,6 +133,7 @@ class FgRequest(Model):
bot.self_id
)
if file_path:
await asyncio.sleep(2)
await PlatformUtils.send_message(
bot,
req.user_id,
+4 -1
View File
@@ -1,4 +1,7 @@
class DbUrlIsNode(Exception):
from zhenxun.utils.exception import HookPriorityException
class DbUrlIsNode(HookPriorityException):
"""
数据库链接地址为空
"""
+30
View File
@@ -44,3 +44,33 @@ GIT_API_PROXY_COMMIT_FORMAT = (
"https://git-api.zhenxun.org/repos/{owner}/{repo}/commits/{branch}"
)
"""git api commit地址格式 (代理)"""
ALIYUN_ORG_ID = "67a361cf556e6cdab537117a"
"""阿里云 organization id"""
ALIYUN_ENDPOINT = "devops.cn-hangzhou.aliyuncs.com"
"""阿里云 endpoint"""
ALIYUN_REGION = "cn-hangzhou"
"""阿里云区域"""
Aliyun_AccessKey_ID = "LTAI5tNmf7KaTAuhcvRobAQs"
"""阿里云AccessKey ID"""
Aliyun_Secret_AccessKey_encrypted = "NmJ3d2VNRU1MREY0T1RtRnBqMlFqdlBxN3pMUk1j"
"""阿里云 Secret Access Key """
RDC_access_token_encrypted = (
"cHQtYXp0allnQWpub0FYZWpqZm1RWGtneHk0XzBlMmYzZTZmLWQwOWItNDE4Mi1iZWUx"
"LTQ1ZTFkYjI0NGRlMg=="
)
"""RDC Access Token """
ALIYUN_REPO_MAPPING = {
"zhenxun-bot-resources": "4957431",
"zhenxun_bot_plugins_index": "4957418",
"zhenxun_bot_plugins": "4957429",
"zhenxun_docs": "4957426",
"zhenxun_bot": "4957428",
}
"""阿里云仓库ID映射"""
+248
View File
@@ -1,8 +1,13 @@
import base64
import contextlib
import sys
from typing import Protocol
from aiocache import cached
from alibabacloud_devops20210625 import models as devops_20210625_models
from alibabacloud_devops20210625.client import Client as devops20210625Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_util import models as util_models
from nonebot.compat import model_dump
from pydantic import BaseModel, Field
@@ -14,11 +19,18 @@ else:
from strenum import StrEnum
from .const import (
ALIYUN_ENDPOINT,
ALIYUN_ORG_ID,
ALIYUN_REGION,
ALIYUN_REPO_MAPPING,
CACHED_API_TTL,
GIT_API_COMMIT_FORMAT,
GIT_API_PROXY_COMMIT_FORMAT,
GIT_API_TREES_FORMAT,
JSD_PACKAGE_API_FORMAT,
Aliyun_AccessKey_ID,
Aliyun_Secret_AccessKey_encrypted,
RDC_access_token_encrypted,
)
from .func import (
get_fastest_archive_formats,
@@ -270,3 +282,239 @@ class GitHubStrategy:
def get_files(self, module_path: str, is_dir: bool = True) -> list[str]:
"""获取文件路径"""
return self.export_files(module_path, is_dir)
class AliyunTreeType(StrEnum):
"""阿里云树类型"""
FILE = "blob"
DIR = "tree"
class AliyunTree(BaseModel):
"""阿里云树节点"""
id: str
is_lfs: bool = Field(alias="isLFS", default=False)
mode: str
name: str
path: str
type: AliyunTreeType
class Config:
populate_by_name = True
class AliyunFileInfo:
"""阿里云策略"""
content: str
"""文件内容"""
file_path: str
"""文件路径"""
ref: str
"""分支/标签/提交版本"""
repository_id: str
"""仓库ID"""
@classmethod
async def get_file_content(
cls, file_path: str, repo: str, ref: str = "main"
) -> str:
"""获取文件内容
参数:
file_path: 文件路径
repo: 仓库名称
ref: 分支名称/标签名称/提交版本号
返回:
str: 文件内容
"""
try:
repository_id = ALIYUN_REPO_MAPPING.get(repo)
if not repository_id:
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
config = open_api_models.Config(
access_key_id=Aliyun_AccessKey_ID,
access_key_secret=base64.b64decode(
Aliyun_Secret_AccessKey_encrypted.encode()
).decode(),
endpoint=ALIYUN_ENDPOINT,
region_id=ALIYUN_REGION,
)
client = devops20210625Client(config)
request = devops_20210625_models.GetFileBlobsRequest(
organization_id=ALIYUN_ORG_ID,
file_path=file_path,
ref=ref,
access_token=base64.b64decode(
RDC_access_token_encrypted.encode()
).decode(),
)
runtime = util_models.RuntimeOptions()
headers = {}
response = await client.get_file_blobs_with_options_async(
repository_id,
request,
headers,
runtime,
)
if response and response.body and response.body.result:
if not response.body.success:
raise ValueError(
f"阿里云请求失败: {response.body.error_code} - "
f"{response.body.error_message}"
)
return response.body.result.content or ""
raise ValueError("获取阿里云文件内容失败")
except Exception as e:
raise ValueError(f"获取阿里云文件内容失败: {e}")
@classmethod
async def get_repository_tree(
cls,
repo: str,
path: str = "",
ref: str = "main",
search_type: str = "DIRECT",
) -> list[AliyunTree]:
"""获取仓库树信息
参数:
repo: 仓库名称
path: 代码仓库内的文件路径
ref: 分支名称/标签名称/提交版本
search_type: 查找策略
"DIRECT" # 仅展示当前目录下的内容
"RECURSIVE" # 递归查找当前路径下的所有文件
"FLATTEN" # 扁平化展示
返回:
list[AliyunTree]: 仓库树信息列表
"""
try:
repository_id = ALIYUN_REPO_MAPPING.get(repo)
if not repository_id:
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
config = open_api_models.Config(
access_key_id=Aliyun_AccessKey_ID,
access_key_secret=base64.b64decode(
Aliyun_Secret_AccessKey_encrypted.encode()
).decode(),
endpoint=ALIYUN_ENDPOINT,
region_id=ALIYUN_REGION,
)
client = devops20210625Client(config)
request = devops_20210625_models.ListRepositoryTreeRequest(
organization_id=ALIYUN_ORG_ID,
path=path,
access_token=base64.b64decode(
RDC_access_token_encrypted.encode()
).decode(),
ref_name=ref,
type=search_type,
)
runtime = util_models.RuntimeOptions()
headers = {}
response = await client.list_repository_tree_with_options_async(
repository_id, request, headers, runtime
)
if response and response.body:
if not response.body.success:
raise ValueError(
f"阿里云请求失败: {response.body.error_code} - "
f"{response.body.error_message}"
)
return [
AliyunTree(**item.to_map()) for item in (response.body.result or [])
]
raise ValueError("获取仓库树信息失败")
except Exception as e:
raise ValueError(f"获取仓库树信息失败: {e}")
@classmethod
async def get_newest_commit(cls, repo: str, branch: str = "main") -> str:
"""获取最新提交
参数:
repo: 仓库名称
branch: sha 分支名称/标签名称/提交版本号
返回:
commit: 最新提交信息
"""
try:
repository_id = ALIYUN_REPO_MAPPING.get(repo)
if not repository_id:
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
config = open_api_models.Config(
access_key_id=Aliyun_AccessKey_ID,
access_key_secret=base64.b64decode(
Aliyun_Secret_AccessKey_encrypted.encode()
).decode(),
endpoint=ALIYUN_ENDPOINT,
region_id=ALIYUN_REGION,
)
client = devops20210625Client(config)
request = devops_20210625_models.GetRepositoryCommitRequest(
organization_id=ALIYUN_ORG_ID,
access_token=base64.b64decode(
RDC_access_token_encrypted.encode()
).decode(),
)
runtime = util_models.RuntimeOptions()
headers = {}
response = await client.get_repository_commit_with_options_async(
repository_id, branch, request, headers, runtime
)
if response and response.body:
if not response.body.success:
raise ValueError(
f"阿里云请求失败: {response.body.error_code} - "
f"{response.body.error_message}"
)
return response.body.result.id or ""
raise ValueError("获取仓库commit信息失败")
except Exception as e:
raise ValueError(f"获取仓库commit信息失败: {e}")
def export_files(
self, tree_list: list[AliyunTree], module_path: str, is_dir: bool
) -> list[str]:
"""导出文件路径"""
return [
file.path
for file in tree_list
if file.type == AliyunTreeType.FILE
and file.path.startswith(module_path)
and (not is_dir or file.path[len(module_path)] == "/" or not module_path)
]
@classmethod
async def parse_repo_info(cls, repo: str) -> list[str]:
"""解析仓库信息获取仓库树"""
repository_id = ALIYUN_REPO_MAPPING.get(repo)
if not repository_id:
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
tree_list = await cls.get_repository_tree(
repo=repo,
)
return cls().export_files(tree_list, "", True)
@@ -76,6 +76,7 @@ class BotProfileManager:
_f = PROFILE_IMAGE_PATH / f
if _f.is_file():
_f.unlink()
cls._bot_data.clear()
@classmethod
async def _read_profile(cls, bot_id: str):