版本更新 (#1666)

* ✨ 父级插件加载

* ✅ 添加测试:更新与添加插件 (#1594)

* ✅ 测试更新与添加插件

* ✅ Sourcery建议

* 👷 添加pytest

* 🎨 优化代码

* 🐛 bug修复

* 🐛修复添加插件返回403的问题 (#1595)

* 完善测试方法
* vscode测试配置
* 重构插件安装过程

* 🎨 修改readme

* Update README.md

* 🐛 修改bug与版本锁定

* 🐛 修复超级用户对群组功能开关

* 🐛 修复插件商店检查插件更新问题 (#1597)

* 🐛 修复插件商店检查插件更新问题

* 🐛 恶意命令检测问题

* 🐛 增加插件状态检查 (#1598)

* ✅ 优化测试用例

* 🐛 更改插件更新与安装逻辑

* 🐛 修复更新群组成员信息

* 🎨 代码优化

* 🚀 更新Dockerfile (#1599)

* 🎨 更新requirements

* ➕ 添加依赖aiocache

* ⚡ 添加github镜像

* ✨ 添加仓库目录多获取渠道

* 🐛 修复测试用例

* ✨ 添加API缓存

* 🎨 采取Sourcery建议

* 🐛 文件下载逻辑修改

* 🎨 优化代码

* 🐛 修复插件开关有时出现错误

* ✨ 重构自检ui

* 🐛 自检html修正

* 修复签到逻辑bug,并使代码更灵活以适应签到好感度等级配置 (#1606)

* 修复签到功能已知问题

* 修复签到功能已知问题

* 修改参数名称

* 修改uid判断

---------

Co-authored-by: HibiKier <45528451+HibiKier@users.noreply.github.com>

* 🎨 代码结构优化

* 🐛 私聊时修改插件时删除私聊帮助

* 🐛 过滤父插件

* 🐛 修复自检在ARM上的问题 (#1607)

* 🐛 修复自检在ARM上的问题

* ✅ 优化测试

* ✨ 支持mysql,psql,sqlite随机函数

* 🔧 VSCode配置修改

* 🔧 VSCode配置修改

* ✨ 添加金币排行

Co-Authored-By: HibiKier <45528451+HibiKier@users.noreply.github.com>

* 📝 修改README

Co-Authored-By: HibiKier <45528451+HibiKier@users.noreply.github.com>

* 🔨 提取GitHub相关操作 (#1609)

* 🔨 提取GitHub相关操作

* 🔨 重构API策略

* ✨ 签到/金币排行限制最大数量 (#1616)

* ✨ 签到/金币排行限制最大数量

* 🐛 修复超级用户id获取问题

* 🐛 修复路径解压与挂载 (#1619)

* 🐛 修复功能少时zhenxun帮助图片排序问题 (#1620)

* 🐛 签到文本适应 (#1622)

* 🐛 好感度排行提供默认值 (#1624)

* 🎈 优先使用github api (#1625)

* ✨ 重构帮助,限制普通用户查询管理插件 (#1626)

* 🐛 修复群权限与插件等级匹配 (#1627)

* ✨ 当管理员尝试ban真寻时将被反杀 (#1628)

* ✨ 群组发言时间检测提供开关配置 (#1630)

* 🐳 chore: 支持自动修改版本号 (#1629)

* 🎈 perf(github_utils): 支持github url下载遍历 (#1632)

* 🎈 perf(github_utils): 支持github url下载遍历

* 🐞 fix(http_utils): 修复一些下载问题

* 🦄 refactor(http_utils): 部分重构

* chore(version): Update version to v0.2.2-e6f17c4

---------

Co-authored-by: AkashiCoin <AkashiCoin@users.noreply.github.com>

* 🧪 test(auto_update): 修复测试用例 (#1633)

* 🐛 修复商店商品为空时报错 (#1634)

* 🐛 修复群权限与插件等级匹配 (#1635)

* ✨ message_build支持AtAll (#1639)

* 🎈 perf: 使用commit号下载插件 (#1641)

* 🎈 perf: 使用commit号下载插件

* chore(version): Update version to v0.2.2-f9c7360

---------

Co-authored-by: AkashiCoin <AkashiCoin@users.noreply.github.com>

* 🐳 chore: 修改运行检查触发路径 (#1642)

* 🐳 chore: 修改运行检查触发路径

* 🐳 chore: 添加tests目录

* ✨ 重构qq群事件处理 (#1643)

* 🐛 签到名称自适应 (#1644)

* 🎨  更新README (#1645)

* 🐛 fix(http_utils): 流式下载Content-Length错误 (#1647)

* 🐛 修复群组中帮助功能状态显示问题 (#1650)

* 🐛 修复群欢迎消息设置 (#1651)

* 🐛 修复webui下载后首次启动错误 (#1652)

* 🐛 修复webui下载后首次启动错误

* chore(version): Update version to v0.2.2-4a8ef85

---------

Co-authored-by: HibiKier <HibiKier@users.noreply.github.com>

* ✨ 移除默认图片文件夹:爬 (#1653)

* ✨ 安装/移除插件提供插件安装/卸载方法用于插件初始化 (#1654)

* ✨ 新增超级用户与管理员帮助模板 (#1655)

* ✨ 新增个人信息命令 (#1657)

* ✨ 修改个人信息菜单名称 (#1658)

* ✨ 新增插件商店api (#1659)

* ✨ 新增插件商店api

* chore(version): Update version to v0.2.2-7e15f20

---------

Co-authored-by: HibiKier <HibiKier@users.noreply.github.com>

* ✨ 将cd,block,count限制复原配置文件 (#1662)

* 🎨 修改README (#1663)

* 🎨 修改版本号 (#1664)

* 🎨 修改requirements (#1665)

---------

Co-authored-by: AkashiCoin <l1040186796@gmail.com>
Co-authored-by: fanyinrumeng <42991257+fanyinrumeng@users.noreply.github.com>
Co-authored-by: AkashiCoin <i@loli.vet>
Co-authored-by: Elaga <1728903318@qq.com>
Co-authored-by: AkashiCoin <AkashiCoin@users.noreply.github.com>
Co-authored-by: HibiKier <HibiKier@users.noreply.github.com>
This commit is contained in:
HibiKier
2024-10-01 00:42:23 +08:00
committed by GitHub
co-authored by AkashiCoin HibiKier AkashiCoin fanyinrumeng AkashiCoin Elaga
parent daa40ae8b8
commit 4e33bf3a50
216 changed files with 10281 additions and 2538 deletions
+59 -14
View File
@@ -1,24 +1,28 @@
from nonebot.adapters import Bot, Event
from nonebot.plugin import PluginMetadata
from nonebot_plugin_session import EventSession
from nonebot_plugin_userinfo import UserInfo, EventUserInfo
from nonebot_plugin_alconna import (
Alconna,
Args,
Query,
Option,
UniMsg,
Alconna,
Arparma,
Subcommand,
UniMessage,
UniMsg,
AlconnaQuery,
on_alconna,
store_true,
)
from nonebot_plugin_session import EventSession
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
from zhenxun.configs.utils import BaseBlock, PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.enum import BlockType, PluginType
from zhenxun.utils.exception import GoodsNotFound
from zhenxun.utils.message import MessageUtils
from zhenxun.utils.exception import GoodsNotFound
from zhenxun.utils.enum import BlockType, PluginType
from zhenxun.configs.utils import BaseBlock, PluginExtraData
from ._data_source import ShopManage
from ._data_source import ShopManage, gold_rank
__plugin_meta__ = PluginMetadata(
name="商店",
@@ -30,6 +34,8 @@ __plugin_meta__ = PluginMetadata(
我的道具
使用道具 [名称/Id]
购买道具 [名称/Id]
金币排行 ?[num=10]
金币总排行 ?[num=10]
""".strip(),
extra=PluginExtraData(
author="HibiKier",
@@ -44,10 +50,12 @@ __plugin_meta__ = PluginMetadata(
_matcher = on_alconna(
Alconna(
"商店",
Option("--all", action=store_true),
Subcommand("my-cost", help_text="我的金币"),
Subcommand("my-props", help_text="我的道具"),
Subcommand("buy", Args["name", str]["num", int, 1], help_text="购买道具"),
Subcommand("use", Args["name", str]["num?", int, 1], help_text="使用道具"),
Subcommand("gold-list", Args["num?", int], help_text="金币排行"),
),
priority=5,
block=True,
@@ -81,6 +89,20 @@ _matcher.shortcut(
prefix=True,
)
_matcher.shortcut(
"金币排行",
command="商店",
arguments=["gold-list"],
prefix=True,
)
_matcher.shortcut(
r"金币总排行",
command="商店",
arguments=["--all", "gold-list"],
prefix=True,
)
@_matcher.assign("$main")
async def _(session: EventSession, arparma: Arparma):
@@ -96,7 +118,7 @@ async def _(session: EventSession, arparma: Arparma):
gold = await ShopManage.my_cost(session.id1, session.platform)
await MessageUtils.build_message(f"你的当前余额: {gold}").send(reply_to=True)
else:
await MessageUtils.build_message(f"用户id为空...").send(reply_to=True)
await MessageUtils.build_message("用户id为空...").send(reply_to=True)
@_matcher.assign("my-props")
@@ -111,11 +133,9 @@ async def _(
session.platform,
):
await MessageUtils.build_message(image.pic2bytes()).finish(reply_to=True)
return await MessageUtils.build_message(f"你的道具为空捏...").send(
reply_to=True
)
return await MessageUtils.build_message("你的道具为空捏...").send(reply_to=True)
else:
await MessageUtils.build_message(f"用户id为空...").send(reply_to=True)
await MessageUtils.build_message("用户id为空...").send(reply_to=True)
@_matcher.assign("buy")
@@ -129,7 +149,7 @@ async def _(session: EventSession, arparma: Arparma, name: str, num: int):
result = await ShopManage.buy_prop(session.id1, name, num, session.platform)
await MessageUtils.build_message(result).send(reply_to=True)
else:
await MessageUtils.build_message(f"用户id为空...").send(reply_to=True)
await MessageUtils.build_message("用户id为空...").send(reply_to=True)
@_matcher.assign("use")
@@ -155,3 +175,28 @@ async def _(
await MessageUtils.build_message(f"没有找到道具 {name} 或道具数量不足...").send(
reply_to=True
)
@_matcher.assign("gold-list")
async def _(
session: EventSession, arparma: Arparma, num: Query[int] = AlconnaQuery("num", 10)
):
if num.result > 50:
await MessageUtils.build_message("排行榜人数不能超过50哦...").finish()
if session.id1:
gid = session.id3 or session.id2
if not arparma.find("all") and not gid:
await MessageUtils.build_message(
"私聊中无法查看 '金币排行',请发送 '金币总排行'"
).finish()
if arparma.find("all"):
gid = None
result = await gold_rank(session.id1, gid, num.result, session.platform)
logger.info(
"查看金币排行",
arparma.header_result,
session=session,
)
await MessageUtils.build_message(result).send(reply_to=True)
else:
await MessageUtils.build_message("用户id为空...").send(reply_to=True)
+100 -55
View File
@@ -1,28 +1,40 @@
import time
import asyncio
import inspect
import time
from typing import Any, Literal
from types import MappingProxyType
from typing import Any, Callable, Literal
from collections.abc import Callable
from nonebot.adapters import Bot, Event
from nonebot_plugin_alconna import UniMessage, UniMsg
from nonebot_plugin_session import EventSession
from pydantic import BaseModel, create_model
from nonebot_plugin_session import EventSession
from nonebot_plugin_alconna import UniMsg, UniMessage
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.services.log import logger
from zhenxun.models.goods_info import GoodsInfo
from zhenxun.utils.platform import PlatformUtils
from zhenxun.models.friend_user import FriendUser
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.models.user_console import UserConsole
from zhenxun.models.user_gold_log import UserGoldLog
from zhenxun.models.user_props_log import UserPropsLog
from zhenxun.services.log import logger
from zhenxun.utils.enum import GoldHandle, PropHandle
from zhenxun.models.user_props_log import UserPropsLog
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.utils.image_utils import BuildImage, ImageTemplate, text2image
ICON_PATH = IMAGE_PATH / "shop_icon"
RANK_ICON_PATH = IMAGE_PATH / "_icon"
PLATFORM_PATH = {
"dodo": RANK_ICON_PATH / "dodo.png",
"discord": RANK_ICON_PATH / "discord.png",
"kaiheila": RANK_ICON_PATH / "kook.png",
"qq": RANK_ICON_PATH / "qq.png",
}
class Goods(BaseModel):
name: str
"""商品名称"""
before_handle: list[Callable] = []
@@ -44,7 +56,6 @@ class Goods(BaseModel):
class ShopParam(BaseModel):
goods_name: str
"""商品名称"""
user_id: int
@@ -65,11 +76,55 @@ class ShopParam(BaseModel):
"""单次使用最大次数"""
session: EventSession | None = None
"""EventSession"""
message: UniMsg
"""UniMessage"""
async def gold_rank(user_id: str, group_id: str | None, num: int, platform: str):
query = UserConsole
if group_id:
uid_list = await GroupInfoUser.filter(group_id=group_id).values_list(
"user_id", flat=True
)
query = query.filter(user_id__in=uid_list)
user_list = await query.annotate().order_by("-gold").values_list("user_id", "gold")
user_id_list = [user[0] for user in user_list]
index = user_id_list.index(user_id) + 1
user_list = user_list[:num] if num < len(user_list) else user_list
friend_user = await FriendUser.filter(user_id__in=user_id_list).values_list(
"user_id", "user_name"
)
uid2name = {user[0]: user[1] for user in friend_user}
if diff_id := set(user_id_list).difference(set(uid2name.keys())):
group_user = await GroupInfoUser.filter(user_id__in=diff_id).values_list(
"user_id", "user_name"
)
for g in group_user:
uid2name[g[0]] = g[1]
column_name = ["排名", "-", "名称", "金币", "平台"]
data_list = []
for i, user in enumerate(user_list):
ava_bytes = await PlatformUtils.get_user_avatar(user[0], platform)
data_list.append(
[
f"{i+1}",
(ava_bytes, 30, 30) if platform == "qq" else "",
uid2name.get(user[0]),
user[1],
(PLATFORM_PATH.get(platform), 30, 30),
]
)
if group_id:
title = "金币群组内排行"
tip = f"你的排名在本群第 {index} 位哦!"
else:
title = "金币全局排行"
tip = f"你的排名在全局第 {index} 位哦!"
return await ImageTemplate.table_page(title, tip, column_name, data_list)
class ShopManage:
uuid2goods: dict[str, Goods] = {}
uuid2goods: dict[str, Goods] = {} # noqa: RUF012
@classmethod
def __build_params(
@@ -102,6 +157,7 @@ class ShopManage:
"num": num,
"text": text,
"session": session,
"message": message,
}
)
return model, {
@@ -113,6 +169,7 @@ class ShopManage:
"num": num,
"text": text,
"goods_name": goods.name,
"message": message,
}
@classmethod
@@ -121,7 +178,6 @@ class ShopManage:
args: MappingProxyType,
param: ShopParam,
session: EventSession,
message: UniMsg,
**kwargs,
) -> list[Any]:
"""解析参数
@@ -144,7 +200,7 @@ class ShopManage:
elif par in ["session"]:
param_list.append(session)
elif par in ["message"]:
param_list.append(message)
param_list.append(kwargs.get("message"))
elif par not in ["args", "kwargs"]:
param_list.append(param_json.get(par))
if kwargs.get(par) is not None:
@@ -170,16 +226,15 @@ class ShopManage:
if fun_list:
for func in fun_list:
args = inspect.signature(func).parameters
if args and list(args.keys())[0] != "kwargs":
if args and next(iter(args.keys())) != "kwargs":
if asyncio.iscoroutinefunction(func):
await func(*cls.__parse_args(args, param, **kwargs))
else:
func(*cls.__parse_args(args, param, **kwargs))
elif asyncio.iscoroutinefunction(func):
await func(**kwargs)
else:
if asyncio.iscoroutinefunction(func):
await func(**kwargs)
else:
func(**kwargs)
func(**kwargs)
@classmethod
async def __run(
@@ -187,7 +242,6 @@ class ShopManage:
goods: Goods,
param: ShopParam,
session: EventSession,
message: UniMsg,
**kwargs,
) -> str | UniMessage | None:
"""运行道具函数
@@ -201,22 +255,18 @@ class ShopManage:
"""
args = inspect.signature(goods.func).parameters # type: ignore
if goods.func:
if args and list(args.keys())[0] != "kwargs":
if asyncio.iscoroutinefunction(goods.func):
return await goods.func(
*cls.__parse_args(args, param, session, message, **kwargs)
)
else:
return goods.func(
*cls.__parse_args(args, param, session, message, **kwargs)
)
if args and next(iter(args.keys())) != "kwargs":
return (
await goods.func(*cls.__parse_args(args, param, session, **kwargs))
if asyncio.iscoroutinefunction(goods.func)
else goods.func(*cls.__parse_args(args, param, session, **kwargs))
)
if asyncio.iscoroutinefunction(goods.func):
return await goods.func(
**kwargs,
)
else:
if asyncio.iscoroutinefunction(goods.func):
return await goods.func(
**kwargs,
)
else:
return goods.func(**kwargs)
return goods.func(**kwargs)
@classmethod
async def use(
@@ -252,17 +302,17 @@ class ShopManage:
if not goods_info:
return f"{goods_name} 不存在..."
if goods_info.is_passive:
return f"{goods_name} 是被动道具, 无法使用..."
return f"{goods_info.goods_name} 是被动道具, 无法使用..."
goods = cls.uuid2goods.get(goods_info.uuid)
if not goods or not goods.func:
return f"{goods_name} 未注册使用函数, 无法使用..."
return f"{goods_info.goods_name} 未注册使用函数, 无法使用..."
param, kwargs = cls.__build_params(
bot, event, session, message, goods, num, text
)
if num > param.max_num_limit:
return f"{goods_info.goods_name} 单次使用最大数量为{param.max_num_limit}..."
await cls.run_before_after(goods, param, "before", **kwargs)
result = await cls.__run(goods, param, session, message, **kwargs)
result = await cls.__run(goods, param, session, **kwargs)
await UserConsole.use_props(session.id1, goods_info.uuid, num, session.platform) # type: ignore
await cls.run_before_after(goods, param, "after", **kwargs)
if not result and param.send_success_msg:
@@ -334,11 +384,10 @@ class ShopManage:
]
if name.isdigit():
goods = goods_list[int(name) - 1]
elif filter_goods := [g for g in goods_list if g.goods_name == name]:
goods = filter_goods[0]
else:
if filter_goods := [g for g in goods_list if g.goods_name == name]:
goods = filter_goods[0]
else:
return "道具名称不存在..."
return "道具名称不存在..."
user = await UserConsole.get_user(user_id, platform)
price = goods.goods_price * num * goods.goods_discount
if user.gold < price:
@@ -423,13 +472,12 @@ class ShopManage:
BuildImage: 商店图片
"""
goods_lst = await GoodsInfo.get_all_goods()
_dc = {}
font_h = BuildImage.get_text_size("正")[1]
h = 10
_list: list[GoodsInfo] = []
for goods in goods_lst:
if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time:
_list.append(goods)
_list: list[GoodsInfo] = [
goods
for goods in goods_lst
if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time
]
# A = BuildImage(1100, h, color="#f9f6f2")
total_n = 0
image_list = []
@@ -471,7 +519,7 @@ class ShopManage:
440 + _tmp.width,
0,
),
f" 金币",
" 金币",
center_type="height",
)
des_image = None
@@ -541,7 +589,7 @@ class ShopManage:
).split()
y_m_d = limit_time[0]
_h_m = limit_time[1].split(":")
h_m = _h_m[0] + "时 " + _h_m[1] + "分"
h_m = f"{_h_m[0]}时 {_h_m[1]}分"
await bk.text((_w + 55, 38), str(y_m_d))
await bk.text((_w + 65, 57), str(h_m))
_w += 140
@@ -575,8 +623,7 @@ class ShopManage:
f"{goods.daily_limit}", size=30
)
await bk.paste(_tmp, (_w + 72, 45))
if total_n < n:
total_n = n
total_n = max(total_n, n)
if n:
await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5)
# await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5)
@@ -585,10 +632,8 @@ class ShopManage:
image_list.append(bk)
# await A.apaste(bk, (0, current_h), True)
# current_h += 90
h = 0
current_h = 0
for img in image_list:
h += img.height + 10
h = sum(img.height + 10 for img in image_list) or 400
A = BuildImage(1100, h, color="#f9f6f2")
for img in image_list:
await A.paste(img, (0, current_h))
@@ -597,7 +642,7 @@ class ShopManage:
if total_n:
w += total_n
h = A.height + 230 + 100
h = 1000 if h < 1000 else h
h = max(h, 1000)
shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png")
shop = BuildImage(w, h, font_size=20, color="#f9f6f2")
await shop.paste(A, (20, 230))