Merge branch 'main' into feature/hook-update

This commit is contained in:
HibiKier 2024-11-26 19:27:15 +08:00 committed by GitHub
commit f669a8ab3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 551 additions and 171 deletions

85
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,85 @@
name: Bug 反馈
title: "Bug: 出现异常"
description: 提交 Bug 反馈以帮助我们改进代码
labels: ["bug"]
body:
- type: dropdown
id: env-os
attributes:
label: 操作系统
description: 选择运行 zhenxun_bot 的系统
options:
- Windows
- MacOS
- Linux
- Other
validations:
required: true
- type: input
id: env-python-ver
attributes:
label: Python 版本
description: 填写运行 zhenxun_bot 的 Python 版本
placeholder: e.g. 3.11.0
validations:
required: true
- type: input
id: env-zhenxun-ver
attributes:
label: zhenxun_bot 版本
description: 填写 zhenxun_bot 版本
placeholder: e.g. 0.1.0
validations:
required: true
- type: input
id: env-adapter
attributes:
label: 适配器
description: 填写使用的适配器以及版本
placeholder: e.g. OneBot v11 2.2.2
validations:
required: true
- type: input
id: env-protocol
attributes:
label: 协议端
description: 填写连接 zhenxun_bot 的协议端及版本
placeholder: e.g. NapCat V4.0.3
validations:
required: true
- type: textarea
id: describe
attributes:
label: 描述问题
description: 清晰简洁地说明问题是什么
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: 复现步骤
description: 提供能复现此问题的详细操作步骤
placeholder: |
1. 首先……
2. 然后……
3. 发生……
validations:
required: true
- type: textarea
id: expected
attributes:
label: 期望的结果
description: 清晰简洁地描述你期望发生的事情
- type: textarea
id: logs
attributes:
label: 截图或日志(请勿包含敏感信息如密码、令牌等)
description: 提供有助于诊断问题的任何日志和截图

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1 @@
blank_issues_enabled: false

18
.github/ISSUE_TEMPLATE/document.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: 文档改进
title: "Docs: 描述"
description: 文档错误及改进意见反馈
labels: ["documentation"]
body:
- type: textarea
id: problem
attributes:
label: 描述问题或主题
validations:
required: true
- type: textarea
id: improve
attributes:
label: 需做出的修改
validations:
required: true

View File

@ -0,0 +1,20 @@
name: 功能建议
title: "Feature: 功能描述"
description: 提出关于项目新功能的想法
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: 希望能解决的问题
description: 在使用中遇到什么问题而需要新的功能?
validations:
required: true
- type: textarea
id: feature
attributes:
label: 描述所需要的功能
description: 请说明需要的功能或解决方法
validations:
required: true

View File

@ -1,23 +0,0 @@
---
name: Issue template
about: " issue template's purpose here."
title: ''
labels: ''
assignees: ''
---
### 系统版本Ubuntu 20.04
### 真寻版本0.1.5.3
### 错误截图
[img]
### 日志截图
[img]
### 错误说明
发生了xx错误...

View File

@ -1,9 +0,0 @@
### 系统版本Ubuntu 20.04
### 真寻版本0.1.5.3
### 错误截图
[img]
### 错误说明
发生了xx错误...

87
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,87 @@
# zhenxun_bot 贡献者公约
## 我们的承诺
身为社区成员、贡献者和负责人,我们承诺使社区参与者不受骚扰,无论其年龄、体型、可见或不可见的缺陷、族裔、性征、性别认同和表达、经验水平、教育程度、社会与经济地位、国籍、相貌、种族、种姓、肤色、宗教信仰、性倾向或性取向如何。
我们承诺以有助于建立开放、友善、多样化、包容、健康社区的方式行事和互动。
## 我们的准则
有助于为我们的社区创造积极环境的行为例子包括但不限于:
* 表现出对他人的同情和善意
* 尊重不同的主张、观点和感受
* 提出和大方接受建设性意见
* 承担责任并向受我们错误影响的人道歉
* 注重社区共同诉求,而非个人得失
不当行为例子包括:
* 使用情色化的语言或图像,及性引诱或挑逗
* 嘲弄、侮辱或诋毁性评论,以及人身或政治攻击
* 公开或私下的骚扰行为
* 未经他人明确许可,公布他人的私人信息,如物理或电子邮件地址
* 其他有理由认定为违反职业操守的不当行为
## 责任和权力
社区负责人有责任解释和落实我们所认可的行为准则,并妥善公正地对他们认为不当、威胁、冒犯或有害的任何行为采取纠正措施。
社区负责人有权力和责任删除、编辑或拒绝或拒绝与本行为准则不相符的评论comment、提交commits、代码、维基wiki编辑、议题issues或其他贡献并在适当时机知采取措施的理由。
## 适用范围
本行为准则适用于所有社区场合,也适用于在公共场所代表社区时的个人。
代表社区的情形包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在线上或线下活动中担任指定代表。
## 监督
辱骂、骚扰或其他不可接受的行为可通过 775757368@qq.com 向负责监督的社区负责人报告。
所有投诉都将得到及时和公平的审查和调查。
所有社区负责人都有义务尊重任何事件报告者的隐私和安全。
## 处理方针
社区负责人将遵循下列社区处理方针来明确他们所认定违反本行为准则的行为的处理方式:
### 1. 纠正
**社区影响**:使用不恰当的语言或其他在社区中被认定为不符合职业道德或不受欢迎的行为。
**处理意见**:由社区负责人发出非公开的书面警告,明确说明违规行为的性质,并解释举止如何不妥。或将要求公开道歉。
### 2. 警告
**社区影响**:单个或一系列违规行为。
**处理意见**:警告并对连续性行为进行处理。在指定时间内,不得与相关人员互动,包括主动与行为准则执行者互动。这包括避免在社区场所和外部渠道中的互动。违反这些条款可能会导致临时或永久封禁。
### 3. 临时封禁
**社区影响**: 严重违反社区准则,包括持续的不当行为。
**处理意见**: 在指定时间内,暂时禁止与社区进行任何形式的互动或公开交流。在此期间,不得与相关人员进行公开或私下互动,包括主动与行为准则执行者互动。违反这些条款可能会导致永久封禁。
### 4. 永久封禁
**社区影响**:行为模式表现出违反社区准则,包括持续的不当行为、骚扰个人或攻击或贬低某个类别的个体。
**处理意见**:永久禁止在社区内进行任何形式的公开互动。
## 参见
本行为准则改编自 [Contributor Covenant][homepage] 2.1 版, 参见 [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]。
社区处理方针灵感来源于 [Mozilla's code of conduct enforcement ladder][Mozilla CoC]。
有关本行为准则的常见问题的答案,参见 [https://www.contributor-covenant.org/faq][FAQ]。
其他语言翻译参见 [https://www.contributor-covenant.org/translations][translations]。
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

95
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,95 @@
# zhenxun_bot 贡献指南
首先,感谢你愿意为 zhenxun_bot 贡献自己的一份力量!
本指南旨在引导你更规范地向 zhenxun_bot 提交贡献,请务必认真阅读。
## 提交 Issue
在提交 Issue 前,我们建议你先查看 [已有的 Issues](https://github.com/HibiKier/zhenxun_bot/issues),以防重复提交。
### 报告问题、故障与漏洞
如果你在使用过程中发现问题并确信是由 zhenxun_bot 引起的,欢迎提交 Issue。
请使用我们提供的 **Bug 反馈** 模板,并尽可能详细地描述:
- 问题描述
- 重现步骤
- 你的环境信息(如操作系统、依赖版本等)
### 建议功能
如果你有新的功能需求或改进建议,欢迎提出。
请使用 **功能建议** 模板,并详细描述你所需要的特性,可能的话可以提出你认为可行的解决方案。
### 文档相关
如果你觉得文档有误或缺乏更新,欢迎提出。
请使用 **文档改进** 模板,并详细描述问题或主题,希望我们做出的修改
## Pull Request
### 分支管理
请从 `main` 分支创建新功能分支,例如:
- 新功能:`feature/功能描述`
- 问题修复:`bugfix/问题描述`
### 代码风格
zhenxun_bot 使用 `pre-commit` 进行代码格式化和检查,请在提交前确保代码通过检查。
```bash
# 在安装项目依赖后安装 pre-commit 钩子
pre-commit install
```
> 未通过 `pre-commit` 检查的代码将无法合并。
### Commit 规范
请确保你的每一个 commit 都能清晰地描述其意图,一个 commit 尽量只有一个目的。
我们建议遵循 [gitmoji](https://gitmoji.dev/) 的 commit message 格式,在创建 commit 时请牢记这一点。
### 工作流程概述
`main` 分支为 zhenxun_bot 的主分支,在任何情况下都请不要直接修改 `main` 分支,而是创建一个目标分支为 `main` 的 Pull Request 来提交修改。Pull Request 标题请尽量清晰,以便维护者进行审核。
如果你不是 zhenxun_bot 团队的成员,可在 fork 本仓库后,向本仓库的 `main` 分支发起 Pull Request注意遵循先前提到的 commit message 规范创建 commit。我们将在 code review 通过后合并你的贡献。
### 撰写文档
如果你对文档有改进建议,欢迎提交 Pull Request 或者 Issue。
[//]: # (我们使用 Markdown 编写文档,建议遵循以下规范:)
[//]: # ()
[//]: # (1. 中文与英文、数字、半角符号之间需要有空格。例:`zhenxun_bot 是一个高效的聊天机器人。`)
[//]: # (2. 若非英文整句,使用全角标点符号。例:`现在你可以看到机器人回复你“Hello世界”。`)
[//]: # (3. 直引号`「」`和弯引号`“”`都可接受,但同一份文件里应使用同种引号。)
[//]: # (4. **不要使用斜体**,你不需要一种与粗体不同的强调。)
[//]: # (5. 文档中应以“我们”指代开发者,以“用户”指代机器人的使用者。)
[//]: # ()
[//]: # (如果你需要编辑器检查 Markdown 规范,可以在 VSCode 中安装 `markdownlint` 扩展。)
### 参与开发
zhenxun_bot 的代码风格遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 与 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 规范,请确保你的代码风格和项目已有的代码保持一致,变量命名清晰,有适当的注释与测试代码。
> 暂未搭建测试框架,因此暂不要求添加测试代码。
## 项目沟通
如有关于贡献流程的疑问或需要进一步指导,请通过 [QQ群](https://jq.qq.com/?_wv=1027&k=u8PgBkMZ) 联系我们。
再次感谢你的贡献!

View File

@ -1 +1 @@
__version__: v0.2.4-fix2
__version__: v0.2.4-dd39a2a

View File

@ -1,66 +0,0 @@
{
"抽卡":{
"buff": ["欧气满满,十连出金","出金不歪"],
"debuff": ["武器大师","保底出金","金色会是痛苦大剑"]
},
"刷世界boss":{
"buff": ["双攻双爆角斗士"],
"debuff": ["只有保底材料","贪生怕死角斗士"]
},
"刷风本":{
"buff": ["会有极品猎人套","会掉真正的少女心","治疗加成少女头"],
"debuff": ["勇往直前少女心","少女飘摇的杀意","少女暴怒的容颜"]
},
"刷火本":{
"buff": ["魔女帽子火伤杯","暴伤魔女帽!","火伤魔女心!"],
"debuff": ["幡 然 醒 悟","这么阴间的地方真的会有魔女套吗?","不务正业火魔女","会匹配到3个卢姥爷"]
},
"刷岩本":{
"buff": ["悠久的磐岩伴你左右","岩神的庇护常在"],
"debuff": ["防御流星杯,你值得拥有"]
},
"刷宗室":{
"buff": ["物理伤害骑士道,元素精通宗室套"],
"debuff": ["贪生怕死骑士道,物理伤害宗室杯"]
},
"刷冰本":{
"buff": ["双暴词条概率up"],
"debuff": ["防御力船帽,无人可及"]
},
"刷雷本":{
"buff": ["愿雷鸟伴你左右"],
"debuff": ["来表演一个只掉平雷套的绝活","风神忽悠雷凶兆"]
},
"锄大地":{
"buff": ["会掉一大堆紫色材料"],
"debuff": ["深渊法师爱你哟","会被冰水法控到死"]
},
"挖矿":{
"buff": ["开矿出双材料"],
"debuff": ["去别人世界会被拒"]
},
"刷天赋本":{
"buff": ["金色!我看到了金色的书!"],
"debuff": ["2蓝2绿不会变"]
},
"刷突破材料":{
"buff": ["金色!我看到了金色的材料!"],
"debuff": ["2蓝2绿不会变"]
},
"升级圣遗物":{
"buff": ["稀有词条跳跳跳","会双爆拉满"],
"debuff": ["女 仆 狂 喜","无中生有防御力","生命拉满","完美避开双爆"]
},
"打风魔龙":{
"buff": ["看我一箭一个风魔鸡","今天特瓦林可以给想要的突破材料","5金加原胚"],
"debuff": ["会不小心掉下平台","不小心被地板烫死了"]
},
"打狼王":{
"buff": ["今天安德琉斯的心情不错可以py一下","5金加原胚"],
"debuff": ["狼尾巴*1"]
},
"打公子":{
"buff": ["今天可以和公子py想要的突破材料","5金加原胚"],
"debuff": ["要角没有!要命一条!"]
}
}

View File

@ -0,0 +1,28 @@
from typing import Any
from nonebot.adapters import Bot
from zhenxun.services.log import logger
from zhenxun.configs.config import Config
Config.add_plugin_config(
"catchphrase",
"CATCHPHRASE",
"",
help="小真寻的口癖,在文本末尾添加指定文字~",
default_value="",
)
@Bot.on_calling_api
async def handle_api_call(bot: Bot, api: str, data: dict[str, Any]):
if api == "send_msg":
catchphrase = Config.get_config("catchphrase", "CATCHPHRASE")
if catchphrase and (message := data.get("message")):
for i in range(len(message) - 1, -1, -1):
if message[i].type == "text":
message[i].data["text"] += catchphrase
logger.debug(
f"文本: {message[i].data['text']} 添加口癖: {catchphrase}"
)
break

View File

@ -202,9 +202,9 @@ async def _():
manager.init()
if limit_list:
for limit in limit_list:
if not manager.exist(limit.module_path, limit.limit_type):
if not manager.exists(limit.module, limit.limit_type):
"""不存在,添加"""
manager.add(limit.module_path, limit)
manager.add(limit.module, limit)
manager.save_file()
await manager.load_to_db()

View File

@ -112,7 +112,7 @@ class Manager:
elif isinstance(data, BaseBlock):
self.block_data[module] = data
def exist(self, module: str, type: PluginLimitType):
def exists(self, module: str, type: PluginLimitType):
"""是否存在"""
if type == PluginLimitType.CD:
return module in self.cd_data

View File

@ -71,8 +71,18 @@ __plugin_meta__ = PluginMetadata(
),
],
tasks=[
Task(module="group_welcome", name="进群欢迎"),
Task(module="refund_group_remind", name="退群提醒"),
Task(
module="group_welcome",
name="进群欢迎",
create_status=False,
default_status=False,
),
Task(
module="refund_group_remind",
name="退群提醒",
create_status=False,
default_status=False,
),
],
).dict(),
)

View File

@ -24,5 +24,8 @@ async def do_something(session: Uninfo):
)
logger.info("添加当前用户群组ID信息", "", session=session)
elif not await FriendUser.exists(user_id=session.user.id, platform=platform):
await FriendUser.create(user_id=session.user.id, platform=platform)
logger.info("添加当前好友用户信息", "", session=session)
try:
await FriendUser.create(user_id=session.user.id, platform=platform)
logger.info("添加当前好友用户信息", "", session=session)
except Exception as e:
logger.error("添加当前好友用户信息失败", session=session, e=e)

View File

@ -3,25 +3,26 @@ import platform
from pathlib import Path
import nonebot
import aiofiles
from nonebot import on_command
from nonebot.rule import to_me
from nonebot.adapters import Bot
from nonebot.params import ArgStr
from nonebot.permission import SUPERUSER
from nonebot_plugin_uninfo import Uninfo
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import BotConfig
from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.configs.config import BotConfig
from zhenxun.utils.message import MessageUtils
from zhenxun.utils.platform import PlatformUtils
from zhenxun.configs.utils import PluginExtraData
__plugin_meta__ = PluginMetadata(
name="重启",
description="执行脚本重启真寻",
usage=f"""
usage="""
重启
""".strip(),
extra=PluginExtraData(
@ -48,15 +49,15 @@ RESTART_FILE = Path() / "restart.sh"
@_matcher.got(
"flag",
prompt=f"确定是否重启{BotConfig.self_nickname}确定请回复[是|好|确定](重启失败咱们将失去联系,请谨慎!)",
prompt=f"确定是否重启{BotConfig.self_nickname}\n确定请回复[是|好|确定]\n(重启失败咱们将失去联系,请谨慎!)",
)
async def _(bot: Bot, session: EventSession, flag: str = ArgStr("flag")):
if flag.lower() in ["true", "", "", "确定", "确定是"]:
async def _(bot: Bot, session: Uninfo, flag: str = ArgStr("flag")):
if flag.lower() in {"true", "", "", "确定", "确定是"}:
await MessageUtils.build_message(
f"开始重启{BotConfig.self_nickname}..请稍等..."
).send()
with open(RESTART_MARK, "w", encoding="utf8") as f:
f.write(f"{bot.self_id} {session.id1}")
async with aiofiles.open(RESTART_MARK, "w", encoding="utf8") as f:
await f.write(f"{bot.self_id} {session.user.id}")
logger.info("开始重启真寻...", "重启", session=session)
if str(platform.system()).lower() == "windows":
import sys
@ -64,34 +65,31 @@ async def _(bot: Bot, session: EventSession, flag: str = ArgStr("flag")):
python = sys.executable
os.execl(python, python, *sys.argv)
else:
os.system("./restart.sh")
os.system("./restart.sh") # noqa: ASYNC221
else:
await MessageUtils.build_message("已取消操作...").send()
@driver.on_bot_connect
async def _(bot: Bot):
if str(platform.system()).lower() != "windows":
if not RESTART_FILE.exists():
with open(RESTART_FILE, "w", encoding="utf8") as f:
f.write(
f"pid=$(netstat -tunlp | grep "
+ str(bot.config.port)
+ " | awk '{print $7}')\n"
"pid=${pid%/*}\n"
"kill -9 $pid\n"
"sleep 3\n"
"python3 bot.py"
)
os.system("chmod +x ./restart.sh")
logger.info(
"已自动生成 restart.sh(重启) 文件,请检查脚本是否与本地指令符合..."
if str(platform.system()).lower() != "windows" and not RESTART_FILE.exists():
async with aiofiles.open(RESTART_FILE, "w", encoding="utf8") as f:
await f.write(
"pid=$(netstat -tunlp | grep "
+ str(bot.config.port)
+ " | awk '{print $7}')\n"
"pid=${pid%/*}\n"
"kill -9 $pid\n"
"sleep 3\n"
"python3 bot.py"
)
os.system("chmod +x ./restart.sh") # noqa: ASYNC221
logger.info("已自动生成 restart.sh(重启) 文件,请检查脚本是否与本地指令符合...")
if RESTART_MARK.exists():
with open(RESTART_MARK, "r", encoding="utf8") as f:
bot_id, session_id = f.read().split()
async with aiofiles.open(RESTART_MARK, encoding="utf8") as f:
bot_id, user_id = (await f.read()).split()
if bot := nonebot.get_bot(bot_id):
if target := PlatformUtils.get_target(bot, session_id):
if target := PlatformUtils.get_target(bot, user_id):
await MessageUtils.build_message(
f"{BotConfig.self_nickname}已成功重启!"
).send(target, bot=bot)

View File

@ -20,7 +20,14 @@ __plugin_meta__ = PluginMetadata(
author="HibiKier",
version="0.1",
plugin_type=PluginType.HIDDEN,
tasks=[Task(module="morning_goodnight", name="早晚安")],
tasks=[
Task(
module="morning_goodnight",
name="早晚安",
create_status=False,
default_status=False,
)
],
).dict(),
)

View File

@ -95,7 +95,10 @@ async def gold_rank(
if not user_list:
return "当前还没有人拥有金币哦..."
user_id_list = [user[0] for user in user_list]
index = user_id_list.index(session.user.id) + 1
if session.user.id in user_id_list:
index = user_id_list.index(session.user.id) + 1
else:
index = "-1未统计"
user_list = user_list[:num] if num < len(user_list) else user_list
friend_user = await FriendUser.filter(user_id__in=user_id_list).values_list(
"user_id", "user_name"

View File

@ -59,7 +59,10 @@ class SignManage:
if not user_list:
return "当前还没有人签到过哦..."
user_id_list = [user[0] for user in user_list]
index = user_id_list.index(session.user.id) + 1
if session.user.id in user_id_list:
index = user_id_list.index(session.user.id) + 1
else:
index = "-1未统计"
user_list = user_list[:num] if num < len(user_list) else user_list
column_name = ["排名", "-", "名称", "好感度", "签到次数", "平台"]
friend_list = await FriendUser.filter(user_id__in=user_id_list).values_list(

View File

@ -1,17 +1,18 @@
from tortoise import Tortoise
from nonebot import on_command
from nonebot.rule import to_me
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_alconna import UniMsg
from nonebot_plugin_session import EventSession
from tortoise import Tortoise
from zhenxun.configs.utils import PluginExtraData
from zhenxun.models.ban_console import BanConsole
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.image_utils import ImageTemplate
from zhenxun.configs.config import BotConfig
from zhenxun.utils.message import MessageUtils
from zhenxun.configs.utils import PluginExtraData
from zhenxun.models.ban_console import BanConsole
from zhenxun.utils.image_utils import ImageTemplate
__plugin_meta__ = PluginMetadata(
name="数据库操作",
@ -43,13 +44,30 @@ _table_matcher = on_command(
block=True,
)
SELECT_TABLE_SQL = """
SELECT_TABLE_MYSQL_SQL = """
SELECT table_name AS name, table_comment AS `desc`
FROM information_schema.tables
WHERE table_schema = DATABASE();
"""
SELECT_TABLE_SQLITE_SQL = """
SELECT name FROM sqlite_master WHERE type='table';
"""
SELECT_TABLE_PSQL_SQL = """
select a.tablename as name,d.description as desc from pg_tables a
left join pg_class c on relname=tablename
left join pg_description d on oid=objoid and objsubid=0 where a.schemaname = 'public'
"""
type2sql = {
"mysql": SELECT_TABLE_MYSQL_SQL,
"sqlite": SELECT_TABLE_SQLITE_SQL,
"postgres": SELECT_TABLE_PSQL_SQL,
}
@_matcher.handle()
async def _(session: EventSession, message: UniMsg):
sql_text = message.extract_plain_text().strip()
@ -70,9 +88,7 @@ async def _(session: EventSession, message: UniMsg):
_column = r.keys()
data_list = []
for r in res:
data = []
for c in _column:
data.append(r.get(c))
data = [r.get(c) for c in _column]
data_list.append(data)
if not data_list:
return await MessageUtils.build_message("查询结果为空!").send()
@ -90,11 +106,13 @@ async def _(session: EventSession, message: UniMsg):
async def _(session: EventSession):
try:
db = Tortoise.get_connection("default")
query = await db.execute_query_dict(SELECT_TABLE_SQL)
sql_type = BotConfig.get_sql_type()
select_sql = type2sql[sql_type]
query = await db.execute_query_dict(select_sql)
column_name = ["表名", "简介"]
data_list = []
for table in query:
data_list.append([table["name"], table["desc"]])
data_list.append([table["name"], table.get("desc")])
logger.info("查看数据库所有表", "查看所有表", session=session)
table = await ImageTemplate.table_page(
"数据库表", f"总共有 {len(data_list)} 张表捏", column_name, data_list

View File

@ -5,12 +5,13 @@ from fastapi import Request, APIRouter
from fastapi.responses import JSONResponse
from tortoise.exceptions import OperationalError
from zhenxun.configs.config import BotConfig
from zhenxun.models.task_info import TaskInfo
from zhenxun.models.plugin_info import PluginInfo
from .models.sql_log import SqlLog
from ....utils import authentication
from .models.model import SqlText, SqlModel
from .models.model import Column, SqlText, SqlModel
from ....base_model import Result, QueryModel, BaseResultModel
router = APIRouter(prefix="/database")
@ -22,19 +23,48 @@ driver: Driver = nonebot.get_driver()
SQL_DICT = {}
SELECT_TABLE_SQL = """
select a.tablename as name,d.description as desc from pg_tables a
left join pg_class c on relname=tablename
left join pg_description d on oid=objoid
and objsubid=0 where a.schemaname = 'public'
SELECT_TABLE_MYSQL_SQL = """
SELECT table_name AS name, table_comment AS `desc`
FROM information_schema.tables
WHERE table_schema = DATABASE();
"""
SELECT_TABLE_COLUMN_SQL = """
SELECT_TABLE_SQLITE_SQL = """
SELECT name FROM sqlite_master WHERE type='table';
"""
SELECT_TABLE_PSQL_SQL = """
select a.tablename as name,d.description as desc from pg_tables a
left join pg_class c on relname=tablename
left join pg_description d on oid=objoid and objsubid=0 where a.schemaname='public'
"""
SELECT_TABLE_COLUMN_PSQL_SQL = """
SELECT column_name, data_type, character_maximum_length as max_length, is_nullable
FROM information_schema.columns
WHERE table_name = '{}';
"""
SELECT_TABLE_COLUMN_MYSQL_SQL = """
SHOW COLUMNS FROM {};
"""
SELECT_TABLE_COLUMN_SQLITE_SQL = """
PRAGMA table_info({});
"""
type2sql = {
"mysql": SELECT_TABLE_MYSQL_SQL,
"sqlite": SELECT_TABLE_SQLITE_SQL,
"postgres": SELECT_TABLE_PSQL_SQL,
}
type2sql_column = {
"mysql": SELECT_TABLE_COLUMN_MYSQL_SQL,
"sqlite": SELECT_TABLE_COLUMN_SQLITE_SQL,
"postgres": SELECT_TABLE_COLUMN_PSQL_SQL,
}
@driver.on_startup
async def _():
@ -71,22 +101,47 @@ async def _():
)
async def _() -> Result[list[dict]]:
db = Tortoise.get_connection("default")
query = await db.execute_query_dict(SELECT_TABLE_SQL)
sql_type = BotConfig.get_sql_type()
query = await db.execute_query_dict(type2sql[sql_type])
return Result.ok(query)
@router.get(
"/get_table_column",
dependencies=[authentication()],
response_model=Result[list[dict]],
response_model=Result[list[Column]],
response_class=JSONResponse,
description="获取表字段",
)
async def _(table_name: str) -> Result[list[dict]]:
async def _(table_name: str) -> Result[list[Column]]:
db = Tortoise.get_connection("default")
# print(SELECT_TABLE_COLUMN_SQL.format(table_name))
query = await db.execute_query_dict(SELECT_TABLE_COLUMN_SQL.format(table_name))
return Result.ok(query)
sql_type = BotConfig.get_sql_type()
sql = type2sql_column[sql_type]
query = await db.execute_query_dict(sql.format(table_name))
result_list = []
if sql_type == "sqlite":
result_list.extend(
Column(
column_name=result["name"],
data_type=result["type"],
max_length=-1,
is_nullable="YES" if result["notnull"] == 1 else "NO",
)
for result in query
)
elif sql_type == "mysql":
result_list.extend(
Column(
column_name=result["Field"],
data_type=result["Type"],
max_length=-1,
is_nullable=result["Null"],
)
for result in query
)
else:
result_list.extend(Column(**result) for result in query)
return Result.ok(result_list)
@router.post(

View File

@ -22,3 +22,18 @@ class SqlModel(BaseModel):
"""插件名称"""
sql_list: list[CommonSql]
"""插件列表"""
class Column(BaseModel):
"""
"""
column_name: str
"""列名"""
data_type: str
"""数据类型"""
max_length: int | None
"""最大长度"""
is_nullable: str
"""是否可为空"""

View File

@ -46,10 +46,13 @@ _matcher = on_alconna(Alconna("撤回"), priority=5, block=True, rule=reply_chec
@_matcher.handle()
async def _(bot: Bot, event: Event, session: Uninfo, arparma: Arparma):
if reply := await reply_fetch(event, bot):
if (
MessageManager.check(session.user.id, reply.id)
or session.user.id in bot.config.superusers
):
if session.user.id in bot.config.superusers:
try:
await bot.delete_msg(message_id=reply.id)
logger.info("撤回消息", arparma.header_result, session=session)
except Exception:
await MessageUtils.build_message("撤回失败,可能消息已过期...").send()
elif MessageManager.check(session.user.id, reply.id):
try:
await bot.delete_msg(message_id=reply.id)
logger.info("撤回消息", arparma.header_result, session=session)

View File

@ -46,7 +46,7 @@ class BotSetting(BaseModel):
"""获取数据库类型
返回:
str: 数据库类型, postgres, aiomysql, sqlite
str: 数据库类型, postgres, mysql, sqlite
"""
return self.db_url.split(":", 1)[0] if self.db_url else ""

View File

@ -156,7 +156,8 @@ class BuildImage:
"""
if not img_list:
raise ValueError("贴图类别为空...")
width, height = img_list[0].size
width = max(img.size[0] for img in img_list)
height = max(img.size[1] for img in img_list)
background_width = width * row + space * (row - 1) + padding * 2
row_count = math.ceil(len(img_list) / row)
if row_count == 1:
@ -168,12 +169,22 @@ class BuildImage:
background_width, background_height, color=color, background=background
)
_cur_width, _cur_height = padding, padding
for img in img_list:
row_num = 0
for i in range(len(img_list)):
row_num += 1
img: Self | tImage = img_list[i]
await background_image.paste(img, (_cur_width, _cur_height))
_cur_width += space + img.width
if _cur_width + padding >= background_image.width:
next_image_width = 0
if i != len(img_list) - 1:
next_image_width = img_list[i + 1].width
if (
row_num == row
or _cur_width + padding + next_image_width >= background_image.width + 1
):
_cur_height += space + img.height
_cur_width = padding
row_num = 0
return background_image
@classmethod
@ -719,3 +730,11 @@ class BuildImage:
bytes: bytes
"""
return self.markImg.tobytes()
def copy(self) -> "BuildImage":
"""复制
返回:
BuildImage: Self
"""
return BuildImage.open(self.pic2bytes())

View File

@ -1,3 +1,4 @@
import base64
from io import BytesIO
from pathlib import Path
@ -59,7 +60,14 @@ class MessageUtils:
for msg in msg_list:
if isinstance(msg, Image | Text | At | AtAll | Video | Voice):
message_list.append(msg)
elif isinstance(msg, str | int | float):
elif isinstance(msg, str):
if msg.startswith("base64://"):
message_list.append(Image(raw=BytesIO(base64.b64decode(msg[9:]))))
elif msg.startswith("http://"):
message_list.append(Image(url=msg))
else:
message_list.append(Text(msg))
elif isinstance(msg, int | float):
message_list.append(Text(str(msg)))
elif isinstance(msg, Path):
if msg.exists():
@ -124,6 +132,8 @@ class MessageUtils:
_message[i] = Image(
raw=BuildImage.open(_message[i]).pic2bytes()
)
elif isinstance(_message[i], BuildImage):
_message[i] = Image(raw=_message[i].pic2bytes())
node_list.append(
CustomNode(uid=uin, name=name, content=UniMessage(_message))
)