Compare commits

..
9 changed files with 152 additions and 94 deletions
+1 -2
View File
@@ -25,8 +25,7 @@
"ujson",
"unban",
"userinfo",
"zhenxun",
"jsdelivr"
"zhenxun"
],
"python.analysis.autoImportCompletions": true,
"python.testing.pytestArgs": ["tests"],
+105 -42
View File
@@ -60,7 +60,7 @@
<img width="350" height="350" src="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/help.png"/>
<img width="250" height="500" src="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/html_help.png"/>
<img width="180" height="450" src="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/zhenxun_help.png"/>
<img width="180" height="450" src="https://github.com/HibiKier/zhenxun_bot/blob/dev/docs_image/zhenxun_help.png"/>
</div>
@@ -72,7 +72,7 @@
(球球了测试君!)
```
Url: ws://test.zhenxun.org:8080/onebot/v11/ws
Url: 43.143.112.57:11451/onebot/v11/ws
AccessToken: PUBLIC_ZHENXUN_TEST
注:你无法获得超级用户权限
@@ -112,8 +112,7 @@ AccessToken: PUBLIC_ZHENXUN_TEST
## ~~来点优点?~~ 可爱难道还不够吗
- 实现了许多功能,且提供了大量功能管理命令,进行了多平台适配,兼容 nb2 商店插件
- 拥有完善可用的 webui
- 实现了许多功能,且提供了大量功能管理命令
- 通过 Config 配置项将所有插件配置统计保存至 config.yaml,利于统一用户修改
- 方便增删插件,原生 nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息`
- 提供了 cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等
@@ -167,93 +166,157 @@ python bot.py
```
## 功能列表
<!-- ## 功能列表 (旧版列表)
<details>
<summary>内置功能</summary>
<summary>已实现的功能</summary>
**真寻原 `plugins` 插件文件夹已迁移至其他仓库,当前内置仅保留必要的功能**
### 已实现的常用功能
### 基础功能
- [x] 昵称系统(群与群与私聊分开.)
- [x] 昵称系统(群与群与私聊分开)
- [x] 图灵 AI(会把'你'等关键字替换为你的昵称),且带有 [AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus),够味
- [x] 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置)
- [x] 商店/我的金币/购买道具/使用道具/金币排行(完整的商店添加/购买/使用流程)
- [x] 发送某文件夹下的随机图片(支持自定义,默认:美图,萝莉,壁纸)
- [x] 色图(这不是基础功能嘛喂)
- [x] coser
- [x] 黑白草图生成器
- [x] 鸡汤/语录
- [x] 骂我(钉宫语音)
- [x] 戳一戳(概率发送美图,钉宫语音或者戳回去)
- [x] 模拟开箱/我的开箱/群开箱统计/我的金色/设置 cookie(csgo,内置爬虫脚本,需要提前抓取数据和图片,需要 session,可能需要代理,阿里云服务器等 ip 也许已经被 ban 了(我无代理访问失败),如果访问太多账号 API 调用可能被禁止访问 api!)
- [x] 鲁迅说过
- [x] 构造假消息(自定义的分享链接)
- [x] 商店/我的金币/购买道具/使用道具
- [x] 8 种手游抽卡 (查看 [nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw))
- [x] 我有一个朋友想问问..(借鉴 pcrbot 插件)
- [x] 原神黄历
- [x] 原神今日素材
- [x] 原神资源查询 (借鉴[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件)
- [x] 原神便笺查询
- [x] 原神玩家查询
- [x] 原神树脂提醒
- [x] 原神签到/自动签到
- [x] 金币红包
- [x] 微博热搜
- [x] B 站主播/UP/番剧订阅
- [x] pil 对图片的一些操作
- [x] BUFF 饰品底价查询(需要 session)
- [x] 天气查询
- [x] 疫情查询
- [x] bt 磁力搜索(咳咳,这功能我想 dddd)
- [x] reimu 搜索(上车) (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件)
- [x] 靠图识番 (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件)
- [x] 以图搜图 (使用[nonebot_plugin_picsearcher](https://github.com/synodriver/nonebot_plugin_picsearcher)插件)
- [x] 搜番
- [x] 点歌 [nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2)插件(删除了选歌和评论)
- [x] epic 免费游戏
- [x] p 站排行榜
- [x] p 站搜图
- [x] 翻译(日英韩)
- [x] pix 图库(一个自己的图库,含有增删查改,黑名单等命令)
- [x] 查看当前群欢迎消息
- [x] 个人信息查看(群组内权限,聊天频率等)
- [x] 消息撤回
- [x] 查看该群自己的权限
- [x] 我的信息(只是为了看看什么时候入群)
- [x] 更新信息(如果继续更新的话)
- [x] go-cqhttp 最新版下载和上传(不需要请删除)
- [x] 撤回
- [x] 滴滴滴-(用户对超级用户发送消息)
- [x] 金币红包/金币排行
- [x] 俄罗斯轮盘/胜场排行/败场排行/欧洲人排行/慈善家排行
- [x] 网易云热评
- [x] 念首古诗
- [x] 获取 b 站视频封面
- [x] 通过 PID 获取图片
- [x] 功能统计可视化
- [x] 词云
- [x] 关于
- [x] 三种样式的帮助菜单
### 管理员功能
### 已实现的管理员功能
- [x] 管理员帮助
- [x] 更新群组成员信息
- [x] 95%的群功能开关
- [x] 查看群内被动技能状态
- [x] 自定义群欢迎消息(是真寻的不是管家的!)
- [x] ban/unban(支持设置 ban 时长)= 群组及用户的黑名单
- [x] 休息吧/醒来(群组内真寻状态)
- [x] .ban/.unban(支持设置 ban 时长)= 黑白名单
- [x] 刷屏禁言相关:刷屏检测设置/设置禁言时长/设置检测次数
- [x] 上传图片/连续上传图片 (上传图片至指定图库)
- [x] 移动图片 (同上)
- [x] 删除图片 (同上)
- [x] 群内 B 站订阅
- [x] 词条设置
- [x] 休息吧/醒来
### 超级用户功能
### 已实现的超级用户功能
- [x] 超级用户帮助
- [x] 添加/删除权限(是真寻的管理员权限,不是群管理员)
- [x] 群组管理,退群指令等
- [x] 开启/关闭指定群的广播通知
- [x] 广播
- [x] 自检(检查系统状态)
- [x] 所有群组/所有好友
- [x] 退出指定群
- [x] 更新好友信息/更新群信息
- [x] /t(对用户进行回复或发送消息)
- [x] 上传/删除/修改商品(需要编写对应的商品功能)
- [x] 节日红包发送
- [x] 修改群权限
- [x] 检查更新
- [x] ban
- [x] 更新色图
- [x] 更新价格/更加图片(csgo 开箱)
- [x] 重载原神/方舟/赛马娘/坎公骑冠剑卡池
- [x] 更新原神今日素材/更新原神资源信息
- [x] PIX 相关操作
- [x] 检查更新真寻
- [x] 重启
- [x] 添加/删除/查看群白名单
- [x] 功能开关(更多设置)
- [x] 功能状态
- [x] 执行 SQL
- [x] b 了
- [x] 执行 sql
- [x] 重载配置
- [x] 清理临时数据
- [x] 增删群认证
- [x] 同意/拒绝好友/群聊请求
- [x] 添加/移除/更新插件/插件商店(plugins 库以及扩展库)
- [x] WebUI API(对真寻前端的支持)
- [x] 配置重载
#### 超级用户的被动技能
- [x] 邀请入群提醒(别人邀请真寻入群,可配置自动同意)
- [x] 邀请入群提醒(别人邀请真寻入群)
- [x] 添加好友提醒(别人添加真寻好友,可配置自动同意)
- [x] 添加好友提醒(别人添加真寻好友)
### 被动技能
### 已实现的被动技能
- [x] 进群欢迎消息
- [x] 群早晚安
- [x] 每日开箱重置提醒
- [x] b 站转发解析(解析 b 站分享信息,支持 bv,bilibili 链接,b 站手机端转发卡片,cv,b23.tv),且 5 分钟内不解析相同 url
- [x] 丢人爬(爬表情包)
- [x] epic 通知(每日发送 epic 免费游戏链接)
- [x] 原神黄历提醒
- [x] 复读
### 看不见的技能
### 已实现的看不见的技能
- [x] 刷屏禁言检测
- [x] 功能调用统计
- [x] 聊天记录统计
- [x] 检测恶意触发命令(将被最高权限 ban 掉 30 分钟,只有最高权限(9 级)可以进行 unban)
- [x] 自动同意好友/群组请求,加群请求将会提醒管理员,退群提示,加群欢迎等等
- [x] 群聊时间检测(当群聊最后一人发言时间大于当前 48 小时后将关闭该群所有通知(即被动技能))
- [x] 自动同意好友请求,加群请求将会提醒管理员,退群提示,加群欢迎等等
- [x] 群聊时间检测(当群聊最后一人发言时间大于当前 36 小时后将关闭该群所有通知(即被动技能))
- [x] 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限
- [x] 群权限系统
- [x] 定时更新权限
- [x] 自动配置重载
- [x] 强制入群保护
- [x] 自定备份(可配置)
- [x] 笨蛋检测(当使用功能名称当指令时真寻会跳出来狠狠嘲笑并帮助)
### 更多插件...
</details> -->
- [更多插件](https://github.com/zhenxun-org/zhenxun_bot_plugins)
- [第三方插件索引库](https://github.com/zhenxun-org/zhenxun_bot_plugins_index)
</details>
## [爱发电](https://afdian.com/a/HibiKier)
## [爱发电](https://afdian.net/@HibiKier)
<details>
<summary>爱发电 以及 感谢投喂 </summary>
+1 -1
View File
@@ -46,7 +46,7 @@ async def _(
nickname = user_id
try:
result = await get_user_info(
session, user_id, session.group.id if session.group else None, nickname
session, bot, user_id, session.group.id if session.group else None, nickname
)
await MessageUtils.build_message(result).send(at_sender=True)
logger.info("获取用户信息", arparma.header_result, session=session)
+4 -3
View File
@@ -1,6 +1,7 @@
import random
from datetime import datetime, timedelta
from nonebot.adapters import Bot
from tortoise.functions import Count
from tortoise.expressions import RawSQL
from nonebot_plugin_uninfo import Uninfo
@@ -130,7 +131,7 @@ async def get_chat_history(
async def get_user_info(
session: Uninfo, user_id: str, group_id: str | None, nickname: str
session: Uninfo, bot: Bot, user_id: str, group_id: str | None, nickname: str
) -> bytes:
"""获取用户个人信息
@@ -144,7 +145,7 @@ async def get_user_info(
返回:
bytes: 图片数据
"""
platform = PlatformUtils.get_platform(session) or "qq"
platform = PlatformUtils.get_platform(bot) or ""
ava_url = PlatformUtils.get_user_avatar_url(user_id, platform)
user = await UserConsole.get_user(user_id, platform)
level = await LevelUser.get_user_level(user_id, group_id)
@@ -163,7 +164,7 @@ async def get_user_info(
data = {
"date": now.date(),
"weather": weather,
"ava_url": ava_url,
"ava_url": session.user.avatar,
"nickname": nickname,
"title": "勇 者",
"race": random.choice(RACE),
+23 -21
View File
@@ -1,31 +1,30 @@
import random
from typing import Any
from typing import Any, List
from nonebot import on_regex
from nonebot.rule import to_me
from nonebot.adapters import Bot
from nonebot.plugin import PluginMetadata
from nonebot.params import Depends, RegexGroup
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_alconna import Alconna, Option, on_alconna, store_true
from nonebot_plugin_session import EventSession
from nonebot_plugin_userinfo import UserInfo, EventUserInfo
from nonebot_plugin_alconna import Option, Alconna, on_alconna, store_true
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.depends import UserName
from zhenxun.utils.message import MessageUtils
from zhenxun.configs.config import BotConfig, Config
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.models.ban_console import BanConsole
from zhenxun.models.friend_user import FriendUser
from zhenxun.configs.config import Config, BotConfig
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.configs.utils import RegisterConfig, PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.depends import UserName
from zhenxun.utils.enum import PluginType
from zhenxun.utils.message import MessageUtils
__plugin_meta__ = PluginMetadata(
name="昵称系统",
description="区区昵称,才不想叫呢!",
usage=f"""
个人昵称,将替换{BotConfig.self_nickname}称呼你的名称,群聊 与 私聊 昵称相互独立,
全局昵称设置将更改您目前所有群聊中及私聊的昵称
个人昵称,将替换{BotConfig.self_nickname}称呼你的名称,群聊 与 私聊 昵称相互独立,全局昵称设置将更改您目前所有群聊中及私聊的昵称
指令:
以后叫我 [昵称]: 设置当前群聊/私聊的昵称
全局昵称设置 [昵称]: 设置当前所有群聊和私聊的昵称
@@ -40,10 +39,9 @@ __plugin_meta__ = PluginMetadata(
RegisterConfig(
key="BLACK_WORD",
value=["爸", "爹", "爷", "父"],
help="昵称所屏蔽的关键词,已设置的昵称会被替换为 *,"
"未设置的昵称会在设置时提示",
help="昵称所屏蔽的关键词,已设置的昵称会被替换为 *,未设置的昵称会在设置时提示",
default_value=None,
type=list[str],
type=List[str],
)
],
).dict(),
@@ -169,8 +167,9 @@ async def _(
):
if session.id1:
(name,) = reg_group
if len(name) < 5 and random.random() < 0.3:
name = "~".join(name)
if len(name) < 5:
if random.random() < 0.3:
name = "~".join(name)
if gid := session.id3 or session.id2:
await GroupInfoUser.set_user_nickname(
session.id1,
@@ -182,6 +181,9 @@ async def _(
session.platform,
)
logger.info(f"设置群昵称成功: {name}", "昵称设置", session=session)
await MessageUtils.build_message(
random.choice(CALL_NAME).format(name)
).finish(reply_to=True)
else:
await FriendUser.set_user_nickname(
session.id1,
@@ -192,9 +194,9 @@ async def _(
session.platform,
)
logger.info(f"设置私聊昵称成功: {name}", "昵称设置", session=session)
await MessageUtils.build_message(random.choice(CALL_NAME).format(name)).finish(
reply_to=True
)
await MessageUtils.build_message(
random.choice(CALL_NAME).format(name)
).finish(reply_to=True)
await MessageUtils.build_message("用户id为空...").send()
@@ -49,8 +49,9 @@ class GroupManager:
group: GroupConsole
"""
if group:
group.group_flag = 1
await group.save(update_fields=["group_flag"])
await GroupConsole.filter(
group_id=group_id, channel_id__isnull=True
).update(group_flag=1)
else:
block_plugin = ""
if plugin_list := await PluginInfo.filter(default_status=False).all():
+7 -5
View File
@@ -1,5 +1,5 @@
from datetime import datetime, timedelta
from typing import Literal
from typing import Literal, Tuple
from tortoise import fields
from tortoise.functions import Count
@@ -27,7 +27,7 @@ class ChatHistory(Model):
platform = fields.CharField(255, null=True)
"""平台"""
class Meta: # type: ignore
class Meta:
table = "chat_history"
table_description = "聊天记录数据表"
@@ -53,7 +53,7 @@ class ChatHistory(Model):
query = query.filter(create_time__range=date_scope)
return list(
await query.annotate(count=Count("user_id"))
.order_by(f"{o}count")
.order_by(o + "count")
.group_by("user_id")
.limit(limit)
.values_list("user_id", "count")
@@ -74,7 +74,9 @@ class ChatHistory(Model):
)
else:
message = await cls.all().order_by("create_time").first()
return message.create_time if message else None
if message:
return message.create_time
return None
@classmethod
async def get_message(
@@ -83,7 +85,7 @@ class ChatHistory(Model):
gid: str,
type_: Literal["user", "group"],
msg_type: Literal["private", "group"] | None = None,
days: int | tuple[datetime, datetime] | None = None,
days: int | Tuple[datetime, datetime] | None = None,
) -> list[Self]:
"""获取消息查询query
+6 -6
View File
@@ -159,11 +159,9 @@ class ImageTemplate:
column_data = []
for i in range(len(column_name)):
c = []
for item in data_list:
if len(item) > i:
c.append(
item[i] if isinstance(item[i], tuple | list) else str(item[i])
)
for lst in data_list:
if len(lst) > i:
c.append(str(lst[i]))
else:
c.append("")
column_data.append(c)
@@ -199,7 +197,7 @@ class ImageTemplate:
style = RowStyle(font=font)
if text_style:
style = text_style(column_name[i], item)
if isinstance(item, tuple | list):
if isinstance(item, tuple):
"""图片"""
data, width, height = item
image_ = None
@@ -221,6 +219,8 @@ class ImageTemplate:
)
cur_h += base_h + row_space
column_image_list.append(background)
# height = max([bk.height for bk in column_image_list])
# width = sum([bk.width for bk in column_image_list])
return await BuildImage.auto_paste(
column_image_list, len(column_image_list), column_space
)
+2 -12
View File
@@ -431,20 +431,14 @@ class AsyncHttpx:
class AsyncPlaywright:
@classmethod
@asynccontextmanager
async def new_page(
cls, cookies: list[dict[str, Any]] | dict[str, Any] | None = None, **kwargs
) -> AsyncGenerator[Page, None]:
async def new_page(cls, **kwargs) -> AsyncGenerator[Page, None]:
"""获取一个新页面
参数:
cookies: cookies
user_agent: 请求头
"""
browser = await get_browser()
ctx = await browser.new_context(**kwargs)
if cookies:
if isinstance(cookies, dict):
cookies = [cookies]
await ctx.add_cookies(cookies) # type: ignore
page = await ctx.new_page()
try:
yield page
@@ -467,7 +461,6 @@ class AsyncPlaywright:
timeout: float | None = None,
type_: Literal["jpeg", "png"] | None = None,
user_agent: str | None = None,
cookies: list[dict[str, Any]] | dict[str, Any] | None = None,
**kwargs,
) -> UniMessage | None:
"""截图,该方法仅用于简单快捷截图,复杂截图请操作 page
@@ -481,8 +474,6 @@ class AsyncPlaywright:
wait_until: 等待类型
timeout: 超时限制
type_: 保存类型
user_agent: user_agent
cookies: cookies
"""
if viewport_size is None:
viewport_size = {"width": 2560, "height": 1080}
@@ -491,7 +482,6 @@ class AsyncPlaywright:
wait_time = wait_time * 1000 if wait_time else None
element_list = [element] if isinstance(element, str) else element
async with cls.new_page(
cookies,
viewport=viewport_size,
user_agent=user_agent,
**kwargs,