mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-29 08:39:59 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1862e9c68 | ||
|
|
731bde8e28 | ||
|
|
47c87f8694 | ||
|
|
5558cdacc3 | ||
|
|
4650d244a5 | ||
|
|
c65b8aae07 | ||
|
|
7ba2fb9584 | ||
|
|
9a2cfd0c9b | ||
|
|
a7d61b1f2b | ||
|
|
7ab185f7d7 | ||
|
|
724541d691 | ||
|
|
baa6cda9c2 | ||
|
|
9c987e9aad | ||
|
|
134974ea9c | ||
|
|
2d946958e3 | ||
|
|
ffe010e9ce | ||
|
|
1c67252b0c | ||
|
|
60c1be25d4 | ||
|
|
18323a0916 | ||
|
|
9d98abded9 | ||
|
|
b06a742b8c | ||
|
|
87fe44f188 | ||
|
|
96f6ef1693 | ||
|
|
23c4141e55 | ||
|
|
c21200c2a9 | ||
|
|
4a8b94259d | ||
|
|
28a58df27b | ||
|
|
caaf486bbe | ||
|
|
0a972a399e | ||
|
|
245dad1239 | ||
|
|
a6b6a2fbff | ||
|
|
53000a949b | ||
|
|
eae1dd59a7 | ||
|
|
e6347a76a8 | ||
|
|
13e0fb3df7 | ||
|
|
3f1b23ef8a | ||
|
|
acae7f0658 | ||
|
|
a9456b1bd6 | ||
|
|
e4d5a230d2 | ||
|
|
fedf44b9fa | ||
|
|
451db104e8 | ||
|
|
9ac4c7f4e3 | ||
|
|
7cf1c6f016 | ||
|
|
6dd87ee301 | ||
|
|
96837fa8db | ||
|
|
d816e76962 | ||
|
|
310945d9cd | ||
|
|
1aba480495 | ||
|
|
07407749fe | ||
|
|
dfab70dd90 | ||
|
|
3b15469c98 | ||
|
|
e4590800c2 | ||
|
|
c63d4b8ad0 | ||
|
|
1fb913e2ed | ||
|
|
72ace2dab7 | ||
|
|
3bcef18629 | ||
|
|
c91758519c | ||
|
|
9c8f2e7aa0 | ||
|
|
5f377af6d5 | ||
|
|
570b5b1766 | ||
|
|
c4d53d58eb | ||
|
|
ed65033cf1 | ||
|
|
41836bde26 | ||
|
|
8381858742 | ||
|
|
1040bd0d58 | ||
|
|
404e47332e | ||
|
|
3db128785e | ||
|
|
2189ea11df | ||
|
|
49b9bd65b0 | ||
|
|
b3fdb1ff58 | ||
|
|
c79e3fdcf0 | ||
|
|
d7509f7030 | ||
|
|
5c99b55502 | ||
|
|
fd255f022b | ||
|
|
8d3f101a68 | ||
|
|
c8ac2b6281 | ||
|
|
d7d8efd305 | ||
|
|
a4bc04c958 | ||
|
|
5fc208c95a | ||
|
|
02743d5955 | ||
|
|
a7ffebea35 | ||
|
|
1de5ae09bc | ||
|
|
8b735f1cb5 | ||
|
|
9cdd2326c8 | ||
|
|
d75653631d | ||
|
|
e234c8906e |
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Issue template
|
||||
about: " issue template's purpose here."
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### 系统版本:Ubuntu 20.04
|
||||
### 真寻版本:0.1.5.3
|
||||
|
||||
### 错误截图
|
||||
|
||||
[img]
|
||||
|
||||
### 日志截图
|
||||
|
||||
[img]
|
||||
|
||||
### 错误说明
|
||||
|
||||
发生了xx错误...
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Generate poetry.lock
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- 'pyproject.toml'
|
||||
|
||||
jobs:
|
||||
poetry_lock:
|
||||
name: Generate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: he0119/setup-python@main
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: poetry lock
|
||||
run: |
|
||||
rm -f poetry.lock
|
||||
sed -i.bak "8,11d" pyproject.toml
|
||||
poetry lock
|
||||
mv pyproject.toml.bak pyproject.toml
|
||||
# commit
|
||||
- name: commit file
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git add poetry.lock
|
||||
git commit -m "Auto generate poetry.lock" -a
|
||||
# push
|
||||
- name: push changes
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,9 @@
|
||||
### 系统版本:Ubuntu 20.04
|
||||
### 真寻版本:0.1.5.3
|
||||
|
||||
### 错误截图
|
||||
|
||||
[img]
|
||||
|
||||
### 错误说明
|
||||
发生了xx错误...
|
||||
@@ -18,6 +18,11 @@
|
||||
## 声明
|
||||
此项目仅用于学习交流,请勿用于非法用途
|
||||
|
||||
# Nonebot2
|
||||
<img style="height: 200px;width: 200px;" src="https://camo.githubusercontent.com/0ef71e86056da694c540790aa4a4e314396884d6c4fdb95362a7538b27a1b034/68747470733a2f2f76322e6e6f6e65626f742e6465762f6c6f676f2e706e67">
|
||||
|
||||
非常 [ **[NICE](https://github.com/nonebot/nonebot2)** ] 的OneBot框架
|
||||
|
||||
## 未完成的文档
|
||||
|
||||
# [传送门](https://hibikier.github.io/zhenxun_bot/)
|
||||
@@ -109,7 +114,7 @@
|
||||
- [x] 获取b站视频封面
|
||||
- [x] 通过PID获取图片
|
||||
- [x] 功能统计可视化
|
||||
|
||||
- [x] 词云
|
||||
- [x] 关于
|
||||
|
||||
### 已实现的管理员功能
|
||||
@@ -231,13 +236,67 @@ python bot.py
|
||||
|
||||
## 使用Docker
|
||||
__Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
|
||||
#### GitHub:[Sakuracio/zxenv](https://github.com/Sakuracio/zxenv)
|
||||
#### GitHub:[Sakuracio/zhenxun_bot_docker](https://github.com/Sakuracio/zhenxun_bot_docker)
|
||||
#### DockerHub:[hibikier/zhenxun_bot](https://hub.docker.com/r/hibikier/zhenxun_bot)
|
||||
|
||||
|
||||
|
||||
## 更新
|
||||
|
||||
### 2022/5/29 \[v0.1.5.7]
|
||||
|
||||
* 修复错误的resource路径会删除IMAGE_PATH等重要文件夹
|
||||
* 提供了真寻适配仓库的插件 安装/卸载 操作
|
||||
|
||||
### 2022/5/28
|
||||
|
||||
* 修复私聊无法添加昵称
|
||||
* 修复原神玩家查询层岩巨渊地下矿区没开时报错
|
||||
* 修复 ```休息吧``` 无法阻断戳一戳
|
||||
* 当图库无图片时,戳一戳将略过发送图片
|
||||
* 新增搜图提供配置项```ALLOW_GROUP_R18```:允许在群聊中使用r18参数
|
||||
* 新增自动更新插件```UPDATE_REMIND```:新版本提醒,原配置项```AUTO_UPDATE_ZHENXUN```改为自动更新升级
|
||||
* black_word新增当群权限为-1时不再检测该群
|
||||
* 修复非超级用户绑定原神cookie会被black_word阻拦
|
||||
* 修复webui中plugins2setting修改时会改变plugins2setting.cmd为字符串
|
||||
* 修复微博热搜报错,发红包小bug [@pull/688](https://github.com/HibiKier/zhenxun_bot/pull/688)
|
||||
* 更多的中文提示
|
||||
|
||||
### 2022/5/26
|
||||
|
||||
* 修复\[滴滴滴]会被转义成[滴滴滴]导致无法触发的问题
|
||||
* 将一些错误以中文提示输出
|
||||
* 更新BT搜索源地址 [@pull/668](https://github.com/HibiKier/zhenxun_bot/pull/668)
|
||||
* 更新抽卡插件 [@pull/667](https://github.com/HibiKier/zhenxun_bot/pull/667)
|
||||
|
||||
### 2022/5/25
|
||||
|
||||
* 修复webui中CountLimit字段limit_type类型错误
|
||||
* 修改nickname插件:一处文案错误,添加敏感词 [@pull/624](https://github.com/HibiKier/zhenxun_bot/pull/624)
|
||||
* gamedraw的ba卡池搬运了过来并且进行了真寻的适配 [@pull/617](https://github.com/HibiKier/zhenxun_bot/pull/617)
|
||||
* feat: stream downloading and progress [@pull/607](https://github.com/HibiKier/zhenxun_bot/pull/607)
|
||||
* 修改翻译插件,添加缺失的冒号 [@pull/602](https://github.com/HibiKier/zhenxun_bot/pull/602)
|
||||
* 修复碧蓝航线/明日方舟up池解析出错的问题 [@pull/610](https://github.com/HibiKier/zhenxun_bot/pull/602)
|
||||
|
||||
### 2022/5/24
|
||||
|
||||
* fix: 修正了签到时日期时间的显示不补齐零的bug (符合日期时间表示法 ISO 8601)[@pull/600](https://github.com/HibiKier/zhenxun_bot/pull/600)
|
||||
* 更新"微博热搜"接口 [@pull/579](https://github.com/HibiKier/zhenxun_bot/pull/579)
|
||||
* refactor&fix(manager): modify argument [@pull/576](https://github.com/HibiKier/zhenxun_bot/pull/576)
|
||||
* 修复复读不能复读图片的问题 [@pull/573](https://github.com/HibiKier/zhenxun_bot/pull/573)
|
||||
* 修复抽卡插件:方舟抽卡的抽取和显示问题 [@pull/581](https://github.com/HibiKier/zhenxun_bot/pull/581)
|
||||
|
||||
### 2022/5/23 \[v0.1.5.6]
|
||||
|
||||
* 修复"清除已删除插件数据" [@pull/545](https://github.com/HibiKier/zhenxun_bot/pull/545)
|
||||
* 修复有置顶的up主B站动态获取失败 [@pull/552](https://github.com/HibiKier/zhenxun_bot/pull/552)
|
||||
* 添加pixiv搜图多关键词支持;修复p站搜图数量参数问题 [@pull/441](https://github.com/HibiKier/zhenxun_bot/pull/441)
|
||||
* 修复开箱更新价格错误传参
|
||||
* 修复pix无法正确查询uid
|
||||
* 新增色图插件添加配置项```ALLOW_GROUP_R18```:允许群聊中使用色图r
|
||||
* 新增PIX插件添加配置项```ALLOW_GROUP_SETU```:允许非超级用户使用-s参数
|
||||
* 新增PIX插件添加配置项```ALLOW_GROUP_R18```:允许非超级用户使用-r参数
|
||||
|
||||
### 2022/5/22 \[v0.1.5.4]
|
||||
|
||||
* 使用action自动更新poetry.lock [@pull/515](https://github.com/HibiKier/zhenxun_bot/pull/515)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__: v0.1.5.4
|
||||
__version__: v0.1.5.6
|
||||
@@ -2,6 +2,7 @@ from configs.config import Config
|
||||
from models.chat_history import ChatHistory
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent
|
||||
from utils.utils import get_message_text
|
||||
|
||||
from ._rule import rule
|
||||
|
||||
@@ -22,10 +23,10 @@ chat_history = on_message(rule=rule, priority=1, block=False)
|
||||
async def _(event: MessageEvent):
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
await ChatHistory.add_chat_msg(
|
||||
event.user_id, event.group_id, str(event.get_message())
|
||||
event.user_id, event.group_id, str(event.get_message()), get_message_text(event.json())
|
||||
)
|
||||
else:
|
||||
await ChatHistory.add_chat_msg(event.user_id, None, str(event.get_message()))
|
||||
await ChatHistory.add_chat_msg(event.user_id, None, str(event.get_message()), get_message_text(event.json()))
|
||||
|
||||
|
||||
# @test.handle()
|
||||
|
||||
@@ -91,7 +91,10 @@ async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State):
|
||||
except AttributeError:
|
||||
pass
|
||||
# 群黑名单检测 群总开关检测
|
||||
if isinstance(event, GroupMessageEvent) or matcher.plugin_name == other_limit_plugins:
|
||||
if (
|
||||
isinstance(event, GroupMessageEvent)
|
||||
or matcher.plugin_name in other_limit_plugins
|
||||
):
|
||||
try:
|
||||
if (
|
||||
group_manager.get_group_level(event.group_id) < 0
|
||||
@@ -148,7 +151,11 @@ async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State):
|
||||
raise IgnoredException("权限不足")
|
||||
if module in plugins2info_dict.keys() and matcher.priority not in [1, 999]:
|
||||
# 戳一戳单独判断
|
||||
if isinstance(event, GroupMessageEvent) or isinstance(event, PokeNotifyEvent) or matcher.plugin_name in other_limit_plugins:
|
||||
if (
|
||||
isinstance(event, GroupMessageEvent)
|
||||
or isinstance(event, PokeNotifyEvent)
|
||||
or matcher.plugin_name in other_limit_plugins
|
||||
):
|
||||
if status_message_manager.get(event.group_id) is None:
|
||||
status_message_manager.delete(event.group_id)
|
||||
if plugins2info_dict[module]["level"] > group_manager.get_group_level(
|
||||
|
||||
@@ -20,26 +20,36 @@ def init_none_plugin_count_manager():
|
||||
"""
|
||||
清除已删除插件数据
|
||||
"""
|
||||
modules = [x.module for x in get_matchers()]
|
||||
for module in none_plugin_count_manager.keys():
|
||||
if module not in modules:
|
||||
none_plugin_count_manager.add_count(module)
|
||||
else:
|
||||
none_plugin_count_manager.reset(module)
|
||||
if none_plugin_count_manager.check(module):
|
||||
modules = [x.plugin_name for x in get_matchers()]
|
||||
plugins_manager_list = list(plugins_manager.keys())
|
||||
for module in plugins_manager_list:
|
||||
if module not in modules or none_plugin_count_manager.check(module):
|
||||
try:
|
||||
plugin_name = plugins_manager.get(module)["plugin_name"]
|
||||
except (AttributeError, KeyError):
|
||||
plugin_name = ""
|
||||
try:
|
||||
plugins2settings_manager.delete(module)
|
||||
plugins2count_manager.delete(module)
|
||||
plugins2cd_manager.delete(module)
|
||||
plugins2block_manager.delete(module)
|
||||
plugins_manager.delete(module)
|
||||
resources_manager.remove_resource(module)
|
||||
logger.info(f"{module}:{plugin_name} 插件疑似已删除,清除对应插件数据...")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"{module}:{plugin_name} 插件疑似已删除,清除对应插件数据失败...{type(e)}:{e}"
|
||||
)
|
||||
if none_plugin_count_manager.check(module):
|
||||
try:
|
||||
plugins2settings_manager.delete(module)
|
||||
plugins2settings_manager.save()
|
||||
plugins2count_manager.delete(module)
|
||||
plugins2count_manager.save()
|
||||
plugins2cd_manager.delete(module)
|
||||
plugins2cd_manager.save()
|
||||
plugins2block_manager.delete(module)
|
||||
plugins2block_manager.save()
|
||||
plugins_manager.delete(module)
|
||||
plugins_manager.save()
|
||||
resources_manager.remove_resource(module)
|
||||
none_plugin_count_manager.delete(module)
|
||||
logger.info(f"{module}:{plugin_name} 插件疑似已删除,清除对应插件数据...")
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"{module}:{plugin_name} 插件疑似已删除,清除对应插件数据失败...{type(e)}:{e}")
|
||||
else:
|
||||
none_plugin_count_manager.add_count(module)
|
||||
logger.info(
|
||||
f"{module}:{plugin_name} 插件疑似已删除,加载{none_plugin_count_manager._max_count}次失败后将清除对应插件数据,当前次数:{none_plugin_count_manager._data[module]}")
|
||||
else:
|
||||
none_plugin_count_manager.reset(module)
|
||||
none_plugin_count_manager.save()
|
||||
|
||||
@@ -3,6 +3,7 @@ from ruamel.yaml import round_trip_load, round_trip_dump, YAML
|
||||
from utils.manager import admin_manager, plugins_manager
|
||||
from configs.config import Config
|
||||
from services.log import logger
|
||||
from utils.text_utils import prompt2cn
|
||||
from utils.utils import get_matchers
|
||||
from ruamel import yaml
|
||||
import nonebot
|
||||
@@ -56,7 +57,9 @@ def init_plugins_config(data_path):
|
||||
_override=True,
|
||||
)
|
||||
else:
|
||||
Config.add_plugin_config(matcher.plugin_name, key, plugin_configs[key])
|
||||
Config.add_plugin_config(
|
||||
matcher.plugin_name, key, plugin_configs[key]
|
||||
)
|
||||
else:
|
||||
plugin_configs = _data[matcher.plugin_name]
|
||||
for key in plugin_configs:
|
||||
@@ -79,7 +82,10 @@ def init_plugins_config(data_path):
|
||||
_data[plugin].yaml_set_start_comment(plugin_name, indent=2)
|
||||
# 初始化未设置的管理员权限等级
|
||||
for k, v in Config.get_admin_level_data():
|
||||
admin_manager.set_admin_level(k, v)
|
||||
try:
|
||||
admin_manager.set_admin_level(k, v)
|
||||
except KeyError as e:
|
||||
raise KeyError(f"{e} ****** 请检查是否有插件加载失败 ******")
|
||||
# 存完插件基本设置
|
||||
with open(plugins2config_file, "w", encoding="utf8") as wf:
|
||||
round_trip_dump(
|
||||
@@ -96,14 +102,23 @@ def init_plugins_config(data_path):
|
||||
for plugin in Config.keys():
|
||||
_tmp_data[plugin] = {}
|
||||
for k in Config[plugin].keys():
|
||||
if _data.get(plugin) and k in _data[plugin].keys():
|
||||
Config.set_config(plugin, k, _data[plugin][k])
|
||||
if level2module := Config.get_level2module(plugin, k):
|
||||
try:
|
||||
admin_manager.set_admin_level(level2module, _data[plugin][k])
|
||||
except KeyError:
|
||||
logger.warning(f"{level2module} 设置权限等级失败:{_data[plugin][k]}")
|
||||
_tmp_data[plugin][k] = Config.get_config(plugin, k)
|
||||
try:
|
||||
if _data.get(plugin) and k in _data[plugin].keys():
|
||||
Config.set_config(plugin, k, _data[plugin][k])
|
||||
if level2module := Config.get_level2module(plugin, k):
|
||||
try:
|
||||
admin_manager.set_admin_level(
|
||||
level2module, _data[plugin][k]
|
||||
)
|
||||
except KeyError:
|
||||
logger.warning(
|
||||
f"{level2module} 设置权限等级失败:{_data[plugin][k]}"
|
||||
)
|
||||
_tmp_data[plugin][k] = Config.get_config(plugin, k)
|
||||
except AttributeError as e:
|
||||
raise AttributeError(
|
||||
f"{e}\n" + prompt2cn("可能为config.yaml配置文件填写不规范", 46)
|
||||
)
|
||||
Config.save()
|
||||
temp_file = Path() / "configs" / "temp_config.yaml"
|
||||
try:
|
||||
|
||||
@@ -28,7 +28,7 @@ def init_plugins_cd_limit(data_path):
|
||||
_module = _plugin.module
|
||||
plugin_cd_limit = _module.__getattribute__("__plugin_cd_limit__")
|
||||
plugins2cd_manager.add_cd_limit(
|
||||
matcher.plugin_name, data_dict=plugin_cd_limit
|
||||
matcher.plugin_name, **plugin_cd_limit
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
@@ -76,7 +76,7 @@ def init_plugins_block_limit(data_path):
|
||||
_module = _plugin.module
|
||||
plugin_block_limit = _module.__getattribute__("__plugin_block_limit__")
|
||||
plugins2block_manager.add_block_limit(
|
||||
matcher.plugin_name, data_dict=plugin_block_limit
|
||||
matcher.plugin_name, **plugin_block_limit
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
@@ -124,7 +124,7 @@ def init_plugins_count_limit(data_path):
|
||||
_module = _plugin.module
|
||||
plugin_count_limit = _module.__getattribute__("__plugin_count_limit__")
|
||||
plugins2count_manager.add_count_limit(
|
||||
matcher.plugin_name, data_dict=plugin_count_limit
|
||||
matcher.plugin_name, **plugin_count_limit
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, PrivateMessageEvent, Message
|
||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, PrivateMessageEvent, Message, MessageEvent
|
||||
from nonebot import on_command
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.rule import to_me
|
||||
@@ -24,7 +24,7 @@ __plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_configs__ = {
|
||||
"BLACK_WORD": {
|
||||
"value": ["爸", "爹", "爷"],
|
||||
"value": ["爸", "爹", "爷", "父亲"],
|
||||
"help": "昵称所屏蔽的关键词,会被替换为 *",
|
||||
"default_value": None
|
||||
}
|
||||
@@ -47,7 +47,7 @@ cancel_nickname = on_command("取消昵称", rule=to_me(), priority=5, block=Tru
|
||||
|
||||
|
||||
@nickname.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()):
|
||||
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
msg = arg.extract_plain_text().strip()
|
||||
if not msg:
|
||||
await nickname.finish("叫你空白?叫你虚空?叫你无名??", at_sender=True)
|
||||
@@ -73,7 +73,7 @@ async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()):
|
||||
f"好啦好啦,我知道啦,{msg},以后就这么叫你吧",
|
||||
f"嗯嗯,{NICKNAME}记住你的昵称了哦,{msg}",
|
||||
f"好突然,突然要叫你昵称什么的...{msg}..",
|
||||
f"{NICKNAME}会好好记住的{msg}的,放心吧",
|
||||
f"{NICKNAME}会好好记住{msg}的,放心吧",
|
||||
f"好..好.,那窝以后就叫你{msg}了.",
|
||||
]
|
||||
)
|
||||
@@ -90,7 +90,7 @@ async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()):
|
||||
f"好啦好啦,我知道啦,{msg},以后就这么叫你吧",
|
||||
f"嗯嗯,{NICKNAME}记住你的昵称了哦,{msg}",
|
||||
f"好突然,突然要叫你昵称什么的...{msg}..",
|
||||
f"{NICKNAME}会好好记住的{msg}的,放心吧",
|
||||
f"{NICKNAME}会好好记住{msg}的,放心吧",
|
||||
f"好..好.,那窝以后就叫你{msg}了.",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
from nonebot import on_command, on_regex
|
||||
|
||||
from .data_source import show_plugin_repo, install_plugin, uninstall_plugin
|
||||
from services.log import logger
|
||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent
|
||||
from nonebot.params import CommandArg
|
||||
|
||||
from utils.message_builder import image
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
|
||||
__zx_plugin_name__ = "插件商店 [Superuser]"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
下载安装插件
|
||||
指令:
|
||||
查看插件仓库
|
||||
安装插件 [name/id]
|
||||
卸载插件 [name/id]
|
||||
""".strip()
|
||||
__plugin_des__ = "从真寻适配仓库中下载插件"
|
||||
__plugin_cmd__ = [""]
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
|
||||
show_repo = on_regex("^查看插件仓库$", priority=1, block=True, permission=SUPERUSER)
|
||||
|
||||
install_plugin_matcher = on_command("安装插件", priority=1, block=True, permission=SUPERUSER)
|
||||
|
||||
uninstall_plugin_matcher = on_command("卸载插件", priority=1, block=True, permission=SUPERUSER)
|
||||
|
||||
|
||||
@install_plugin_matcher.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
arg = arg.extract_plain_text().strip()
|
||||
msg = await install_plugin(arg)
|
||||
await install_plugin_matcher.send(msg)
|
||||
|
||||
|
||||
@uninstall_plugin_matcher.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
arg = arg.extract_plain_text().strip()
|
||||
msg = await uninstall_plugin(arg)
|
||||
await install_plugin_matcher.send(msg)
|
||||
|
||||
|
||||
@show_repo.handle()
|
||||
async def _(bot: Bot, event: MessageEvent):
|
||||
msg = await show_plugin_repo()
|
||||
if isinstance(msg, int):
|
||||
await show_repo.finish("文件下载失败或解压失败..")
|
||||
await show_repo.send(image(b64=msg))
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
|
||||
f" 查看插件仓库"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Union, Tuple
|
||||
|
||||
import ujson as json
|
||||
|
||||
from services import logger
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from configs.path_config import TEMP_PATH, DATA_PATH
|
||||
from utils.image_utils import text2image, BuildImage
|
||||
from utils.utils import is_number
|
||||
|
||||
path = DATA_PATH / "plugin_shop"
|
||||
if not path.exists():
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
repo_json_url = "https://github.com/zhenxun-org/nonebot_plugins_zhenxun_bot/archive/refs/heads/index.zip"
|
||||
repo_zip_path = TEMP_PATH / "plugin_repo_json.zip"
|
||||
plugin_json = path / "zhenxun_plugins.json"
|
||||
|
||||
extensive_plugin_path = Path() / "extensive_plugin"
|
||||
|
||||
path = DATA_PATH / "plugin_shop"
|
||||
if not path.exists():
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
if not extensive_plugin_path.exists():
|
||||
extensive_plugin_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
data = {}
|
||||
|
||||
|
||||
async def install_plugin(name: str) -> str:
|
||||
"""
|
||||
安装插件
|
||||
:param name: 插件名或下标
|
||||
"""
|
||||
try:
|
||||
if is_number(name):
|
||||
name, code = await get_plugin_name(int(name))
|
||||
if code != 200:
|
||||
return name
|
||||
plugin_url = data[name]["download_url"]
|
||||
url = (await AsyncHttpx.get(plugin_url)).headers.get("Location")
|
||||
zip_file = TEMP_PATH / f"{name}.zip"
|
||||
if zip_file.exists():
|
||||
zip_file.unlink()
|
||||
if await AsyncHttpx.download_file(url, zip_file):
|
||||
logger.debug("开始解压插件压缩包...")
|
||||
# 解压
|
||||
zf = zipfile.ZipFile(zip_file, "r")
|
||||
extract_path = TEMP_PATH / f"{name}"
|
||||
if extract_path.exists():
|
||||
shutil.rmtree(extract_path.absolute(), ignore_errors=True)
|
||||
extract_path.mkdir(exist_ok=True, parents=True)
|
||||
for file in zf.namelist():
|
||||
zf.extract(file, extract_path)
|
||||
zf.close()
|
||||
logger.debug("解压插件压缩包完成...")
|
||||
logger.debug("开始移动插件文件夹...")
|
||||
if (extensive_plugin_path / f"{name}").exists():
|
||||
logger.debug("extensive_plugin目录下文件夹已存在,删除该目录插件文件夹...")
|
||||
shutil.rmtree(
|
||||
(extensive_plugin_path / f"{name}").absolute(), ignore_errors=True
|
||||
)
|
||||
extract_path.rename(extensive_plugin_path / f"{name}")
|
||||
tmp = ""
|
||||
if "pyproject.toml" in os.listdir(extensive_plugin_path / f"{name}"):
|
||||
tmp = "检测到该插件含有额外依赖,当前安装无法保证依赖完全安装成功。"
|
||||
os.system(
|
||||
f"poetry run pip install -r {(extensive_plugin_path / f'{name}' / 'pyproject.toml').absolute()}"
|
||||
)
|
||||
logger.debug("移动插件文件夹完成...")
|
||||
logger.info(f"成功安装插件 {name} 成功!\n{tmp}")
|
||||
return f"成功安装插件 {name},请重启bot!"
|
||||
except Exception as e:
|
||||
logger.error(f"安装插件 {name} 失败 {type(e)}:{e}")
|
||||
return f"安装插件 {name} 失败 {type(e)}:{e}"
|
||||
|
||||
|
||||
async def uninstall_plugin(name: str) -> str:
|
||||
"""
|
||||
删除插件
|
||||
:param name: 插件名或下标
|
||||
"""
|
||||
try:
|
||||
if is_number(name):
|
||||
name, code = await get_plugin_name(int(name))
|
||||
if code != 200:
|
||||
return name
|
||||
if name not in os.listdir(extensive_plugin_path):
|
||||
return f"未安装 {name} 插件!"
|
||||
shutil.rmtree((extensive_plugin_path / name).absolute(), ignore_errors=True)
|
||||
logger.info(f"插件 {name} 删除成功!")
|
||||
return f"插件 {name} 删除成功!"
|
||||
except Exception as e:
|
||||
logger.error(f"删除插件 {name} 失败 {type(e)}:{e}")
|
||||
return f"删除插件 {name} 失败 {type(e)}:{e}"
|
||||
|
||||
|
||||
async def show_plugin_repo() -> Union[int, str]:
|
||||
"""
|
||||
获取插件仓库数据并格式化
|
||||
"""
|
||||
if not plugin_json.exists():
|
||||
code = await download_json()
|
||||
if code != 200:
|
||||
return code
|
||||
plugin_info = json.load(open(plugin_json, "r", encoding="utf8"))
|
||||
image_list = []
|
||||
w, h = 0, 0
|
||||
line_height = 10
|
||||
for i, key in enumerate(plugin_info.keys()):
|
||||
data[key] = {
|
||||
"名称": plugin_info[key]["plugin_name"],
|
||||
"模块": key,
|
||||
"作者": plugin_info[key]["author"],
|
||||
"版本": plugin_info[key]["version"],
|
||||
"简介": plugin_info[key]["introduction"],
|
||||
"download_url": plugin_info[key]["download_url"],
|
||||
"github_url": plugin_info[key]["github_url"],
|
||||
}
|
||||
s = (
|
||||
f'id:{i+1}\n名称:{plugin_info[key]["plugin_name"]}\n'
|
||||
f'模块:{key}\n'
|
||||
f'作者:{plugin_info[key]["author"]}\n'
|
||||
f'版本:{plugin_info[key]["version"]}\n'
|
||||
f'简介:{plugin_info[key]["introduction"]}\n'
|
||||
f"-------------------"
|
||||
)
|
||||
img = await text2image(s, font_size=20, color="#f9f6f2")
|
||||
w = w if w > img.w else img.w
|
||||
h += img.h + line_height
|
||||
image_list.append(img)
|
||||
A = BuildImage(w + 100, h + 100, color="#f9f6f2")
|
||||
cur_h = 50
|
||||
for img in image_list:
|
||||
await A.apaste(img, (50, cur_h))
|
||||
cur_h += img.h + line_height
|
||||
return A.pic2bs4()
|
||||
|
||||
|
||||
async def download_json() -> int:
|
||||
"""
|
||||
下载插件库json文件
|
||||
"""
|
||||
try:
|
||||
url = (await AsyncHttpx.get(repo_json_url)).headers.get("Location")
|
||||
if repo_zip_path.exists():
|
||||
repo_zip_path.unlink()
|
||||
if await AsyncHttpx.download_file(url, repo_zip_path):
|
||||
zf = zipfile.ZipFile(repo_zip_path, "r")
|
||||
extract_path = path / "temp"
|
||||
for file in zf.namelist():
|
||||
zf.extract(file, extract_path)
|
||||
zf.close()
|
||||
if plugin_json.exists():
|
||||
plugin_json.unlink()
|
||||
(
|
||||
extract_path
|
||||
/ "nonebot_plugins_zhenxun_bot-index"
|
||||
/ "zhenxun_plugins.json"
|
||||
).rename(plugin_json)
|
||||
shutil.rmtree(extract_path.absolute(), ignore_errors=True)
|
||||
return 200
|
||||
except Exception as e:
|
||||
logger.error(f"下载插件库压缩包失败或解压失败 {type(e)}:{e}")
|
||||
return 999
|
||||
|
||||
|
||||
async def get_plugin_name(name: int) -> Tuple[str, int]:
|
||||
"""
|
||||
通过下标获取插件名
|
||||
:param name: 下标
|
||||
"""
|
||||
name = int(name)
|
||||
if not data:
|
||||
await show_plugin_repo()
|
||||
if name < 1 or name > len(data.keys()):
|
||||
return "下标超过上下限!", 999
|
||||
name = list(data.keys())[name - 1]
|
||||
return name, 200
|
||||
@@ -108,6 +108,10 @@ async def _():
|
||||
"ALTER TABLE genshin ADD bind_group Integer;",
|
||||
"genshin"
|
||||
), # 新增原神群号绑定字段
|
||||
(
|
||||
"ALTER TABLE chat_history ADD plain_text Text;",
|
||||
"chat_history"
|
||||
), # 新增纯文本
|
||||
]
|
||||
for sql in sql_str:
|
||||
try:
|
||||
|
||||
@@ -12,6 +12,7 @@ driver.on_shutdown(disconnect)
|
||||
nonebot.load_plugin("nonebot_plugin_apscheduler")
|
||||
nonebot.load_plugins("basic_plugins")
|
||||
nonebot.load_plugins("plugins")
|
||||
nonebot.load_plugins("extensive_plugin")
|
||||
# 最后加载权限控制
|
||||
nonebot.load_plugins("basic_plugins/hooks")
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from typing import Optional, Any, Union
|
||||
from pathlib import Path
|
||||
from ruamel.yaml import YAML
|
||||
from ruamel import yaml
|
||||
from ruamel.yaml.scanner import ScannerError
|
||||
|
||||
|
||||
class ConfigsManager:
|
||||
@@ -21,9 +22,24 @@ class ConfigsManager:
|
||||
if file.exists():
|
||||
with open(file, "r", encoding="utf8") as f:
|
||||
self._data = _yaml.load(f)
|
||||
if not self._data:
|
||||
self.file.unlink()
|
||||
raise ValueError(
|
||||
"配置文件为空!\n"
|
||||
"***********************************************************\n"
|
||||
"****** 配置文件 plugins2config.yaml 为空,已删除,请重启 ******\n"
|
||||
"***********************************************************"
|
||||
)
|
||||
if self._simple_file.exists():
|
||||
with open(self._simple_file, "r", encoding="utf8") as f:
|
||||
self._simple_data = _yaml.load(f)
|
||||
try:
|
||||
with open(self._simple_file, "r", encoding="utf8") as f:
|
||||
self._simple_data = _yaml.load(f)
|
||||
except ScannerError as e:
|
||||
raise ScannerError(
|
||||
f"{e}\n**********************************************\n"
|
||||
f"****** 可能为config.yaml配置文件填写不规范 ******\n"
|
||||
f"**********************************************"
|
||||
)
|
||||
|
||||
def add_plugin_config(
|
||||
self,
|
||||
@@ -86,7 +102,10 @@ class ConfigsManager:
|
||||
:param value: 值
|
||||
"""
|
||||
if module in self._data.keys():
|
||||
if self._data[module].get(key) is not None and self._data[module][key] != value:
|
||||
if (
|
||||
self._data[module].get(key) is not None
|
||||
and self._data[module][key] != value
|
||||
):
|
||||
self._data[module][key]["value"] = value
|
||||
self._simple_data[module][key] = value
|
||||
self.save()
|
||||
@@ -115,7 +134,9 @@ class ConfigsManager:
|
||||
self._data[module][key]["default_value"] = value
|
||||
self.save()
|
||||
|
||||
def get_config(self, module: str, key: str, default: Optional[Any] = None) -> Optional[Any]:
|
||||
def get_config(
|
||||
self, module: str, key: str, default: Optional[Any] = None
|
||||
) -> Optional[Any]:
|
||||
"""
|
||||
获取指定配置值
|
||||
:param module: 模块名
|
||||
@@ -161,7 +182,11 @@ class ConfigsManager:
|
||||
if save_simple_data:
|
||||
with open(self._simple_file, "w", encoding="utf8") as f:
|
||||
yaml.dump(
|
||||
self._simple_data, f, indent=2, Dumper=yaml.RoundTripDumper, allow_unicode=True
|
||||
self._simple_data,
|
||||
f,
|
||||
indent=2,
|
||||
Dumper=yaml.RoundTripDumper,
|
||||
allow_unicode=True,
|
||||
)
|
||||
path = path if path else self.file
|
||||
with open(path, "w", encoding="utf8") as f:
|
||||
|
||||
@@ -11,12 +11,13 @@ class ChatHistory(db.Model):
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False)
|
||||
group_id = db.Column(db.BigInteger())
|
||||
text = db.Column(db.Text())
|
||||
plain_text = db.Column(db.Text())
|
||||
create_time = db.Column(db.DateTime(timezone=True), nullable=False)
|
||||
|
||||
@classmethod
|
||||
async def add_chat_msg(cls, user_qq: int, group_id: Optional[int], text: str):
|
||||
async def add_chat_msg(cls, user_qq: int, group_id: Optional[int], text: str, plain_text: str):
|
||||
await cls.create(
|
||||
user_qq=user_qq, group_id=group_id, text=text, create_time=datetime.now()
|
||||
user_qq=user_qq, group_id=group_id, text=text, plain_text=plain_text, create_time=datetime.now()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -97,7 +98,6 @@ class ChatHistory(db.Model):
|
||||
if date_scope:
|
||||
sql += f"AND create_time BETWEEN '{date_scope[0]}' AND '{date_scope[1]}' "
|
||||
sql += f"GROUP BY user_qq ORDER BY sum {order if order and order.upper() != 'DES' else ''} LIMIT {limit}"
|
||||
print(sql)
|
||||
return await db.all(db.text(sql))
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from utils.image_utils import BuildImage
|
||||
from utils.message_builder import image
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from typing import Optional, Tuple, Union
|
||||
from configs.config import Config
|
||||
from utils.http_utils import AsyncHttpx
|
||||
@@ -28,24 +24,3 @@ async def get_data(url: str, params: Optional[dict] = None) -> Tuple[Union[dict,
|
||||
return f'发生了错误...code:{data["code"]}', 999
|
||||
except TimeoutError:
|
||||
return "超时了....", 998
|
||||
|
||||
|
||||
def gen_wbtop_pic(data: dict) -> MessageSegment:
|
||||
"""
|
||||
生成微博热搜图片
|
||||
:param data: 微博热搜数据
|
||||
"""
|
||||
bk = BuildImage(700, 32 * 50 + 280, 700, 32, color="#797979")
|
||||
wbtop_bk = BuildImage(700, 280, background=f"{IMAGE_PATH}/other/webtop.png")
|
||||
bk.paste(wbtop_bk)
|
||||
text_bk = BuildImage(700, 32 * 50, 700, 32, color="#797979")
|
||||
for i, data in enumerate(data):
|
||||
title = f"{i+1}. {data['hot_word']}"
|
||||
hot = data["hot_word_num"]
|
||||
img = BuildImage(700, 30, font_size=20)
|
||||
w, h = img.getsize(title)
|
||||
img.text((10, int((30 - h) / 2)), title)
|
||||
img.text((580, int((30 - h) / 2)), hot)
|
||||
text_bk.paste(img)
|
||||
bk.paste(text_bk, (0, 280))
|
||||
return image(b64=bk.pic2bs4())
|
||||
|
||||
@@ -330,7 +330,7 @@ async def get_user_dynamic(
|
||||
"""
|
||||
xs = document.getElementsByClassName('bili-dyn-item__tag');
|
||||
for (x of xs) {
|
||||
x.parentNode.remove();
|
||||
x.parentNode.parentNode.remove();
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -38,7 +38,6 @@ async def create_live_des_image(uid: int, title: str, cover: str, tags: str, des
|
||||
ava = BuildImage(100, 100, background=BytesIO(await get_pic(face)))
|
||||
ava.circle()
|
||||
cover = BuildImage(470, 265, background=BytesIO(await get_pic(cover)))
|
||||
print()
|
||||
|
||||
|
||||
def _create_live_des_image(
|
||||
|
||||
@@ -8,6 +8,7 @@ from nonebot.adapters.onebot.v11 import (
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_preprocessor
|
||||
from utils.image_utils import BuildImage
|
||||
from utils.manager import group_manager
|
||||
from utils.utils import get_message_text, is_number
|
||||
from nonebot.params import RegexGroup, CommandArg
|
||||
from .utils import black_word_manager
|
||||
@@ -155,7 +156,11 @@ async def _(
|
||||
and matcher.plugin_name == "black_word"
|
||||
and not await BanUser.is_ban(event.user_id)
|
||||
and str(event.user_id) not in bot.config.superusers
|
||||
and not get_message_text(event.json()).startswith("原神绑定")
|
||||
):
|
||||
# 屏蔽群权限-1的群
|
||||
if isinstance(event, GroupMessageEvent) and group_manager.get_group_level(event.group_id) < 0:
|
||||
return
|
||||
user_id = event.user_id
|
||||
group_id = event.group_id if isinstance(event, GroupMessageEvent) else None
|
||||
msg = get_message_text(event.json())
|
||||
@@ -241,7 +246,6 @@ async def _(event: MessageEvent, arg: Message = CommandArg()):
|
||||
uid = int(msg[0])
|
||||
id_ = int(msg[1])
|
||||
punish_level = int(msg[2])
|
||||
print(uid, id_, punish_level)
|
||||
rst = await set_user_punish(uid, id_, punish_level)
|
||||
await set_punish.send(rst)
|
||||
logger.info(
|
||||
|
||||
@@ -68,7 +68,6 @@ class BlackWord(db.Model):
|
||||
user = await query.where(cls.black_word == black_word).order_by(cls.id.desc()).gino.first()
|
||||
elif id_:
|
||||
user_list = await query.gino.all()
|
||||
print(len(user_list))
|
||||
if len(user_list) == 0 or (id_ < 0 or id_ > len(user_list)):
|
||||
return False
|
||||
user = user_list[id_]
|
||||
|
||||
@@ -88,9 +88,7 @@ class BlackWordManager:
|
||||
:param group_id: 群号
|
||||
:param message: 消息
|
||||
"""
|
||||
print(user_id, group_id, message)
|
||||
if data := self._check(message):
|
||||
print(data)
|
||||
if data[0]:
|
||||
await _add_user_black_word(
|
||||
user_id, group_id, data[0], message, int(data[1])
|
||||
|
||||
@@ -9,7 +9,7 @@ import platform
|
||||
# asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
|
||||
url = "http://www.eclzz.mobi"
|
||||
url = "http://www.eclzz.win"
|
||||
|
||||
|
||||
async def get_bt_info(keyword: str, page: int):
|
||||
@@ -19,7 +19,7 @@ async def get_bt_info(keyword: str, page: int):
|
||||
:param page: 页数
|
||||
"""
|
||||
text = (await AsyncHttpx.get(f"{url}/s/{keyword}_rel_{page}.html", timeout=5)).text
|
||||
if text.find("大约0条结果") != -1:
|
||||
if "大约0条结果" in text:
|
||||
return
|
||||
soup = BeautifulSoup(text, "lxml")
|
||||
item_lst = soup.find_all("div", {"class": "search-item"})
|
||||
|
||||
@@ -25,6 +25,11 @@ __plugin_cmd__ = ["检查更新真寻", "重启"]
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_configs__ = {
|
||||
"UPDATE_REMIND": {
|
||||
"value": True,
|
||||
"help": "真寻是否自动检查更新",
|
||||
"default": True,
|
||||
},
|
||||
"AUTO_UPDATE_ZHENXUN": {
|
||||
"value": False,
|
||||
"help": "真寻是否自动检查更新",
|
||||
@@ -72,7 +77,7 @@ async def _():
|
||||
await restart.finish("暂无windows重启脚本...")
|
||||
|
||||
|
||||
@restart.got("flag", prompt="确定是否重启真寻?(重启失败咱们将失去联系,请谨慎!)")
|
||||
@restart.got("flag", prompt="确定是否重启真寻?确定请回复[是|好|确定](重启失败咱们将失去联系,请谨慎!)")
|
||||
async def _(flag: str = ArgStr("flag")):
|
||||
if flag.lower() in ["true", "是", "好", "确定", "确定是"]:
|
||||
await restart.send("开始重启真寻..请稍等...")
|
||||
@@ -88,7 +93,7 @@ async def _(flag: str = ArgStr("flag")):
|
||||
minute=0,
|
||||
)
|
||||
async def _():
|
||||
if Config.get_config("check_zhenxun_update", "AUTO_UPDATE_ZHENXUN"):
|
||||
if Config.get_config("check_zhenxun_update", "UPDATE_REMIND"):
|
||||
_version = "v0.0.0"
|
||||
_version_file = Path() / "__version__"
|
||||
if _version_file.exists():
|
||||
@@ -108,17 +113,18 @@ async def _():
|
||||
message=f"检测到真寻版本更新\n"
|
||||
f"当前版本:{_version},最新版本:{latest_version}",
|
||||
)
|
||||
# try:
|
||||
# code = await check_update(bot)
|
||||
# except Exception as e:
|
||||
# logger.error(f"更新真寻未知错误 {type(e)}:{e}")
|
||||
# await bot.send_private_msg(
|
||||
# user_id=int(list(bot.config.superusers)[0]),
|
||||
# message=f"更新真寻未知错误 {type(e)}:{e}\n",
|
||||
# )
|
||||
# else:
|
||||
# if code == 200:
|
||||
# await bot.send_private_msg(
|
||||
# user_id=int(list(bot.config.superusers)[0]),
|
||||
# message=f"更新完毕,请重启真寻....",
|
||||
# )
|
||||
if Config.get_config("check_zhenxun_update", "AUTO_UPDATE_ZHENXUN"):
|
||||
try:
|
||||
code = await check_update(bot)
|
||||
except Exception as e:
|
||||
logger.error(f"更新真寻未知错误 {type(e)}:{e}")
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f"更新真寻未知错误 {type(e)}:{e}\n",
|
||||
)
|
||||
else:
|
||||
if code == 200:
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f"更新完毕,请重启真寻....",
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ __plugin_settings__ = {
|
||||
dialogue_data = {}
|
||||
|
||||
|
||||
dialogue = on_command("[滴滴滴]", aliases={"滴滴滴-"}, priority=5, block=True)
|
||||
dialogue = on_command("[滴滴滴]", aliases={"滴滴滴-"}, priority=5, block=True)
|
||||
reply = on_command("/t", priority=1, permission=SUPERUSER, block=True)
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ from .handles.onmyoji_handle import OnmyojiHandle
|
||||
from .handles.pcr_handle import PcrHandle
|
||||
from .handles.pretty_handle import PrettyHandle
|
||||
from .handles.prts_handle import PrtsHandle
|
||||
from .handles.ba_handle import BaHandle
|
||||
|
||||
from .config import draw_config
|
||||
|
||||
@@ -30,7 +31,7 @@ from .config import draw_config
|
||||
__zx_plugin_name__ = "游戏抽卡"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
模拟赛马娘,原神,明日方舟,坎公骑冠剑,公主连结(国/台),碧蓝航线,FGO,阴阳师进行抽卡
|
||||
模拟赛马娘,原神,明日方舟,坎公骑冠剑,公主连结(国/台),碧蓝航线,FGO,阴阳师,碧蓝档案进行抽卡
|
||||
指令:
|
||||
原神[1-180]抽: 原神常驻池
|
||||
原神角色[1-180]抽: 原神角色UP池子
|
||||
@@ -43,6 +44,7 @@ usage:
|
||||
碧蓝航线/碧蓝[重型/轻型/特型/活动][1-300]抽: 碧蓝航线重型/轻型/特型/活动卡池
|
||||
fgo[1-300]抽: fgo卡池
|
||||
阴阳师[1-300]抽: 阴阳师卡池
|
||||
ba/碧蓝档案[1-200]抽:碧蓝档案卡池
|
||||
* 以上指令可以通过 XX一井 来指定最大抽取数量 *
|
||||
* 示例:原神一井 *
|
||||
""".strip()
|
||||
@@ -73,6 +75,7 @@ __plugin_cmd__ = [
|
||||
"pcr/公主连接[1-300]抽",
|
||||
"fgo[1-300]抽",
|
||||
"阴阳师[1-300]抽",
|
||||
"碧蓝档案[1-200]抽",
|
||||
"更新方舟信息 [_superuser]",
|
||||
"重载方舟卡池 [_superuser]",
|
||||
"更新原神信息 [_superuser]",
|
||||
@@ -83,6 +86,7 @@ __plugin_cmd__ = [
|
||||
"更新碧蓝航线信息 [_superuser]",
|
||||
"更新fgo信息 [_superuser]",
|
||||
"更新阴阳师信息 [_superuser]",
|
||||
"更新碧蓝档案信息 [_superuser]",
|
||||
]
|
||||
__plugin_type__ = ("抽卡相关", 1)
|
||||
__plugin_version__ = 0.1
|
||||
@@ -105,7 +109,7 @@ class Game:
|
||||
|
||||
|
||||
games = (
|
||||
Game({"azur", "碧蓝", "碧蓝航线"}, AzurHandle(), draw_config.AZUR_FLAG),
|
||||
Game({"azur", "碧蓝航线"}, AzurHandle(), draw_config.AZUR_FLAG),
|
||||
Game({"fgo", "命运冠位指定"}, FgoHandle(), draw_config.FGO_FLAG),
|
||||
Game(
|
||||
{"genshin", "原神"},
|
||||
@@ -130,6 +134,11 @@ games = (
|
||||
reload_time=4,
|
||||
),
|
||||
Game({"prts", "方舟", "明日方舟"}, PrtsHandle(), draw_config.PRTS_FLAG, reload_time=4),
|
||||
Game(
|
||||
{"ba","碧蓝档案"},BaHandle(),
|
||||
draw_config.BA_FLAG,
|
||||
max_count=200,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -87,6 +87,12 @@ class OnmyojiConfig(BaseModel, extra=Extra.ignore):
|
||||
ONMYOJI_SR: float = 0.2
|
||||
ONMYOJI_R: float = 0.7875
|
||||
|
||||
#碧蓝档案
|
||||
class BaConfig(BaseModel, extra=Extra.ignore):
|
||||
BA_THREE_P: float = 0.025
|
||||
BA_TWO_P: float = 0.185
|
||||
BA_ONE_P: float = 0.79
|
||||
BA_G_TWO_P: float = 0.975
|
||||
|
||||
class Config(BaseModel, extra=Extra.ignore):
|
||||
# 开关
|
||||
@@ -98,6 +104,7 @@ class Config(BaseModel, extra=Extra.ignore):
|
||||
AZUR_FLAG: bool = AConfig.get_config("draw_card", "AZUR_FLAG")
|
||||
FGO_FLAG: bool = AConfig.get_config("draw_card", "FGO_FLAG")
|
||||
ONMYOJI_FLAG: bool = AConfig.get_config("draw_card", "ONMYOJI_FLAG")
|
||||
BA_FLAG: bool = AConfig.get_config("draw_card", "BA_FLAG")
|
||||
|
||||
# 其他配置
|
||||
PCR_TAI: bool = AConfig.get_config("draw_card", "PCR_TAI")
|
||||
@@ -112,6 +119,7 @@ class Config(BaseModel, extra=Extra.ignore):
|
||||
azur: AzurConfig = AzurConfig()
|
||||
fgo: FgoConfig = FgoConfig()
|
||||
onmyoji: OnmyojiConfig = OnmyojiConfig()
|
||||
ba: BaConfig = BaConfig()
|
||||
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
@@ -133,8 +141,9 @@ for game_flag, game_name in zip(
|
||||
"FGO_FLAG",
|
||||
"ONMYOJI_FLAG",
|
||||
"PCR_TAI",
|
||||
"BA_FLAG"
|
||||
],
|
||||
["明日方舟", "原神", "赛马娘", "坎公骑冠剑", "公主连结", "碧蓝航线", "命运-冠位指定(FGO)", "阴阳师", "pcr台服卡池"],
|
||||
["明日方舟", "原神", "赛马娘", "坎公骑冠剑", "公主连结", "碧蓝航线", "命运-冠位指定(FGO)", "阴阳师", "pcr台服卡池","碧蓝档案"],
|
||||
):
|
||||
AConfig.add_plugin_config(
|
||||
"draw_card",
|
||||
|
||||
@@ -293,7 +293,7 @@ class AzurHandle(BaseHandle[AzurChar]):
|
||||
type_ = ship.xpath("./tbody/tr/td[2]/p/small/text()")[0] # 舰船类型
|
||||
try:
|
||||
p = float(str(ship.xpath(".//sup/text()")[0]).strip("%"))
|
||||
except IndexError:
|
||||
except (IndexError, ValueError):
|
||||
p = 0
|
||||
star = self.parse_star(
|
||||
ship.xpath("./tbody/tr/td[1]/div/div/div/a/img/@alt")[0]
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import random
|
||||
from lxml import etree
|
||||
from typing import List, Tuple
|
||||
from PIL import ImageDraw
|
||||
from urllib.parse import unquote
|
||||
from nonebot.log import logger
|
||||
|
||||
from .base_handle import BaseHandle, BaseData
|
||||
from ..config import draw_config
|
||||
from ..util import remove_prohibited_str, cn2py, load_font
|
||||
from utils.image_utils import BuildImage
|
||||
|
||||
|
||||
class BaChar(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
class BaHandle(BaseHandle[BaChar]):
|
||||
def __init__(self):
|
||||
super().__init__("ba", "碧蓝档案")
|
||||
self.max_star = 3
|
||||
self.config = draw_config.ba
|
||||
self.ALL_CHAR: List[BaChar] = []
|
||||
|
||||
def get_card(self, mode: int = 1) -> BaChar:
|
||||
if mode == 2:
|
||||
star = self.get_star(
|
||||
[3, 2], [self.config.BA_THREE_P, self.config.BA_G_TWO_P]
|
||||
)
|
||||
else:
|
||||
star = self.get_star(
|
||||
[3, 2, 1],
|
||||
[self.config.BA_THREE_P, self.config.BA_TWO_P, self.config.BA_ONE_P],
|
||||
)
|
||||
chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited]
|
||||
return random.choice(chars)
|
||||
|
||||
def get_cards(self, count: int, **kwargs) -> List[Tuple[BaChar, int]]:
|
||||
card_list = []
|
||||
card_count = 0 # 保底计算
|
||||
for i in range(count):
|
||||
card_count += 1
|
||||
# 十连保底
|
||||
if card_count == 10:
|
||||
card = self.get_card(2)
|
||||
card_count = 0
|
||||
else:
|
||||
card = self.get_card(1)
|
||||
if card.star > self.max_star - 2:
|
||||
card_count = 0
|
||||
card_list.append((card, i + 1))
|
||||
return card_list
|
||||
|
||||
def generate_card_img(self, card: BaChar) -> BuildImage:
|
||||
sep_w = 5
|
||||
sep_h = 5
|
||||
star_h = 15
|
||||
img_w = 90
|
||||
img_h = 100
|
||||
font_h = 20
|
||||
bar_h = 20
|
||||
bar_w = 90
|
||||
bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5")
|
||||
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
|
||||
img = BuildImage(img_w, img_h, background=img_path)
|
||||
bar = BuildImage(bar_w, bar_h, color="#6495ED")
|
||||
bg.paste(img, (sep_w, sep_h), alpha=True)
|
||||
bg.paste(bar, (sep_w, img_h - bar_h + sep_h), alpha=True)
|
||||
if (card.star == 1):
|
||||
star_path = str(self.img_path / "star-1.png")
|
||||
star_w = 15
|
||||
elif (card.star == 2):
|
||||
star_path = str(self.img_path / "star-2.png")
|
||||
star_w = 30
|
||||
else:
|
||||
star_path = str(self.img_path / "star-3.png")
|
||||
star_w = 45
|
||||
star = BuildImage(star_w, star_h, background=star_path)
|
||||
bg.paste(star, (img_w // 2 - 15 * (card.star - 1) // 2, img_h - star_h), alpha=True)
|
||||
text = card.name[:5] + "..." if len(card.name) > 6 else card.name
|
||||
font = load_font(fontsize=14)
|
||||
text_w, text_h = font.getsize(text)
|
||||
draw = ImageDraw.Draw(bg.markImg)
|
||||
draw.text(
|
||||
(sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2),
|
||||
text,
|
||||
font=font,
|
||||
fill="gray",
|
||||
)
|
||||
return bg
|
||||
|
||||
def _init_data(self):
|
||||
self.ALL_CHAR = [
|
||||
BaChar(
|
||||
name=value["名称"],
|
||||
star=int(value["星级"]),
|
||||
limited=True if "(" in key else False,
|
||||
)
|
||||
for key, value in self.load_data().items()
|
||||
]
|
||||
|
||||
def title2star(self, title: int):
|
||||
if title == 'Star-3.png':
|
||||
return 3
|
||||
elif title == 'Star-2.png':
|
||||
return 2
|
||||
else:
|
||||
return 1
|
||||
|
||||
async def _update_info(self):
|
||||
info = {}
|
||||
url = "https://wiki.biligame.com/bluearchive/学生筛选"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
return
|
||||
else:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath("//div[@class='filters']/table[2]/tbody/tr")
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./td[2]/a/div/text()")[0]
|
||||
avatar = char.xpath("./td[1]/div/div/a/img/@data-src")[0]
|
||||
star_pic = char.xpath("./td[4]/img/@alt")[0]
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar)),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"星级": self.title2star(star_pic),
|
||||
}
|
||||
info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(info)
|
||||
logger.info(f"{self.game_name_cn} 更新成功")
|
||||
# 下载头像
|
||||
for value in info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
# 下载星星
|
||||
await self.download_img(
|
||||
"https://patchwiki.biligame.com/images/bluearchive/thumb/e/e0/82nj2x9sxko473g7782r14fztd4zyky.png/15px-Star-1.png",
|
||||
"star-1",
|
||||
)
|
||||
await self.download_img(
|
||||
"https://patchwiki.biligame.com/images/bluearchive/thumb/0/0b/msaff2g0zk6nlyl1rrn7n1ri4yobcqc.png/30px-Star-2.png",
|
||||
"star-2",
|
||||
)
|
||||
await self.download_img(
|
||||
"https://patchwiki.biligame.com/images/bluearchive/thumb/8/8a/577yv79x1rwxk8efdccpblo0lozl158.png/46px-Star-3.png",
|
||||
"star-3"
|
||||
)
|
||||
@@ -3,7 +3,6 @@ import random
|
||||
import dateparser
|
||||
from lxml import etree
|
||||
from PIL import ImageDraw
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
from urllib.parse import unquote
|
||||
from typing import List, Optional, Tuple
|
||||
@@ -32,8 +31,9 @@ class Operator(BaseData):
|
||||
|
||||
class PrtsHandle(BaseHandle[Operator]):
|
||||
def __init__(self):
|
||||
super().__init__("prts", "明日方舟", "#eff2f5")
|
||||
super().__init__(game_name="prts", game_name_cn="明日方舟")
|
||||
self.max_star = 6
|
||||
self.game_card_color = "#eff2f5"
|
||||
self.config = draw_config.prts
|
||||
|
||||
self.ALL_OPERATOR: List[Operator] = []
|
||||
@@ -41,8 +41,8 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
|
||||
def get_card(self, add: float) -> Operator:
|
||||
star = self.get_star(
|
||||
[6, 5, 4, 3],
|
||||
[
|
||||
star_list=[6, 5, 4, 3],
|
||||
probability_list=[
|
||||
self.config.PRTS_SIX_P + add,
|
||||
self.config.PRTS_FIVE_P,
|
||||
self.config.PRTS_FOUR_P,
|
||||
@@ -53,7 +53,7 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
all_operators = [
|
||||
x
|
||||
for x in self.ALL_OPERATOR
|
||||
if x.star == star and not any([x.limited, x.event_only, x.recruit_only])
|
||||
if x.star == star and not any([x.limited, x.recruit_only, x.event_only])
|
||||
]
|
||||
acquire_operator = None
|
||||
|
||||
@@ -74,7 +74,7 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
acquire_operator = random.choice(all_operators)
|
||||
return acquire_operator
|
||||
|
||||
def get_cards(self, count: int) -> List[Tuple[Operator, int]]:
|
||||
def get_cards(self, count: int, **kwargs) -> List[Tuple[Operator, int]]:
|
||||
card_list = [] # 获取所有角色
|
||||
add = 0.0
|
||||
count_idx = 0
|
||||
@@ -109,7 +109,8 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
|
||||
def _draw(self, count: int, **kwargs) -> Message:
|
||||
index2card = self.get_cards(count)
|
||||
cards = [card[0] for card in self.get_cards(count)]
|
||||
"""这里cards修复了抽卡图文不符的bug"""
|
||||
cards = [card[0] for card in index2card]
|
||||
up_list = [x.name for x in self.UP_EVENT.up_char] if self.UP_EVENT else []
|
||||
result = self.format_result(index2card, up_list=up_list)
|
||||
pool_info = self.format_pool_info()
|
||||
@@ -162,7 +163,11 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
def load_up_char(self):
|
||||
try:
|
||||
data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json")
|
||||
self.UP_EVENT = UpEvent.parse_obj(data.get("char", {}))
|
||||
"""这里的 waring 有点模糊,更新游戏信息时没有up池的情况下也会报错,所以细分了一下"""
|
||||
if not data:
|
||||
logger.warning(f"当前无UP池或 {self.game_name}_up_char.json 文件不存在")
|
||||
else:
|
||||
self.UP_EVENT = UpEvent.parse_obj(data.get("char", {}))
|
||||
except ValidationError:
|
||||
logger.warning(f"{self.game_name}_up_char 解析出错")
|
||||
|
||||
@@ -185,14 +190,15 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
avatar = char.xpath("./td[1]/div/div/div/a/img/@srcset")[0]
|
||||
name = char.xpath("./td[2]/a/text()")[0]
|
||||
star = char.xpath("./td[5]/text()")[0]
|
||||
sources = str(char.xpath("./td[8]/text()")[0]).split("\n")
|
||||
"""这里sources修好了干员获取标签有问题的bug,如三星只能抽到卡缇就是这个原因"""
|
||||
sources = [_.strip('\n') for _ in char.xpath("./td[8]/text()")]
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar).split(" ")[-2]),
|
||||
"名称": remove_prohibited_str(str(name).strip()),
|
||||
"星级": int(str(star).strip()),
|
||||
"获取途径": [s for s in sources if s],
|
||||
"获取途径": sources,
|
||||
}
|
||||
info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(info)
|
||||
@@ -208,6 +214,7 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
await self.update_up_char()
|
||||
|
||||
async def update_up_char(self):
|
||||
"""重载卡池"""
|
||||
announcement_url = "https://ak.hypergryph.com/news.html"
|
||||
result = await self.get_url(announcement_url)
|
||||
if not result:
|
||||
@@ -221,41 +228,53 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
end_time = None
|
||||
up_chars = []
|
||||
pool_img = ""
|
||||
title = ""
|
||||
for activity_url in activity_urls:
|
||||
for activity_url in activity_urls[:10]: # 减少响应时间, 10个就够了
|
||||
activity_url = f"https://ak.hypergryph.com{activity_url}"
|
||||
result = await self.get_url(activity_url)
|
||||
if not result:
|
||||
logger.warning(f"{self.game_name_cn}获取公告 {activity_url} 出错")
|
||||
continue
|
||||
soup = BeautifulSoup(result, "lxml")
|
||||
contents = soup.find_all("p")
|
||||
|
||||
"""因为鹰角的前端太自由了,这里重写了匹配规则以尽可能避免因为前端乱七八糟而导致的重载失败"""
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
contents = dom.xpath(
|
||||
"//div[@class='article-content']/p/text() | //div[@class='article-content']/p/span/text() | //div[@class='article-content']/div[@class='media-wrap image-wrap']/img/@src"
|
||||
)
|
||||
title = ""
|
||||
time = ""
|
||||
chars: List[str] = []
|
||||
for index, content in enumerate(contents):
|
||||
if re.search("(.*)(寻访|复刻).*?开启", content.text):
|
||||
title = content.text
|
||||
if "【" in title and "】" in title:
|
||||
title = re.split(r"[【】]", title)[1]
|
||||
lines = [str(contents[index + i + 1].text) for i in range(5)]
|
||||
time = ""
|
||||
chars: List[str] = []
|
||||
for line in lines:
|
||||
if re.search("(.*)(寻访|复刻).*?开启", content):
|
||||
title = re.split(r"[【】]", content)
|
||||
title = "".join(title[1:-1]) if "-" in title else title[1]
|
||||
lines = [contents[index-2+_] for _ in range(8)] # 从 -2 开始是因为xpath获取的时间有的会在寻访开启这一句之前
|
||||
lines.append("") # 防止IndexError,加个空字符串
|
||||
for idx, line in enumerate(lines):
|
||||
match = re.search(
|
||||
r"(\d{1,2}月\d{1,2}日.*?-.*?\d{1,2}月\d{1,2}日.*?$)", line
|
||||
)
|
||||
if match:
|
||||
time = match.group(1)
|
||||
if "★" in line:
|
||||
chars.append(line)
|
||||
"""因为 <p> 的诡异排版,所以有了下面的一段"""
|
||||
if ("★★" in line and "%" in line) or ("★★" in line and "%" in lines[idx + 1]):
|
||||
chars.append(line) if ("★★" in line and "%" in line) else chars.append(line + lines[idx + 1])
|
||||
if not time:
|
||||
continue
|
||||
start, end = time.replace("月", "/").replace("日", "").split("-")[:2]
|
||||
start, end = time.replace("月", "/").replace("日", " ").split("-")[:2] # 日替换为空格是因为有日后面不接空格的情况,导致 split 出问题
|
||||
start_time = dateparser.parse(start)
|
||||
end_time = dateparser.parse(end)
|
||||
pool_img = content.find_previous("img")["src"]
|
||||
pool_img = contents[index-2]
|
||||
r"""两类格式:用/分割,用\分割;★+(概率)+名字,★+名字+(概率)"""
|
||||
for char in chars:
|
||||
star = char.split("(")[0].count("★")
|
||||
name = re.split(r"[:(]", char)[1]
|
||||
names = name.split("/") if "/" in name else [name]
|
||||
name = re.split(r"[:(]", char)[1] if "★(" not in char else re.split("):", char)[1] # 有的括号在前面有的在后面
|
||||
if "\\" in name:
|
||||
names = name.split("\\")
|
||||
elif "/" in name:
|
||||
names = name.split("/")
|
||||
else:
|
||||
names = [name] # 既有用/分割的,又有用\分割的
|
||||
|
||||
names = [name.replace("[限定]", "").strip() for name in names]
|
||||
if "权值" in char:
|
||||
match = re.search(r"(在.*?以.*?(\d+).*?倍权值.*?)", char)
|
||||
@@ -269,7 +288,7 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
up_chars.append(
|
||||
UpChar(name=name, star=star, limited=False, zoom=zoom)
|
||||
)
|
||||
break
|
||||
break # 这里break会导致个问题:如果一个公告里有两个池子,会漏掉下面的池子,比如 5.19 的定向寻访。但目前我也没啥好想法解决
|
||||
if title and start_time and end_time:
|
||||
if start_time <= datetime.now() <= end_time:
|
||||
self.UP_EVENT = UpEvent(
|
||||
|
||||
+7
-6
@@ -106,20 +106,21 @@ async def _(event: GroupMessageEvent):
|
||||
"fudu", "FUDU_PROBABILITY"
|
||||
) and not _fudu_list.is_repeater(event.group_id):
|
||||
if random.random() < 0.2:
|
||||
await fudu.finish("[[_task|fudu]]打断施法!")
|
||||
if msg.endswith("打断施法!"):
|
||||
await fudu.finish("[[_task|fudu]]打断" + msg)
|
||||
else:
|
||||
await fudu.finish("[[_task|fudu]]打断施法!")
|
||||
_fudu_list.set_repeater(event.group_id)
|
||||
if img and msg:
|
||||
rst = msg + image(f"compare_{event.group_id}_img.jpg", "temp")
|
||||
rst = msg + image(TEMP_PATH / f"compare_{event.group_id}_img.jpg")
|
||||
elif img:
|
||||
rst = image(f"compare_{event.group_id}_img.jpg", "temp")
|
||||
rst = image(TEMP_PATH / f"compare_{event.group_id}_img.jpg")
|
||||
elif msg:
|
||||
rst = msg
|
||||
else:
|
||||
rst = ""
|
||||
if rst:
|
||||
if rst.endswith("打断施法!"):
|
||||
rst = "打断" + rst
|
||||
await fudu.send("[[_task|fudu]]" + rst)
|
||||
await fudu.finish("[[_task|fudu]]" + rst)
|
||||
|
||||
|
||||
async def get_fudu_img_hash(url, group_id):
|
||||
|
||||
@@ -330,18 +330,19 @@ def get_country_data_image(world_data_dict: Dict) -> BuildImage:
|
||||
f"{world_data_dict['层岩巨渊']['exploration_percentage'] / 10}%",
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
content_bk.text((300, 85), "地下矿区探索", fill=(239, 211, 114))
|
||||
content_bk.text(
|
||||
(570, 85),
|
||||
f"{world_data_dict['层岩巨渊·地下矿区']['exploration_percentage'] / 10}%",
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
content_bk.text((300, 150), "流明石触媒", fill=(239, 211, 114))
|
||||
content_bk.text(
|
||||
(570, 150),
|
||||
f"LV.{world_data_dict['层岩巨渊·地下矿区']['offerings'][0]['level']}",
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
if world_data_dict.get('层岩巨渊·地下矿区'):
|
||||
content_bk.text((300, 85), "地下矿区探索", fill=(239, 211, 114))
|
||||
content_bk.text(
|
||||
(570, 85),
|
||||
f"{world_data_dict['层岩巨渊·地下矿区']['exploration_percentage'] / 10}%",
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
content_bk.text((300, 150), "流明石触媒", fill=(239, 211, 114))
|
||||
content_bk.text(
|
||||
(570, 150),
|
||||
f"LV.{world_data_dict['层岩巨渊·地下矿区']['offerings'][0]['level']}",
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
elif country in ["龙脊雪山"]:
|
||||
content_bk.text((300, 40), "探索", fill=(239, 211, 114))
|
||||
content_bk.text(
|
||||
|
||||
@@ -64,8 +64,8 @@ async def _generate_open_redbag_pic(user_id: int, send_user_nickname: str, amoun
|
||||
head = BuildImage(1000, 980, font_size=30, background=f'{IMAGE_PATH}/prts/redbag_12.png')
|
||||
size = BuildImage(0, 0, font_size=50).getsize(send_user_nickname)
|
||||
# QQ头像
|
||||
ava_bk = BuildImage(100 + size[0], 66, color='white', font_size=50)
|
||||
ava = BuildImage(66, 66, background=BytesIO(await get_user_avatar(user_id)))
|
||||
ava_bk = BuildImage(100 + size[0], 66, is_alpha=True, font_size=50)
|
||||
ava = BuildImage(66, 66, is_alpha=True, background=BytesIO(await get_user_avatar(user_id)))
|
||||
ava_bk.paste(ava)
|
||||
ava_bk.text((100, 7), send_user_nickname)
|
||||
# ava_bk.show()
|
||||
@@ -73,7 +73,7 @@ async def _generate_open_redbag_pic(user_id: int, send_user_nickname: str, amoun
|
||||
head.paste(ava_bk, (int((1000 - ava_bk_w) / 2), 300))
|
||||
# 金额
|
||||
size = BuildImage(0, 0, font_size=150).getsize(amount)
|
||||
price = BuildImage(size[0], size[1], font_size=150)
|
||||
price = BuildImage(size[0], size[1], is_alpha=True, font_size=150)
|
||||
price.text((0, 0), amount, fill=(209, 171, 108))
|
||||
# 金币中文
|
||||
head.paste(price, (int((1000 - size[0]) / 2) - 50, 460))
|
||||
|
||||
@@ -211,16 +211,16 @@ update_price = on_command("更新开箱价格", priority=1, permission=SUPERUSER
|
||||
|
||||
|
||||
@update_price.handle()
|
||||
async def _( event: MessageEvent):
|
||||
await update_price.send(await util_get_buff_price(str(event.get_message())))
|
||||
async def _(event: MessageEvent, arg: Message = CommandArg()):
|
||||
await update_price.send(await util_get_buff_price(arg.extract_plain_text().strip() or "狂牙大行动"))
|
||||
|
||||
|
||||
update_img = on_command("更新开箱图片", priority=1, permission=SUPERUSER, block=True)
|
||||
|
||||
|
||||
@update_img.handle()
|
||||
async def _(event: MessageEvent):
|
||||
await update_img.send(await util_get_buff_img(str(event.get_message())))
|
||||
async def _(event: MessageEvent, arg: Message = CommandArg()):
|
||||
await update_img.send(await util_get_buff_img(arg.extract_plain_text().strip() or "狂牙大行动"))
|
||||
|
||||
|
||||
# 重置开箱
|
||||
|
||||
@@ -139,15 +139,14 @@ class OmegaPixivIllusts(db.Model):
|
||||
data = await cls.select('pid').gino.all()
|
||||
return [x[0] for x in data]
|
||||
|
||||
@classmethod
|
||||
async def test(cls, nsfw_tag: int = 1):
|
||||
if nsfw_tag is not None:
|
||||
query = cls.query.where(cls.nsfw_tag == nsfw_tag)
|
||||
else:
|
||||
query = cls.query
|
||||
query = query.where((cls.width - cls.height) < 50)
|
||||
for x in await query.gino.all():
|
||||
print(x.pid)
|
||||
# async def test(cls, nsfw_tag: int = 1):
|
||||
# if nsfw_tag is not None:
|
||||
# query = cls.query.where(cls.nsfw_tag == nsfw_tag)
|
||||
# else:
|
||||
# query = cls.query
|
||||
# query = query.where((cls.width - cls.height) < 50)
|
||||
# for x in await query.gino.all():
|
||||
# print(x.pid)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ usage:
|
||||
查看 pix 好康图库
|
||||
指令:
|
||||
pix ?*[tags]: 通过 tag 获取相似图片,不含tag时随机抽取
|
||||
pid [uid]: 通过uid获取图片
|
||||
pix pid[pid]: 查看图库中指定pid图片
|
||||
""".strip()
|
||||
__plugin_superuser_usage__ = """
|
||||
@@ -49,7 +50,9 @@ __plugin_configs__ = {
|
||||
"value": None,
|
||||
"help": "单次发送的图片数量达到指定值时转发为合并消息",
|
||||
"default_value": None,
|
||||
}
|
||||
},
|
||||
"ALLOW_GROUP_SETU": {"value": False, "help": "允许非超级用户使用-s参数", "default_value": False},
|
||||
"ALLOW_GROUP_R18": {"value": False, "help": "允许非超级用户使用-r参数", "default_value": False},
|
||||
}
|
||||
|
||||
|
||||
@@ -80,10 +83,13 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
nsfw_tag = 2
|
||||
else:
|
||||
nsfw_tag = 0
|
||||
if nsfw_tag != 0 and str(event.user_id) not in bot.config.superusers:
|
||||
await pix.finish("你不能看这些噢,这些都是是留给管理员看的...")
|
||||
if n := len(x) == 1 and is_number(x[0]):
|
||||
num = int(x[-1])
|
||||
if str(event.user_id) not in bot.config.superusers:
|
||||
if (nsfw_tag == 1 and not Config.get_config("pix", "ALLOW_GROUP_SETU")) or (
|
||||
nsfw_tag == 2 and not Config.get_config("pix", "ALLOW_GROUP_R18")
|
||||
):
|
||||
await pix.finish("你不能看这些噢,这些都是是留给管理员看的...")
|
||||
if n := len(x) == 1 and is_number(x[0]) and int(x[0]) < 100:
|
||||
num = int(x[0])
|
||||
keyword = ""
|
||||
elif n > 1:
|
||||
if is_number(x[-1]):
|
||||
|
||||
@@ -44,6 +44,8 @@ usage:
|
||||
搜图 樱岛麻衣
|
||||
搜图 樱岛麻衣 5
|
||||
搜图 樱岛麻衣 5 r18
|
||||
搜图 樱岛麻衣#1000users 5
|
||||
【多个关键词用#分割】
|
||||
【默认为 热度排序】
|
||||
【注意空格!!】【在线搜索会较慢】【数量可能不符?可能该页数量不够,也可能被R-18屏蔽】
|
||||
""".strip()
|
||||
@@ -69,6 +71,11 @@ __plugin_configs__ = {
|
||||
"value": 20,
|
||||
"help": "作品最大页数限制,超过的作品会被略过",
|
||||
"default_value": 20
|
||||
},
|
||||
"ALLOW_GROUP_R18": {
|
||||
"value": False,
|
||||
"help": "允许群聊中使用 r18 参数",
|
||||
"default_value": False
|
||||
}
|
||||
}
|
||||
Config.add_plugin_config(
|
||||
@@ -152,7 +159,7 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
msg = arg.extract_plain_text().strip()
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
if "r18" in msg.lower():
|
||||
if "r18" in msg.lower() and not Config.get_config("pixiv_rank_search", "ALLOW_GROUP_R18"):
|
||||
await pixiv_keyword.finish("(脸红#) 你不会害羞的 八嘎!", at_sender=True)
|
||||
r18 = 0 if "r18" in msg else 1
|
||||
msg = msg.replace("r18", "").strip().split()
|
||||
@@ -161,8 +168,8 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
info_list = None
|
||||
num = 10
|
||||
page = 1
|
||||
if (n := len(msg)) == 1:
|
||||
keyword = msg[0]
|
||||
if (n := len(msg)) > 0:
|
||||
keyword = msg[0].replace("#"," ")
|
||||
if n > 1:
|
||||
if not is_number(msg[1]):
|
||||
await pixiv_keyword.finish("图片数量必须是数字!", at_sender=True)
|
||||
|
||||
@@ -62,12 +62,12 @@ async def _poke_event(event: PokeNotifyEvent):
|
||||
rst = "气死我了!"
|
||||
await poke_.finish(rst + random.choice(poke__reply), at_sender=True)
|
||||
rand = random.random()
|
||||
if rand <= 0.3:
|
||||
path = random.choice(["luoli", "meitu"])
|
||||
path = random.choice(["luoli", "meitu"])
|
||||
if rand <= 0.3 and len(os.listdir(IMAGE_PATH / "image_management" / path)) > 0:
|
||||
index = random.randint(0, len(os.listdir(IMAGE_PATH / "image_management" / path)))
|
||||
result = f"id:{index}" + image(f"{index}.jpg", "image_management/" + path)
|
||||
await poke_.send(result)
|
||||
logger.info(f"USER {event.user_id} 戳了戳我 回复: {result} \n {result}")
|
||||
logger.info(f"USER {event.user_id} 戳了戳我 回复: {result} {result}")
|
||||
elif 0.3 < rand < 0.6:
|
||||
voice = random.choice(os.listdir(RECORD_PATH / "dinggong"))
|
||||
result = record(voice, "dinggong")
|
||||
|
||||
@@ -97,6 +97,7 @@ __plugin_configs__ = {
|
||||
},
|
||||
"TIMEOUT": {"value": 10, "help": "色图下载超时限制(秒)", "default_value": 10},
|
||||
"SHOW_INFO": {"value": True, "help": "是否显示色图的基本信息,如PID等", "default_value": True},
|
||||
"ALLOW_GROUP_R18": {"value": False, "help": "在群聊中启用R18权限", "default_value": False},
|
||||
}
|
||||
Config.add_plugin_config("pixiv", "PIXIV_NGINX_URL", "i.pixiv.re", help_="Pixiv反向代理")
|
||||
|
||||
@@ -148,10 +149,16 @@ async def _(
|
||||
if cmd[0] == "色图r" and isinstance(event, PrivateMessageEvent):
|
||||
r18 = 1
|
||||
num = 10
|
||||
elif cmd[0] == "色图r" and isinstance(event, GroupMessageEvent):
|
||||
await setu.finish(
|
||||
random.choice(["这种不好意思的东西怎么可能给这么多人看啦", "羞羞脸!给我滚出克私聊!", "变态变态变态变态大变态!"])
|
||||
)
|
||||
elif (
|
||||
cmd[0] == "色图r"
|
||||
and isinstance(event, GroupMessageEvent)
|
||||
):
|
||||
if not Config.get_config("send_setu", "ALLOW_GROUP_R18"):
|
||||
await setu.finish(
|
||||
random.choice(["这种不好意思的东西怎么可能给这么多人看啦", "羞羞脸!给我滚出克私聊!", "变态变态变态变态大变态!"])
|
||||
)
|
||||
else:
|
||||
r18 = 1
|
||||
# 有 数字 的话先尝试本地色图id
|
||||
if msg and is_number(msg):
|
||||
setu_list, code = await get_setu_list(int(msg), r18=r18)
|
||||
|
||||
@@ -24,6 +24,8 @@ from io import BytesIO
|
||||
import asyncio
|
||||
import random
|
||||
import nonebot
|
||||
import time
|
||||
import locale
|
||||
import os
|
||||
|
||||
|
||||
@@ -260,15 +262,12 @@ def _generate_card(
|
||||
today_data.text((0, 25), f"金币 + {gold}")
|
||||
_type = "sign"
|
||||
current_date = datetime.now()
|
||||
week = current_date.isoweekday()
|
||||
current_datetime_str = current_date.strftime("%Y-%m-%d %a %H:%M:%S")
|
||||
data = current_date.date()
|
||||
hour = current_date.hour
|
||||
minute = current_date.minute
|
||||
second = current_date.second
|
||||
data_img = BuildImage(
|
||||
0,
|
||||
0,
|
||||
plain_text=f"时间:{data} {weekdays[week]} {hour}:{minute}:{second}",
|
||||
plain_text=f"时间:{current_datetime_str}",
|
||||
color=(255, 255, 255, 0),
|
||||
font_size=20,
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ async def translate_msg(language_type, msg):
|
||||
}
|
||||
data = (await AsyncHttpx.post(url, data=data)).json()
|
||||
if data["errorCode"] == 0:
|
||||
return f'原文:{msg}\n翻译{data["translateResult"][0][0]["tgt"]}'
|
||||
return f'原文:{msg}\n翻译:{data["translateResult"][0][0]["tgt"]}'
|
||||
return "翻译惜败.."
|
||||
|
||||
|
||||
|
||||
Executable → Regular
+78
-70
@@ -1,70 +1,78 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent, Message
|
||||
from nonebot.params import CommandArg
|
||||
from services.log import logger
|
||||
from ._data_source import get_data, gen_wbtop_pic
|
||||
from utils.utils import is_number
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from utils.http_utils import AsyncPlaywright
|
||||
import asyncio
|
||||
|
||||
__zx_plugin_name__ = "微博热搜"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
在QQ上吃个瓜
|
||||
指令:
|
||||
微博热搜:发送实时热搜
|
||||
微博热搜 [id]:截图该热搜页面
|
||||
示例:微博热搜 5
|
||||
""".strip()
|
||||
__plugin_des__ = "刚买完瓜,在吃瓜现场"
|
||||
__plugin_cmd__ = ["微博热搜", "微博热搜 [id]"]
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_settings__ = {
|
||||
"level": 5,
|
||||
"default_status": True,
|
||||
"limit_superuser": False,
|
||||
"cmd": ["微博热搜"],
|
||||
}
|
||||
|
||||
wbtop = on_command("wbtop", aliases={"微博热搜"}, priority=5, block=True)
|
||||
|
||||
|
||||
wbtop_url = "https://v2.alapi.cn/api/new/wbtop"
|
||||
|
||||
wbtop_data = []
|
||||
|
||||
|
||||
@wbtop.handle()
|
||||
async def _(event: MessageEvent, arg: Message = CommandArg()):
|
||||
global wbtop_data
|
||||
msg = arg.extract_plain_text().strip()
|
||||
if not wbtop_data or not msg:
|
||||
data, code = await get_data(wbtop_url)
|
||||
if code != 200:
|
||||
await wbtop.finish(data, at_sender=True)
|
||||
wbtop_data = data["data"]
|
||||
if not msg:
|
||||
img = await asyncio.get_event_loop().run_in_executor(
|
||||
None, gen_wbtop_pic, wbtop_data
|
||||
)
|
||||
await wbtop.send(img)
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
|
||||
f" 查询微博热搜"
|
||||
)
|
||||
if is_number(msg) and 0 < int(msg) <= 50:
|
||||
url = wbtop_data[int(msg) - 1]["url"]
|
||||
await wbtop.send("开始截取数据...")
|
||||
img = await AsyncPlaywright.screenshot(
|
||||
url,
|
||||
f"{IMAGE_PATH}/temp/wbtop_{event.user_id}.png",
|
||||
"#pl_feed_main",
|
||||
wait_time=12
|
||||
)
|
||||
if img:
|
||||
await wbtop.send(img)
|
||||
else:
|
||||
await wbtop.send("发生了一些错误.....")
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent, Message
|
||||
from nonebot.params import CommandArg
|
||||
from services.log import logger
|
||||
from .data_source import gen_wbtop_pic,get_wbtop
|
||||
from utils.utils import is_number
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from utils.http_utils import AsyncPlaywright
|
||||
import asyncio
|
||||
import datetime
|
||||
__zx_plugin_name__ = "微博热搜"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
在QQ上吃个瓜
|
||||
指令:
|
||||
微博热搜:发送实时热搜
|
||||
微博热搜 [id]:截图该热搜页面
|
||||
示例:微博热搜 5
|
||||
""".strip()
|
||||
__plugin_des__ = "刚买完瓜,在吃瓜现场"
|
||||
__plugin_cmd__ = ["微博热搜", "微博热搜 [id]"]
|
||||
__plugin_version__ = 0.2
|
||||
__plugin_author__ = "HibiKier & yajiwa"
|
||||
__plugin_settings__ = {
|
||||
"level": 5,
|
||||
"default_status": True,
|
||||
"limit_superuser": False,
|
||||
"cmd": ["微博热搜"],
|
||||
}
|
||||
|
||||
wbtop = on_command("wbtop", aliases={"微博热搜"}, priority=5, block=True)
|
||||
|
||||
|
||||
wbtop_url = "https://weibo.com/ajax/side/hotSearch"
|
||||
|
||||
wbtop_data = []
|
||||
|
||||
|
||||
@wbtop.handle()
|
||||
async def _(event: MessageEvent, arg: Message = CommandArg()):
|
||||
global wbtop_data
|
||||
msg = arg.extract_plain_text().strip()
|
||||
if not wbtop_data or not msg:
|
||||
if wbtop_data:
|
||||
now_time = datetime.datetime.now()
|
||||
if now_time > wbtop_data["time"] + datetime.timedelta(minutes=5):
|
||||
data, code = await get_wbtop(wbtop_url)
|
||||
if code != 200:
|
||||
await wbtop.finish(data, at_sender=True)
|
||||
wbtop_data = data
|
||||
else:
|
||||
data, code = await get_wbtop(wbtop_url)
|
||||
if code != 200:
|
||||
await wbtop.finish(data, at_sender=True)
|
||||
wbtop_data = data
|
||||
if not msg:
|
||||
img = await asyncio.get_event_loop().run_in_executor(
|
||||
None, gen_wbtop_pic, wbtop_data["data"]
|
||||
)
|
||||
await wbtop.send(img)
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
|
||||
f" 查询微博热搜"
|
||||
)
|
||||
if is_number(msg) and 0 < int(msg) <= 50:
|
||||
url = wbtop_data["data"][int(msg) - 1]["url"]
|
||||
await wbtop.send("开始截取数据...")
|
||||
img = await AsyncPlaywright.screenshot(
|
||||
url,
|
||||
f"{IMAGE_PATH}/temp/wbtop_{event.user_id}.png",
|
||||
"#pl_feed_main",
|
||||
wait_time=12
|
||||
)
|
||||
if img:
|
||||
await wbtop.send(img)
|
||||
else:
|
||||
await wbtop.send("发生了一些错误.....")
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from utils.image_utils import BuildImage
|
||||
from utils.message_builder import image
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from typing import Tuple, Union
|
||||
from utils.http_utils import AsyncHttpx
|
||||
import datetime
|
||||
|
||||
|
||||
async def get_wbtop(url: str) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
:param url: 请求链接
|
||||
"""
|
||||
n = 0
|
||||
while True:
|
||||
try:
|
||||
data = []
|
||||
get_response = (await AsyncHttpx.get(url, timeout=20))
|
||||
if get_response.status_code == 200:
|
||||
data_json = get_response.json()['data']['realtime']
|
||||
for data_item in data_json:
|
||||
# 如果是广告,则不添加
|
||||
if 'is_ad' in data_item:
|
||||
continue
|
||||
dic = {
|
||||
'hot_word': data_item['note'],
|
||||
'hot_word_num': str(data_item['num']),
|
||||
'url': 'https://s.weibo.com/weibo?q=%23' + data_item['word'] + '%23',
|
||||
}
|
||||
data.append(dic)
|
||||
if not data:
|
||||
return "没有搜索到...", 997
|
||||
return {'data': data, 'time': datetime.datetime.now()}, 200
|
||||
else:
|
||||
if n > 2:
|
||||
return f'获取失败,请十分钟后再试', 999
|
||||
else:
|
||||
n += 1
|
||||
continue
|
||||
except TimeoutError:
|
||||
return "超时了....", 998
|
||||
|
||||
|
||||
def gen_wbtop_pic(data: dict) -> MessageSegment:
|
||||
"""
|
||||
生成微博热搜图片
|
||||
:param data: 微博热搜数据
|
||||
"""
|
||||
bk = BuildImage(700, 32 * 50 + 280, 700, 32, color="#797979")
|
||||
wbtop_bk = BuildImage(700, 280, background=f"{IMAGE_PATH}/other/webtop.png")
|
||||
bk.paste(wbtop_bk)
|
||||
text_bk = BuildImage(700, 32 * 50, 700, 32, color="#797979")
|
||||
for i, data in enumerate(data):
|
||||
title = f"{i + 1}. {data['hot_word']}"
|
||||
hot = str(data["hot_word_num"])
|
||||
img = BuildImage(700, 30, font_size=20)
|
||||
w, h = img.getsize(title)
|
||||
img.text((10, int((30 - h) / 2)), title)
|
||||
img.text((580, int((30 - h) / 2)), hot)
|
||||
text_bk.paste(img)
|
||||
bk.paste(text_bk, (0, 280))
|
||||
return image(b64=bk.pic2bs4())
|
||||
@@ -127,6 +127,8 @@ def _(plugin: Plugin, user: User = Depends(token_to_user)) -> Result:
|
||||
else:
|
||||
if plugin.plugin_settings:
|
||||
for key, value in plugin.plugin_settings:
|
||||
if plugin.plugin_settings.cmd:
|
||||
plugin.plugin_settings.cmd = plugin.plugin_settings.cmd.split(',')
|
||||
plugins2settings_manager.set_module_data(plugin.model, key, value)
|
||||
if plugin.plugin_manager:
|
||||
for key, value in plugin.plugin_manager:
|
||||
|
||||
@@ -45,7 +45,7 @@ class CountLimit(BaseModel):
|
||||
"""
|
||||
max_count: int
|
||||
status: bool
|
||||
limit_type: bool
|
||||
limit_type: str
|
||||
rst: Optional[str]
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from io import BytesIO
|
||||
from configs.path_config import IMAGE_PATH, FONT_PATH
|
||||
from services import logger
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from models.chat_history import ChatHistory
|
||||
from configs.config import Config
|
||||
@@ -19,10 +20,11 @@ from configs.config import Config
|
||||
|
||||
async def pre_precess(msg: List[str], config) -> str:
|
||||
return await asyncio.get_event_loop().run_in_executor(
|
||||
None, _pre_precess, msg,config)
|
||||
None, _pre_precess, msg, config
|
||||
)
|
||||
|
||||
|
||||
def _pre_precess(msg: List[str],config) -> str:
|
||||
def _pre_precess(msg: List[str], config) -> str:
|
||||
"""对消息进行预处理"""
|
||||
# 过滤掉命令
|
||||
command_start = tuple([i for i in config.command_start if i])
|
||||
@@ -46,7 +48,6 @@ def _pre_precess(msg: List[str],config) -> str:
|
||||
return msg
|
||||
|
||||
|
||||
|
||||
async def draw_word_cloud(messages, config):
|
||||
wordcloud_dir = IMAGE_PATH / "wordcloud"
|
||||
wordcloud_dir.mkdir(exist_ok=True, parents=True)
|
||||
@@ -57,19 +58,21 @@ async def draw_word_cloud(messages, config):
|
||||
url = "https://ghproxy.com/https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/resources/image/wordcloud/default.png"
|
||||
try:
|
||||
await AsyncHttpx.download_file(url, zx_logo_path)
|
||||
except:
|
||||
except Exception as e:
|
||||
logger.error(f"词云图片资源下载发生错误 {type(e)}:{e}")
|
||||
return False
|
||||
if not wordcloud_ttf.exists():
|
||||
ttf_url = 'https://ghproxy.com/https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/resources/font/STKAITI.TTF'
|
||||
ttf_url = "https://ghproxy.com/https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/resources/font/STKAITI.TTF"
|
||||
try:
|
||||
await AsyncHttpx.download_file(ttf_url, wordcloud_ttf)
|
||||
except:
|
||||
except Exception as e:
|
||||
logger.error(f"词云字体资源下载发生错误 {type(e)}:{e}")
|
||||
return False
|
||||
|
||||
topK = min(int(len(messages)), 100000)
|
||||
read_name = jieba.analyse.extract_tags(await pre_precess(messages, config), topK=topK,
|
||||
withWeight=True,
|
||||
allowPOS=())
|
||||
read_name = jieba.analyse.extract_tags(
|
||||
await pre_precess(messages, config), topK=topK, withWeight=True, allowPOS=()
|
||||
)
|
||||
name = []
|
||||
value = []
|
||||
for t in read_name:
|
||||
@@ -79,10 +82,11 @@ async def draw_word_cloud(messages, config):
|
||||
name[i] = str(name[i])
|
||||
dic = dict(zip(name, value))
|
||||
if Config.get_config("word_clouds", "WORD_CLOUDS_TEMPLATE") == 1:
|
||||
|
||||
def random_pic(base_path: str) -> str:
|
||||
path_dir = os.listdir(base_path)
|
||||
path = random.sample(path_dir, 1)[0]
|
||||
return (str(base_path) + "/" + str(path))
|
||||
return str(base_path) + "/" + str(path)
|
||||
|
||||
mask = np.array(IMG.open(random_pic(wordcloud_dir)))
|
||||
wc = WordCloud(
|
||||
@@ -113,7 +117,11 @@ async def draw_word_cloud(messages, config):
|
||||
|
||||
|
||||
async def get_list_msg(user_id, group_id, days):
|
||||
messages_list = await ChatHistory()._get_msg(uid=user_id, gid=group_id, type_="group", days=days).gino.all()
|
||||
messages_list = (
|
||||
await ChatHistory()
|
||||
._get_msg(uid=user_id, gid=group_id, type_="group", days=days)
|
||||
.gino.all()
|
||||
)
|
||||
if messages_list:
|
||||
messages = [i.text for i in messages_list]
|
||||
return messages
|
||||
|
||||
Generated
+70
-482
File diff suppressed because it is too large
Load Diff
@@ -42,6 +42,7 @@ python-multipart = "^0.0.5"
|
||||
bilireq = "^0.1.2"
|
||||
emoji = "^1.7.0"
|
||||
wordcloud = "^1.8.1"
|
||||
rich = "^12.4.3"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
from gino import Gino
|
||||
from .log import logger
|
||||
from utils.text_utils import prompt2cn
|
||||
from configs.config import bind, sql_name, user, password, address, port, database
|
||||
|
||||
|
||||
@@ -9,6 +10,8 @@ db = Gino()
|
||||
|
||||
|
||||
async def init():
|
||||
if not bind and (not user and not password and not address and not port and not database):
|
||||
raise ValueError("\n" + prompt2cn("数据库配置未填写", 28))
|
||||
i_bind = bind
|
||||
if not i_bind:
|
||||
i_bind = f"{sql_name}://{user}:{password}@{address}:{port}/{database}"
|
||||
|
||||
+2
-1
@@ -9,7 +9,8 @@
|
||||
"configs/utils",
|
||||
"poetry.lock",
|
||||
"pyproject.toml",
|
||||
"resources/font"
|
||||
"resources/font",
|
||||
"bot.py"
|
||||
],
|
||||
"add_file": [],
|
||||
"delete_file": []
|
||||
|
||||
+57
-19
@@ -14,6 +14,7 @@ from retrying import retry
|
||||
import asyncio
|
||||
import aiofiles
|
||||
import httpx
|
||||
import rich
|
||||
|
||||
|
||||
class AsyncHttpx:
|
||||
@@ -121,6 +122,7 @@ class AsyncHttpx:
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
cookies: Optional[Dict[str, str]] = None,
|
||||
timeout: Optional[int] = 30,
|
||||
stream: bool = False,
|
||||
**kwargs,
|
||||
) -> bool:
|
||||
"""
|
||||
@@ -135,31 +137,67 @@ class AsyncHttpx:
|
||||
:param headers: 请求头
|
||||
:param cookies: cookies
|
||||
:param timeout: 超时时间
|
||||
:param stream: 是否使用流式下载(流式写入+进度条,适用于下载大文件)
|
||||
"""
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
for _ in range(3):
|
||||
try:
|
||||
content = (
|
||||
await cls.get(
|
||||
url,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
use_proxy=use_proxy,
|
||||
proxy=proxy,
|
||||
timeout=timeout,
|
||||
**kwargs,
|
||||
)
|
||||
).content
|
||||
async with aiofiles.open(path, "wb") as wf:
|
||||
await wf.write(content)
|
||||
logger.info(f"下载 {url} 成功.. Path:{path.absolute()}")
|
||||
return True
|
||||
except (TimeoutError, ConnectTimeout):
|
||||
pass
|
||||
if not stream:
|
||||
try:
|
||||
content = (
|
||||
await cls.get(
|
||||
url,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
use_proxy=use_proxy,
|
||||
proxy=proxy,
|
||||
timeout=timeout,
|
||||
**kwargs,
|
||||
)
|
||||
).content
|
||||
async with aiofiles.open(path, "wb") as wf:
|
||||
await wf.write(content)
|
||||
logger.info(f"下载 {url} 成功.. Path:{path.absolute()}")
|
||||
return True
|
||||
except (TimeoutError, ConnectTimeout):
|
||||
pass
|
||||
else:
|
||||
if not headers:
|
||||
headers = get_user_agent()
|
||||
proxy = proxy if proxy else cls.proxy if use_proxy else None
|
||||
try:
|
||||
async with httpx.AsyncClient(proxies=proxy) as client:
|
||||
async with client.stream(
|
||||
"GET",
|
||||
url,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
timeout=timeout,
|
||||
**kwargs
|
||||
) as response:
|
||||
logger.info(f"开始下载 {path.name}.. Path: {path.absolute()}")
|
||||
async with aiofiles.open(path, "wb") as wf:
|
||||
total = int(response.headers["Content-Length"])
|
||||
with rich.progress.Progress(
|
||||
rich.progress.TextColumn(path.name),
|
||||
"[progress.percentage]{task.percentage:>3.0f}%",
|
||||
rich.progress.BarColumn(bar_width=None),
|
||||
rich.progress.DownloadColumn(),
|
||||
rich.progress.TransferSpeedColumn()
|
||||
) as progress:
|
||||
download_task = progress.add_task("Download", total=total)
|
||||
async for chunk in response.aiter_bytes():
|
||||
await wf.write(chunk)
|
||||
await wf.flush()
|
||||
progress.update(download_task, completed=response.num_bytes_downloaded)
|
||||
logger.info(f"下载 {url} 成功.. Path:{path.absolute()}")
|
||||
return True
|
||||
except (TimeoutError, ConnectTimeout):
|
||||
pass
|
||||
else:
|
||||
logger.error(f"下载 {url} 下载超时.. Path:{path.absolute()}")
|
||||
except Exception as e:
|
||||
|
||||
@@ -1508,7 +1508,7 @@ async def text2image(
|
||||
font=font,
|
||||
)
|
||||
await A.atext((left_padding, top_padding), text, font_color)
|
||||
# A.show()
|
||||
# A.show()
|
||||
return A
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from pathlib import Path
|
||||
|
||||
|
||||
class NonePluginCountManager(StaticData):
|
||||
|
||||
"""
|
||||
插件加载容忍管理器,当连续 max_count 次插件加载,视为删除插件,清楚数据
|
||||
"""
|
||||
@@ -15,6 +14,8 @@ class NonePluginCountManager(StaticData):
|
||||
:param max_count: 容忍最大次数
|
||||
"""
|
||||
super().__init__(file)
|
||||
if not self._data:
|
||||
self._data = {}
|
||||
self._max_count = max_count
|
||||
|
||||
def add_count(self, module: str, count: int = 1):
|
||||
@@ -42,10 +43,5 @@ class NonePluginCountManager(StaticData):
|
||||
:param module: 模块
|
||||
"""
|
||||
if module in self._data.keys():
|
||||
return self._data.keys() > self._max_count
|
||||
return self._data[module] >= self._max_count
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,18 +21,17 @@ class Plugins2blockManager(StaticData):
|
||||
with open(file, "r", encoding="utf8") as f:
|
||||
self._data = yaml.load(f)
|
||||
if "PluginBlockLimit" in self._data.keys():
|
||||
self._data = (
|
||||
self._data["PluginBlockLimit"] if self._data["PluginBlockLimit"] else {}
|
||||
)
|
||||
self._data = self._data["PluginBlockLimit"] or {}
|
||||
|
||||
def add_block_limit(
|
||||
self,
|
||||
plugin: str,
|
||||
*,
|
||||
status: Optional[bool] = True,
|
||||
check_type: Optional[str] = "all",
|
||||
limit_type: Optional[str] = "user",
|
||||
rst: Optional[str] = None,
|
||||
data_dict: Optional[dict] = None,
|
||||
**kwargs # 用于接收额外实参
|
||||
):
|
||||
"""
|
||||
添加插件调用 block 限制
|
||||
@@ -41,16 +40,10 @@ class Plugins2blockManager(StaticData):
|
||||
:param check_type: 检查类型 'private'/'group'/'all',限制私聊/群聊/全部
|
||||
:param limit_type: 限制类型 监听对象,以user_id或group_id作为键来限制,'user':用户id,'group':群id
|
||||
:param rst: 回复的话,为空则不回复
|
||||
:param data_dict: 封装好的字典数据
|
||||
"""
|
||||
if data_dict:
|
||||
status = data_dict.get("status")
|
||||
check_type = data_dict.get("check_type")
|
||||
limit_type = data_dict.get("limit_type")
|
||||
rst = data_dict.get("rst")
|
||||
status = status if status is not None else True
|
||||
check_type = check_type if check_type else "all"
|
||||
limit_type = limit_type if limit_type else "user"
|
||||
status = status or True
|
||||
check_type = check_type or "all"
|
||||
limit_type = limit_type or "user"
|
||||
if check_type not in ["all", "group", "private"]:
|
||||
raise ValueError(
|
||||
f"{plugin} 添加block限制错误,‘check_type‘ 必须为 'private'/'group'/'all'"
|
||||
|
||||
@@ -34,7 +34,7 @@ class Plugins2cdManager(StaticData):
|
||||
check_type: Optional[str] = "all",
|
||||
limit_type: Optional[str] = "user",
|
||||
rst: Optional[str] = None,
|
||||
data_dict: Optional[dict] = None,
|
||||
**kwargs # 用于接收额外实参
|
||||
):
|
||||
"""
|
||||
添加插件调用 cd 限制
|
||||
@@ -44,18 +44,11 @@ class Plugins2cdManager(StaticData):
|
||||
:param check_type: 检查类型 'private'/'group'/'all',限制私聊/群聊/全部
|
||||
:param limit_type: 限制类型 监听对象,以user_id或group_id作为键来限制,'user':用户id,'group':群id
|
||||
:param rst: 回复的话,为空则不回复
|
||||
:param data_dict: 封装好的字典数据
|
||||
"""
|
||||
if data_dict:
|
||||
cd = data_dict.get("cd")
|
||||
status = data_dict.get("status")
|
||||
check_type = data_dict.get("check_type")
|
||||
limit_type = data_dict.get("limit_type")
|
||||
rst = data_dict.get("rst")
|
||||
cd = cd if cd is not None else 5
|
||||
status = status if status is not None else True
|
||||
check_type = check_type if check_type else "all"
|
||||
limit_type = limit_type if limit_type else "user"
|
||||
cd = cd or 5
|
||||
status = status or True
|
||||
check_type = check_type or "all"
|
||||
limit_type = limit_type or "user"
|
||||
if check_type not in ["all", "group", "private"]:
|
||||
raise ValueError(
|
||||
f"{plugin} 添加cd限制错误,‘check_type‘ 必须为 'private'/'group'/'all'"
|
||||
|
||||
@@ -33,7 +33,7 @@ class Plugins2countManager(StaticData):
|
||||
status: Optional[bool] = True,
|
||||
limit_type: Optional[str] = "user",
|
||||
rst: Optional[str] = None,
|
||||
data_dict: Optional[dict] = None,
|
||||
**kwargs # 用于接收额外实参
|
||||
):
|
||||
"""
|
||||
添加插件调用 次数 限制
|
||||
@@ -42,16 +42,10 @@ class Plugins2countManager(StaticData):
|
||||
:param status: 默认开关状态
|
||||
:param limit_type: 限制类型 监听对象,以user_id或group_id作为键来限制,'user':用户id,'group':群id
|
||||
:param rst: 回复的话,为空则不回复
|
||||
:param data_dict: 封装好的字典数据
|
||||
"""
|
||||
if data_dict:
|
||||
max_count = data_dict.get("max_count")
|
||||
status = data_dict.get("status")
|
||||
limit_type = data_dict.get("limit_type")
|
||||
rst = data_dict.get("rst")
|
||||
status = status if status is not None else True
|
||||
limit_type = limit_type if limit_type else "user"
|
||||
max_count = max_count if max_count is not None else 5
|
||||
max_count = max_count or 5
|
||||
status = status or True
|
||||
limit_type = limit_type or "user"
|
||||
if limit_type not in ["user", "group"]:
|
||||
raise ValueError(f"{plugin} 添加count限制错误,‘limit_type‘ 必须为 'user'/'group'")
|
||||
self._data[plugin] = {
|
||||
|
||||
@@ -47,26 +47,17 @@ class Plugins2settingsManager(StaticData):
|
||||
:param plugin_type: 插件类型
|
||||
:param cost_gold: 需要消费的金币
|
||||
"""
|
||||
if kwargs:
|
||||
level = kwargs.get("level") if kwargs.get("level") is not None else 5
|
||||
default_status = (
|
||||
kwargs.get("default_status")
|
||||
if kwargs.get("default_status") is not None
|
||||
else True
|
||||
)
|
||||
limit_superuser = (
|
||||
kwargs.get("limit_superuser")
|
||||
if kwargs.get("limit_superuser") is not None
|
||||
else False
|
||||
)
|
||||
cmd = kwargs.get("cmd") if kwargs.get("cmd") is not None else []
|
||||
cost_gold = cost_gold if kwargs.get("cost_gold") else 0
|
||||
level = level or 5
|
||||
cmd = cmd or []
|
||||
cost_gold = cost_gold or 0
|
||||
self._data[plugin] = {
|
||||
"level": level if level is not None else 5,
|
||||
"default_status": default_status if default_status is not None else True,
|
||||
"limit_superuser": limit_superuser
|
||||
if limit_superuser is not None
|
||||
else False,
|
||||
"limit_superuser": (
|
||||
limit_superuser
|
||||
if limit_superuser is not None
|
||||
else False
|
||||
),
|
||||
"cmd": cmd,
|
||||
"plugin_type": list(
|
||||
plugin_type if plugin_type is not None else ("normal",)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from typing import Union, List, Optional
|
||||
|
||||
from configs.path_config import IMAGE_PATH, DATA_PATH, RECORD_PATH, TEXT_PATH, FONT_PATH, LOG_PATH
|
||||
from .data_class import StaticData
|
||||
from pathlib import Path
|
||||
from ruamel.yaml import YAML
|
||||
@@ -47,19 +49,21 @@ class ResourcesManager(StaticData):
|
||||
if module in self._data.keys():
|
||||
for x in self._data[module].keys():
|
||||
move_file = Path(self._data[module][x])
|
||||
if move_file.exists():
|
||||
shutil.rmtree(move_file.absolute(), ignore_errors=True)
|
||||
logger.info(f"已清除插件 {module} 资源路径:{self._data[module][x]}")
|
||||
del self._data[module][x]
|
||||
if move_file not in [IMAGE_PATH, DATA_PATH, RECORD_PATH, TEXT_PATH, FONT_PATH, LOG_PATH]:
|
||||
if move_file.exists():
|
||||
shutil.rmtree(move_file.absolute(), ignore_errors=True)
|
||||
logger.info(f"已清除插件 {module} 资源路径:{self._data[module][x]}")
|
||||
del self._data[module][x]
|
||||
else:
|
||||
if isinstance(source_file, Path):
|
||||
source_file = str(source_file.absolute())
|
||||
if source_file:
|
||||
if module in self._data.keys() and source_file in self._data[module].keys():
|
||||
move_file = Path(self._data[module][source_file])
|
||||
if move_file.exists():
|
||||
shutil.rmtree(move_file.absolute(), ignore_errors=True)
|
||||
del self._data[module][source_file]
|
||||
if move_file not in [IMAGE_PATH, DATA_PATH, RECORD_PATH, TEXT_PATH, FONT_PATH, LOG_PATH]:
|
||||
if move_file.exists():
|
||||
shutil.rmtree(move_file.absolute(), ignore_errors=True)
|
||||
del self._data[module][source_file]
|
||||
self.save()
|
||||
|
||||
def start_move(self):
|
||||
@@ -97,7 +101,6 @@ class ResourcesManager(StaticData):
|
||||
"""
|
||||
添加临时清理文件夹
|
||||
:param path: 路径
|
||||
:param recursive: 是否将该目录下的所有目录也添加为临时文件夹
|
||||
"""
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
def prompt2cn(text: str, count: int, s: str = "#") -> str:
|
||||
"""
|
||||
格式化中文提示
|
||||
:param text: 文本
|
||||
:param count: 个数
|
||||
:param s: #
|
||||
"""
|
||||
return s * count + "\n" + s * 6 + " " + text + " " + s * 6 + "\n" + s * count
|
||||
|
||||
|
||||
Reference in New Issue
Block a user