refactor🎨: 重构红包功能

This commit is contained in:
HibiKier 2023-08-21 00:31:18 +08:00
parent 0a4df8296e
commit 81ddbc30f4
8 changed files with 671 additions and 335 deletions

View File

@ -331,9 +331,14 @@ PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能
## 更新 ## 更新
### 2022/8/20 ### 2023/8/28
* 重构`红包`功能,允许一个群聊中有多个用户发起的红包,发送`开`等命令会开启群中所有条件允许的红包,新增`红包结算排行`,在红包退回或抢完时统计,在`塞红包`时at可以发送专属红包
### 2023/8/20
* 修复词条回答包含at时使用模糊|正则等问时无法正确匹配问题 * 修复词条回答包含at时使用模糊|正则等问时无法正确匹配问题
* 修复开箱时最后开箱日期数据未更新
### 2023/8/7 ### 2023/8/7

View File

@ -1,6 +1,8 @@
import random import random
import re
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Dict, List, Optional
from apscheduler.jobstores.base import JobLookupError from apscheduler.jobstores.base import JobLookupError
from nonebot import on_command, on_notice from nonebot import on_command, on_notice
@ -21,15 +23,16 @@ from nonebot.rule import to_me
from configs.config import NICKNAME from configs.config import NICKNAME
from configs.path_config import IMAGE_PATH from configs.path_config import IMAGE_PATH
from services.log import logger from services.log import logger
from utils.message_builder import image from utils.depends import AtList, GetConfig
from utils.message_builder import at, image
from utils.utils import is_number, scheduler from utils.utils import is_number, scheduler
from .config import FESTIVE_KEY, GroupRedBag, RedBag
from .data_source import ( from .data_source import (
build_open_result_image,
check_gold, check_gold,
generate_open_redbag_pic, end_festive_red_bag,
generate_send_redbag_pic, random_red_bag_background,
open_redbag,
return_gold,
) )
__zx_plugin_name__ = "金币红包" __zx_plugin_name__ = "金币红包"
@ -37,7 +40,7 @@ __plugin_usage__ = """
usage usage
在群内发送指定金额的红包拼手气项目 在群内发送指定金额的红包拼手气项目
指令 指令
塞红包 [金币数] ?[红包数=5]: 塞入红包 塞红包 [金币数] ?[红包数=5] ?[at指定人]: 塞入红包
//*戳一戳*: 打开红包 //*戳一戳*: 打开红包
退回: 退回未开完的红包必须在一分钟后使用 退回: 退回未开完的红包必须在一分钟后使用
示例塞红包 1000 示例塞红包 1000
@ -51,7 +54,7 @@ usage
""".strip() """.strip()
__plugin_des__ = "运气项目又来了" __plugin_des__ = "运气项目又来了"
__plugin_cmd__ = [ __plugin_cmd__ = [
"塞红包 [金币数] ?[红包数=5]", "塞红包 [金币数] ?[红包数=5] ?[at指定人]",
"开/抢", "开/抢",
"退回", "退回",
"节日红包 [金额] [数量] ?[祝福语] ?[指定群] [_superuser]", "节日红包 [金额] [数量] ?[祝福语] ?[指定群] [_superuser]",
@ -64,14 +67,35 @@ __plugin_settings__ = {
"limit_superuser": False, "limit_superuser": False,
"cmd": ["金币红包", "塞红包"], "cmd": ["金币红包", "塞红包"],
} }
__plugin_resources__ = {"prts": IMAGE_PATH} __plugin_cd_limit__ = {"rst": "急什么急什么,待会再发!"}
__plugin_configs__ = {
"DEFAULT_TIMEOUT": {
"value": 600,
"help": "普通红包默认超时时间",
"default_value": 600,
"type": int,
},
"DEFAULT_INTERVAL": {
"value": 60,
"help": "用户发送普通红包最小间隔时间",
"default_value": 60,
"type": int,
},
"RANK_NUM": {
"value": 10,
"help": "结算排行显示前N位",
"default_value": 10,
"type": int,
},
}
# __plugin_resources__ = {"prts": IMAGE_PATH}
async def rule(event: GroupMessageEvent) -> bool: async def rule(event: GroupMessageEvent) -> bool:
return check_on_gold_red(event) return check_on_gold_red(event)
gold_redbag = on_command( gold_red_bag = on_command(
"塞红包", aliases={"金币红包"}, priority=5, block=True, permission=GROUP "塞红包", aliases={"金币红包"}, priority=5, block=True, permission=GROUP
) )
@ -87,167 +111,197 @@ festive_redbag = on_command(
"节日红包", priority=5, block=True, permission=SUPERUSER, rule=to_me() "节日红包", priority=5, block=True, permission=SUPERUSER, rule=to_me()
) )
redbag_data = {} GROUP_DATA: Dict[int, GroupRedBag] = {}
festive_redbag_data = {} PATTERN = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~,。;‘、"""
# 阻断其他poke # 阻断其他poke
@run_preprocessor # @run_preprocessor
# async def _(
# matcher: Matcher,
# event: PokeNotifyEvent,
# ):
# try:
# if matcher.type == "notice" and event.self_id == event.target_id:
# flag = check_on_gold_red(event)
# if flag:
# if matcher.plugin_name == "poke":
# raise IgnoredException("目前正在抢红包...")
# else:
# if matcher.plugin_name == "gold_red_bag":
# raise IgnoredException("目前没有红包...")
# except AttributeError:
# pass
@gold_red_bag.handle()
async def _( async def _(
matcher: Matcher, bot: Bot,
event: PokeNotifyEvent, event: GroupMessageEvent,
arg: Message = CommandArg(),
at_list: List[int] = AtList(),
default_interval: int = GetConfig(config="DEFAULT_INTERVAL"),
): ):
try: group_red_bag: Optional[GroupRedBag] = GROUP_DATA.get(event.group_id)
if matcher.type == "notice" and event.self_id == event.target_id: if not group_red_bag:
flag = check_on_gold_red(event) group_red_bag = GroupRedBag(event.group_id)
if flag: GROUP_DATA[event.group_id] = group_red_bag
if matcher.plugin_name == "poke": # 剩余过期时间
raise IgnoredException("目前正在抢红包...") time_remaining = group_red_bag.check_timeout(event.user_id)
else: if time_remaining != -1:
if matcher.plugin_name == "gold_redbag": # 判断用户红包是否存在且是否过时覆盖
raise IgnoredException("目前没有红包...") if user_red_bag := group_red_bag.get_user_red_bag(event.user_id):
except AttributeError: now = time.time()
pass if now < user_red_bag.start_time + default_interval:
await gold_red_bag.finish(
f"你的红包还没消化完捏...还剩下 {user_red_bag.num - len(user_red_bag.open_user)} 个! 请等待红包领取完毕..."
@gold_redbag.handle() f"(或等待{time_remaining}秒红包cd)"
async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()): )
global redbag_data, festive_redbag_data msg = arg.extract_plain_text().strip().split()
try: if not msg:
if time.time() - redbag_data[event.group_id]["time"] > 60: await gold_red_bag.finish("不塞钱发什么红包!")
amount = ( amount = msg[0]
redbag_data[event.group_id]["amount"]
- redbag_data[event.group_id]["open_amount"]
)
await return_gold(
redbag_data[event.group_id]["user_id"], str(event.group_id), amount
)
await gold_redbag.send(
f'{redbag_data[event.group_id]["nickname"]}的红包过时未开完,退还{amount}金币...'
)
redbag_data[event.group_id] = {}
else:
await gold_redbag.finish(
f'目前 {redbag_data[event.group_id]["nickname"]} 的红包还没有开完噢,'
f'还剩下 {len(redbag_data[event.group_id]["redbag"])} 个红包!'
f'(或等待{str(60 - time.time() + redbag_data[event.group_id]["time"])[:2]}秒红包过时)'
)
except KeyError:
pass
msg = arg.extract_plain_text().strip()
msg = msg.split()
if len(msg) == 1: if len(msg) == 1:
flag, amount = await check_gold(str(event.user_id), str(event.group_id), msg[0]) flag, tip = await check_gold(str(event.user_id), str(event.group_id), amount)
if not flag: if not flag:
await gold_redbag.finish(str(amount)) await gold_red_bag.finish(tip, at_sender=True)
num = 5 num = 5
else: else:
amount = msg[0]
num = msg[1] num = msg[1]
if not is_number(num) or int(num) < 1: if not is_number(num) or int(num) < 1:
await gold_redbag.finish("红包个数给我输正确啊!", at_sender=True) await gold_red_bag.finish("红包个数给我输正确啊!", at_sender=True)
flag, amount = await check_gold(str(event.user_id), str(event.group_id), amount) flag, tip = await check_gold(str(event.user_id), str(event.group_id), amount)
if not flag: if not flag:
await gold_redbag.finish(str(amount), at_sender=True) await gold_red_bag.finish(tip, at_sender=True)
group_member_num = (await bot.get_group_info(group_id=event.group_id))[ group_member_num = (await bot.get_group_info(group_id=event.group_id))[
"member_count" "member_count"
] ]
num = int(num) num = int(num)
if num > group_member_num: if num > group_member_num:
await gold_redbag.send("你发的红包数量也太多了,已经为你修改成与本群人数相同的红包数量...") await gold_red_bag.send("你发的红包数量也太多了,已经为你修改成与本群人数相同的红包数量...")
num = group_member_num num = group_member_num
nickname = event.sender.card or event.sender.nickname nickname = event.sender.card or event.sender.nickname
flag, result = init_redbag( await group_red_bag.add_red_bag(
f"{nickname}的红包",
int(amount),
1 if at_list else num,
nickname or "",
str(event.user_id), str(event.user_id),
str(event.group_id), assigner=str(at_list[0]) if at_list else None,
nickname or str(event.user_id), )
amount, await gold_red_bag.send(
num, f"{nickname}发起了金币红包\n金额: {amount}\n数量: {num}\n"
int(bot.self_id), + image(await random_red_bag_background(event.user_id))
) )
if not flag:
await gold_redbag.finish(result, at_sender=True)
else:
await gold_redbag.send(
f"{nickname}发起了金币红包\n金额:{amount}\n数量:{num}\n"
+ image(
b64=await generate_send_redbag_pic(
redbag_data[str(event.group_id)]["user_id"]
)
)
)
logger.info(f"塞入 {num} 个红包,共 {amount} 金币", "金币红包", event.user_id, event.group_id) logger.info(f"塞入 {num} 个红包,共 {amount} 金币", "金币红包", event.user_id, event.group_id)
@open_.handle() @open_.handle()
async def _(event: GroupMessageEvent, arg: Message = CommandArg()): async def _(
global redbag_data, festive_redbag_data event: GroupMessageEvent,
msg = arg.extract_plain_text().strip() arg: Message = CommandArg(),
msg = ( rank_num: int = GetConfig(config="RANK_NUM"),
msg.replace("!", "") ):
.replace("", "") if msg := arg.extract_plain_text().strip():
.replace(",", "") msg = re.sub(PATTERN, "", msg)
.replace("", "")
.replace(".", "")
.replace("", "")
)
if msg:
if "红包" not in msg: if "红包" not in msg:
return return
try: group_red_bag: Optional[GroupRedBag] = GROUP_DATA.get(event.group_id)
await open_.send( if group_red_bag:
image(b64=await get_redbag_img(str(event.user_id), str(event.group_id))), open_data, settlement_list = await group_red_bag.open(event.user_id)
at_sender=True, send_msg = ""
) for _, item in open_data.items():
except KeyError: amount, red_bag = item
await open_.finish("真贪心,明明已经开过这个红包了的说...", at_sender=True) result_image = await build_open_result_image(red_bag, event.user_id, amount)
send_msg += (
f"开启了 {red_bag.promoter} 的红包, 获取 {amount} 个金币\n"
+ image(result_image)
+ "\n"
)
logger.info(
f"抢到了 {red_bag.promoter}({red_bag.promoter_id}) 的红包,获取了{amount}个金币",
"开红包",
event.user_id,
event.group_id,
)
send_msg = send_msg[:-1] if send_msg else "没有红包给你开!"
await open_.send(send_msg, at_sender=True)
if settlement_list:
for red_bag in settlement_list:
await open_.send(
f"{red_bag.name}已结算\n"
+ image(await red_bag.build_amount_rank(rank_num))
)
@poke_.handle() # @poke_.handle()
async def _poke_(event: PokeNotifyEvent): # async def _poke_(event: PokeNotifyEvent):
global redbag_data, festive_redbag_data # group_id = getattr(event, "group_id", None)
if event.self_id == event.target_id: # if event.self_id == event.target_id and group_id:
flag = check_on_gold_red(event) # is_open = check_on_gold_red(event)
if not flag: # if not is_open:
return # return
await poke_.send( # group_red_bag: Optional[GroupRedBag] = GROUP_DATA.get(group_id)
image(b64=await get_redbag_img(str(event.user_id), str(event.group_id))), # if group_red_bag:
at_sender=True, # open_data, settlement_list = await group_red_bag.open(event.user_id)
) # send_msg = ""
# for _, item in open_data.items():
# amount, red_bag = item
# result_image = await build_open_result_image(
# red_bag, event.user_id, amount
# )
# send_msg += (
# f"开启了 {red_bag.promoter} 的红包, 获取 {amount} 个金币\n"
# + image(result_image)
# + "\n"
# )
# logger.info(
# f"抢到了 {red_bag.promoter}({red_bag.promoter_id}) 的红包,获取了{amount}个金币",
# "开红包",
# event.user_id,
# event.group_id,
# )
# if send_msg:
# await open_.send(send_msg, at_sender=True)
# if settlement_list:
# for red_bag in settlement_list:
# await open_.send(
# f"{red_bag.name}已结算\n"
# + image(await red_bag.build_amount_rank())
# )
@return_.handle() @return_.handle()
async def _(event: GroupMessageEvent): async def _(
global redbag_data event: GroupMessageEvent,
try: default_interval: int = GetConfig(config="DEFAULT_INTERVAL"),
if redbag_data[event.group_id]["user_id"] != event.user_id: rank_num: int = GetConfig(config="RANK_NUM"),
await return_.finish("不是你的红包你退回什么!", at_sender=True) ):
if time.time() - redbag_data[event.group_id]["time"] <= 60: group_red_bag: GroupRedBag = GROUP_DATA[event.group_id]
await return_.finish( if group_red_bag:
f'你的红包还没有过时,在 {str(60 - time.time() + redbag_data[event.group_id]["time"])[:2]} ' if user_red_bag := group_red_bag.get_user_red_bag(event.user_id):
f"秒后可以退回..", now = time.time()
at_sender=True, if now - user_red_bag.start_time < default_interval:
) await return_.finish(
await return_gold( f"你的红包还没有过时, 在 {int(default_interval - now + user_red_bag.start_time)} "
str(event.user_id), f"秒后可以退回...",
str(event.group_id), at_sender=True,
redbag_data[event.group_id]["amount"] )
- redbag_data[event.group_id]["open_amount"], user_red_bag = group_red_bag.get_user_red_bag(event.user_id)
) if user_red_bag and (
await return_.send( return_amount := await group_red_bag.settlement(event.user_id)
f"已成功退还了 " ):
f"{redbag_data[event.group_id]['amount'] - redbag_data[event.group_id]['open_amount']} " logger.info(
f"金币", f"退回了红包 {return_amount} 金币", "红包退回", event.user_id, event.group_id
at_sender=True, )
) await return_.send(
logger.info( f"已成功退还了 "
f"USER {event.user_id} GROUP {event.group_id} 退回了" f"{return_amount} 金币\n"
f"红包 {redbag_data[event.group_id]['amount'] - redbag_data[event.group_id]['open_amount']} 金币" + image(await user_red_bag.build_amount_rank(rank_num)),
) at_sender=True,
redbag_data[event.group_id] = {} )
except KeyError: await return_.send("目前没有红包可以退回...", at_sender=True)
await return_.finish("目前没有红包可以退回...", at_sender=True)
@festive_redbag.handle() @festive_redbag.handle()
@ -280,138 +334,40 @@ async def _(bot: Bot, arg: Message = CommandArg()):
gl = await bot.get_group_list() gl = await bot.get_group_list()
gl = [g["group_id"] for g in gl] gl = [g["group_id"] for g in gl]
for g in gl: for g in gl:
group_red_bag: Optional[GroupRedBag] = GROUP_DATA.get(g)
if not group_red_bag:
group_red_bag = GroupRedBag(g)
GROUP_DATA[g] = group_red_bag
try: try:
scheduler.remove_job(f"festive_redbag_{g}") scheduler.remove_job(f"{FESTIVE_KEY}_{g}")
await end_festive_redbag(bot, g) await end_festive_red_bag(bot, group_red_bag)
except JobLookupError: except JobLookupError:
pass pass
init_redbag(bot.self_id, g, f"{NICKNAME}", amount, num, int(bot.self_id), 1) await group_red_bag.add_red_bag(
f"{NICKNAME}的红包", int(amount), num, NICKNAME, FESTIVE_KEY, True
)
scheduler.add_job( scheduler.add_job(
end_festive_redbag, end_festive_red_bag,
"date", "date",
run_date=(datetime.now() + timedelta(hours=24)).replace(microsecond=0), # run_date=(datetime.now() + timedelta(hours=24)).replace(microsecond=0),
id=f"festive_redbag_{g}", run_date=(datetime.now() + timedelta(seconds=30)).replace(
args=[bot, g], microsecond=0
),
id=f"{FESTIVE_KEY}_{g}",
args=[bot, group_red_bag],
) )
try: try:
await bot.send_group_msg( await bot.send_group_msg(
group_id=g, group_id=g,
message=f"{NICKNAME}发起了金币红包\n金额:{amount}\n数量:{num}\n" message=f"{NICKNAME}发起了金币红包\n金额:{amount}\n数量:{num}\n"
+ image( + image(await random_red_bag_background(bot.self_id, greetings)),
b64=await generate_send_redbag_pic(int(bot.self_id), greetings)
),
) )
logger.debug("节日红包图片信息发送成功...", "节日红包", group_id=g)
except ActionFailed: except ActionFailed:
logger.warning(f"节日红包 GROUP {g} 发送失败..") logger.warning(f"节日红包图片信息发送失败...", "节日红包", group_id=g)
pass
# 红包数据初始化
def init_redbag(
user_id: str,
group_id: str,
nickname: str,
amount: int,
num: int,
bot_self_id: int,
mode: int = 0,
):
global redbag_data, festive_redbag_data
data = redbag_data if mode == 0 else festive_redbag_data
if not data.get(group_id):
data[group_id] = {}
try:
if data[group_id]["user_id"] and user_id != bot_self_id:
return False, f'{data[group_id]["nickname"]}的红包还没抢完呢...'
except KeyError:
pass
data[group_id]["user_id"] = user_id
data[group_id]["nickname"] = nickname
data[group_id]["amount"] = amount
data[group_id]["num"] = num
data[group_id]["open_amount"] = 0
data[group_id]["time"] = time.time()
data[group_id]["redbag"] = random_redbag(amount, num)
data[group_id]["open_user"] = []
if mode == 0:
redbag_data = data
else:
festive_redbag_data = data
return True, ""
# 随机红包排列
def random_redbag(amount: int, num: int) -> list:
redbag_lst = []
for _ in range(num - 1):
tmp = int(amount / random.choice(range(3, num + 3)))
redbag_lst.append(tmp)
amount -= tmp
redbag_lst.append(amount)
return redbag_lst
# 返回开红包图片
async def get_redbag_img(user_id: str, group_id: str):
global redbag_data, festive_redbag_data
data = redbag_data
mode = 0
if festive_redbag_data.get(group_id):
try:
if user_id not in festive_redbag_data[group_id]["open_user"]:
data = festive_redbag_data
mode = 1
except KeyError:
pass
amount, data = await open_redbag(user_id, group_id, data)
text = (
f"已领取"
f'{data[group_id]["num"] - len(data[group_id]["redbag"])}'
f'/{data[group_id]["num"]}个,'
f'{data[group_id]["open_amount"]}/{data[group_id]["amount"]}金币'
)
logger.info(
f"USER {user_id} GROUP {group_id} 抢到了 {data[group_id]['user_id']} 的红包,获取了{amount}金币"
)
b64 = await generate_open_redbag_pic(
data[group_id]["user_id"], data[group_id]["nickname"], amount, text
)
if data[group_id]["open_amount"] == data[group_id]["amount"]:
data[group_id] = {}
if mode == 0:
redbag_data = data
else:
festive_redbag_data = data
return b64
async def end_festive_redbag(bot: Bot, group_id: int):
global festive_redbag_data
message = (
f"{NICKNAME}的节日红包过时了,一共开启了 "
f"{festive_redbag_data[group_id]['num'] - len(festive_redbag_data[group_id]['redbag'])}"
f" 个红包,共 {festive_redbag_data[group_id]['open_amount']} 金币"
)
await bot.send_group_msg(group_id=group_id, message=message)
festive_redbag_data[group_id] = {}
def check_on_gold_red(event) -> bool: def check_on_gold_red(event) -> bool:
flag1 = True if group_red_bag := GROUP_DATA.get(event.group_id):
flag2 = True return group_red_bag.check_open(event.user_id)
try: return False
if festive_redbag_data[str(event.group_id)]["user_id"]:
if event.user_id in festive_redbag_data[str(event.group_id)]["open_user"]:
flag1 = False
except KeyError:
flag1 = False
try:
if redbag_data[str(event.group_id)]["user_id"]:
if event.user_id in redbag_data[str(event.group_id)]["open_user"]:
flag2 = False
except KeyError:
flag2 = False
if flag1 or flag2:
return True
else:
return False

View File

@ -0,0 +1,311 @@
import random
import time
from datetime import datetime
from io import BytesIO
from typing import Dict, List, Optional, Tuple, Union, overload
from pydantic import BaseModel
from models.bag_user import BagUser
from models.group_member_info import GroupInfoUser
from plugins.gold_redbag.model import RedbagUser
from utils.image_utils import BuildImage
from utils.utils import get_user_avatar
FESTIVE_KEY = "FESTIVE"
"""节日红包KEY"""
class RedBag(BaseModel):
"""
红包
"""
group_id: str
"""所属群聊"""
name: str
"""红包名称"""
amount: int
"""总金币"""
num: int
"""红包数量"""
promoter: str
"""发起人昵称"""
promoter_id: str
"""发起人id"""
is_festival: bool
"""是否为节日红包"""
timeout: int
"""过期时间"""
assigner: Optional[str] = None
"""指定人id"""
start_time: float
"""红包发起时间"""
open_user: Dict[str, int] = {}
"""开启用户"""
red_bag_list: List[int]
async def build_amount_rank(self, num: int = 10) -> BuildImage:
"""生成结算红包图片
参数:
num: 查看的排名数量.
返回:
BuildImage: 结算红包图片
"""
user_image_list = []
if self.open_user:
sort_data = sorted(
self.open_user.items(), key=lambda item: item[1], reverse=True
)
num = num if num < len(self.open_user) else len(self.open_user)
user_id_list = [sort_data[i][0] for i in range(num)]
group_user_list = await GroupInfoUser.filter(
group_id=self.group_id, user_id__in=user_id_list
).all()
for i in range(num):
user_background = BuildImage(600, 100, font_size=30)
user_id, amount = sort_data[i]
user_ava_bytes = await get_user_avatar(user_id)
user_ava = None
if user_ava_bytes:
user_ava = BuildImage(80, 80, background=BytesIO(user_ava_bytes))
else:
user_ava = BuildImage(80, 80)
await user_ava.acircle_corner(10)
await user_background.apaste(user_ava, (130, 10), True)
no_image = BuildImage(100, 100, font_size=65, font="CJGaoDeGuo.otf")
await no_image.atext((0, 0), f"{i+1}", center_type="center")
await no_image.aline((99, 10, 99, 90), "#b9b9b9")
await user_background.apaste(no_image)
name = [
user.user_name
for user in group_user_list
if user_id == user.user_id
]
await user_background.atext((225, 15), name[0] if name else "")
amount_image = BuildImage(
0, 0, plain_text=f"{amount}", font_size=30, font_color="#cdac72"
)
await user_background.apaste(
amount_image, (user_background.w - amount_image.w - 20, 50), True
)
await user_background.aline((225, 99, 590, 99), "#b9b9b9")
user_image_list.append(user_background)
background = BuildImage(600, 150 + len(user_image_list) * 100)
top = BuildImage(600, 100, color="#f55545", font_size=30)
promoter_ava_bytes = await get_user_avatar(self.promoter_id)
promoter_ava = None
if promoter_ava_bytes:
promoter_ava = BuildImage(60, 60, background=BytesIO(promoter_ava_bytes))
else:
promoter_ava = BuildImage(60, 60)
await promoter_ava.acircle()
await top.apaste(promoter_ava, (10, 0), True, "by_height")
await top.atext((80, 33), self.name, (255, 255, 255))
right_text = BuildImage(150, 100, color="#f55545", font_size=30)
await right_text.atext((10, 33), "结算排行", (255, 255, 255))
await right_text.aline((4, 10, 4, 90), (255, 255, 255), 2)
await top.apaste(right_text, (460, 0))
await background.apaste(top)
cur_h = 110
for user_image in user_image_list:
await background.apaste(user_image, (0, cur_h))
cur_h += user_image.h
return background
class GroupRedBag:
"""
群组红包管理
"""
def __init__(self, group_id: Union[int, str]):
self.group_id = str(group_id)
self._data: Dict[str, RedBag] = {}
"""红包列表"""
def get_user_red_bag(self, user_id: Union[str, int]) -> Optional[RedBag]:
"""获取用户塞红包数据
参数:
user_id: 用户id
返回:
Optional[RedBag]: RedBag
"""
return self._data.get(str(user_id))
def check_open(self, user_id: Union[str, int]) -> bool:
"""检查是否有可开启的红包
参数:
user_id: 用户id
返回:
bool: 是否有可开启的红包
"""
user_id = str(user_id)
for _, red_bag in self._data.items():
if red_bag.assigner:
if red_bag.assigner == user_id:
return True
else:
if user_id not in red_bag.open_user:
return True
return False
def check_timeout(self, user_id: Union[int, str]) -> int:
"""判断用户红包是否过期
参数:
user_id: 用户id
返回:
int: 距离过期时间
"""
user_id = str(user_id)
if user_id in self._data:
reg_bag = self._data[user_id]
now = time.time()
if now < reg_bag.timeout + reg_bag.start_time:
return int(reg_bag.timeout + reg_bag.start_time - now)
return -1
async def open(
self, user_id: Union[int, str]
) -> Tuple[Dict[str, Tuple[int, RedBag]], List[RedBag]]:
"""开启红包
参数:
user_id: 用户id
返回:
Dict[str, Tuple[int, RedBag]]: 键为发起者id, 值为开启金额以及对应RedBag
List[RedBag]: 开完的红包
"""
user_id = str(user_id)
open_data = {}
settlement_list: List[RedBag] = []
for _, red_bag in self._data.items():
if red_bag.num > len(red_bag.open_user):
is_open = False
if red_bag.assigner:
is_open = red_bag.assigner == user_id
else:
is_open = user_id not in red_bag.open_user
if is_open:
random_amount = red_bag.red_bag_list.pop()
await RedbagUser.add_redbag_data(
user_id, self.group_id, "get", random_amount
)
await BagUser.add_gold(user_id, self.group_id, random_amount)
red_bag.open_user[user_id] = random_amount
open_data[red_bag.promoter_id] = (random_amount, red_bag)
if red_bag.num == len(red_bag.open_user):
# 红包开完,结算
settlement_list.append(red_bag)
if settlement_list:
for uid in [red_bag.promoter_id for red_bag in settlement_list]:
if uid in self._data:
del self._data[uid]
return open_data, settlement_list
def festive_red_bag_expire(self) -> Optional[RedBag]:
"""节日红包过期
返回:
Optional[RedBag]: 过期的节日红包
"""
if FESTIVE_KEY in self._data:
red_bag = self._data[FESTIVE_KEY]
del self._data[FESTIVE_KEY]
return red_bag
return None
async def settlement(
self, user_id: Optional[Union[int, str]] = None
) -> Optional[int]:
"""红包退回
参数:
user_id: 用户id, 指定id时结算指定用户红包.
返回:
int: 退回金币
"""
user_id = str(user_id)
if user_id:
if red_bag := self._data.get(user_id):
del self._data[user_id]
if red_bag.red_bag_list:
# 退还剩余金币
if amount := sum(red_bag.red_bag_list):
await BagUser.add_gold(user_id, self.group_id, amount)
return amount
return None
async def add_red_bag(
self,
name: str,
amount: int,
num: int,
promoter: str,
promoter_id: str,
is_festival: bool = False,
timeout: int = 60,
assigner: Optional[str] = None,
):
"""添加红包
参数:
name: 红包名称
amount: 金币数量
num: 红包数量
promoter: 发起人昵称
promoter_id: 发起人id
is_festival: 是否为节日红包.
timeout: 超时时间.
assigner: 指定人.
"""
user_gold = await BagUser.get_gold(promoter_id, self.group_id)
if not is_festival and (amount < 1 or user_gold < amount):
raise ValueError("红包金币不足或用户金币不足")
red_bag_list = self._random_red_bag(amount, num)
if not is_festival:
await BagUser.spend_gold(promoter_id, self.group_id, amount)
await RedbagUser.add_redbag_data(promoter_id, self.group_id, "send", amount)
self._data[promoter_id] = RedBag(
group_id=self.group_id,
name=name,
amount=amount,
num=num,
promoter=promoter,
promoter_id=promoter_id,
is_festival=is_festival,
timeout=timeout,
start_time=time.time(),
assigner=assigner,
red_bag_list=red_bag_list,
)
def _random_red_bag(self, amount: int, num: int) -> List[int]:
"""初始化红包金币
参数:
amount: 金币数量
num: 红包数量
返回:
List[int]: 红包列表
"""
red_bag_list = []
for _ in range(num - 1):
tmp = int(amount / random.choice(range(3, num + 3)))
red_bag_list.append(tmp)
amount -= tmp
red_bag_list.append(amount)
return red_bag_list

View File

@ -1,19 +1,51 @@
import asyncio
import os import os
import random import random
from io import BytesIO from io import BytesIO
from typing import Union from typing import Tuple, Union
from nonebot.adapters.onebot.v11 import Bot
from configs.config import NICKNAME, Config
from configs.path_config import IMAGE_PATH from configs.path_config import IMAGE_PATH
from models.bag_user import BagUser from models.bag_user import BagUser
from utils.image_utils import BuildImage from utils.image_utils import BuildImage
from utils.message_builder import image
from utils.utils import get_user_avatar, is_number from utils.utils import get_user_avatar, is_number
from .model import RedbagUser from .config import FESTIVE_KEY, GroupRedBag, RedBag
# 检查金币数量合法性,并添加记录数据 async def end_festive_red_bag(bot: Bot, group_red_bag: GroupRedBag):
async def check_gold(user_id: str, group_id: str, amount: Union[str, int]): """结算节日红包
参数:
bot: Bot
group_red_bag: GroupRedBag
"""
if festive_red_bag := group_red_bag.festive_red_bag_expire():
rank_num = Config.get_config("gold_redbag", "RANK_NUM") or 10
rank_image = await festive_red_bag.build_amount_rank(rank_num)
message = (
f"{NICKNAME}的节日红包过时了,一共开启了 "
f"{len(festive_red_bag.open_user)}"
f" 个红包,共 {sum(festive_red_bag.open_user.values())} 金币\n" + image(rank_image)
)
await bot.send_group_msg(group_id=int(group_red_bag.group_id), message=message)
async def check_gold(
user_id: str, group_id: str, amount: Union[str, int]
) -> Tuple[bool, str]:
"""检查金币数量是否合法
参数:
user_id: 用户id
group_id: 群聊id
amount: 金币数量
返回:
Tuple[bool, str]: 是否合法以及提示语
"""
if is_number(amount): if is_number(amount):
amount = int(amount) amount = int(amount)
user_gold = await BagUser.get_gold(user_id, group_id) user_gold = await BagUser.get_gold(user_id, group_id)
@ -21,88 +53,102 @@ async def check_gold(user_id: str, group_id: str, amount: Union[str, int]):
return False, "小气鬼,要别人倒贴金币给你嘛!" return False, "小气鬼,要别人倒贴金币给你嘛!"
if user_gold < amount: if user_gold < amount:
return False, "没有金币的话请不要发红包..." return False, "没有金币的话请不要发红包..."
await BagUser.spend_gold(user_id, group_id, amount) return True, ""
await RedbagUser.add_redbag_data(user_id, group_id, "send", amount)
return True, amount
else: else:
return False, "给我好好的输入红包里金币的数量啊喂!" return False, "给我好好的输入红包里金币的数量啊喂!"
# 金币退回 async def random_red_bag_background(
async def return_gold(user_id: str, group_id: str, amount: int): user_id: Union[str, int], msg="恭喜发财 大吉大利"
await BagUser.add_gold(user_id, group_id, amount) ) -> BuildImage:
"""构造发送红包图片
参数:
user_id: 用户id
msg: 红包消息.
# 开红包 异常:
async def open_redbag(user_id: str, group_id: str, redbag_data: dict): ValueError: 图片背景列表为空
amount = random.choice(redbag_data[group_id]["redbag"])
redbag_data[group_id]["redbag"].remove(amount)
redbag_data[group_id]["open_user"].append(user_id)
redbag_data[group_id]["open_amount"] += amount
await RedbagUser.add_redbag_data(user_id, group_id, "get", amount)
await BagUser.add_gold(user_id, group_id, amount)
return amount, redbag_data
返回:
# 随机红包图片 BuildImage: 构造后的图片
async def generate_send_redbag_pic(user_id: int, msg: str = "恭喜发财 大吉大利"): """
random_redbag = random.choice(os.listdir(f"{IMAGE_PATH}/prts/redbag_2")) background_list = os.listdir(f"{IMAGE_PATH}/prts/redbag_2")
if not background_list:
raise ValueError("prts/redbag_1 背景图列表为空...")
random_redbag = random.choice(background_list)
redbag = BuildImage( redbag = BuildImage(
0, 0, font_size=38, background=f"{IMAGE_PATH}/prts/redbag_2/{random_redbag}" 0, 0, font_size=38, background=IMAGE_PATH / "prts" / "redbag_2" / random_redbag
) )
ava = BuildImage(65, 65, background=BytesIO(await get_user_avatar(user_id))) ava_byte = await get_user_avatar(user_id)
await asyncio.get_event_loop().run_in_executor(None, ava.circle) ava = None
redbag.text( if ava_byte:
ava = BuildImage(65, 65, background=BytesIO(ava_byte))
else:
ava = BuildImage(65, 65, color=(0, 0, 0), is_alpha=True)
await ava.acircle()
await redbag.atext(
(int((redbag.size[0] - redbag.getsize(msg)[0]) / 2), 210), msg, (240, 218, 164) (int((redbag.size[0] - redbag.getsize(msg)[0]) / 2), 210), msg, (240, 218, 164)
) )
redbag.paste(ava, (int((redbag.size[0] - ava.size[0]) / 2), 130), True) await redbag.apaste(ava, (int((redbag.size[0] - ava.size[0]) / 2), 130), True)
return redbag.pic2bs4() return redbag
# 开红包图片 async def build_open_result_image(
async def generate_open_redbag_pic( red_bag: RedBag, user_id: Union[int, str], amount: int
user_id: str, send_user_nickname: str, amount: int, text: str ) -> BuildImage:
): """构造红包开启图片
return await asyncio.create_task(
_generate_open_redbag_pic(user_id, send_user_nickname, amount, text)
)
参数:
red_bag: RedBag
user_id: 开启红包用户id
amount: 开启红包获取的金额
# 开红包图片 异常:
async def _generate_open_redbag_pic( ValueError: 图片背景列表为空
user_id: str, send_user_nickname: str, amount: int, text: str
): 返回:
send_user_nickname += "的红包" BuildImage: 构造后的图片
amount_str = str(amount) """
random_redbag = random.choice(os.listdir(f"{IMAGE_PATH}/prts/redbag_1")) background_list = os.listdir(f"{IMAGE_PATH}/prts/redbag_1")
if not background_list:
raise ValueError("prts/redbag_1 背景图列表为空...")
random_redbag = random.choice(background_list)
head = BuildImage( head = BuildImage(
1000, 1000,
980, 980,
font_size=30, font_size=30,
background=f"{IMAGE_PATH}/prts/redbag_1/{random_redbag}", background=IMAGE_PATH / "prts" / "redbag_1" / random_redbag,
) )
size = BuildImage(0, 0, font_size=50).getsize(send_user_nickname) size = BuildImage(0, 0, font_size=50).getsize(red_bag.name)
# QQ头像
ava_bk = BuildImage(100 + size[0], 66, is_alpha=True, font_size=50) 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_byte = await get_user_avatar(user_id)
) ava = None
ava_bk.paste(ava) if ava_byte:
ava_bk.text((100, 7), send_user_nickname) ava = BuildImage(66, 66, is_alpha=True, background=BytesIO(ava_byte))
# ava_bk.show() else:
ava = BuildImage(66, 66, color=(0, 0, 0), is_alpha=True)
await ava_bk.apaste(ava)
ava_bk.text((100, 7), red_bag.name)
ava_bk_w, ava_bk_h = ava_bk.size ava_bk_w, ava_bk_h = ava_bk.size
head.paste(ava_bk, (int((1000 - ava_bk_w) / 2), 300), alpha=True) await head.apaste(ava_bk, (int((1000 - ava_bk_w) / 2), 300), alpha=True)
# 金额 size = BuildImage(0, 0, font_size=150).getsize(amount)
size = BuildImage(0, 0, font_size=150).getsize(amount_str) amount_image = BuildImage(size[0], size[1], is_alpha=True, font_size=150)
price = BuildImage(size[0], size[1], is_alpha=True, font_size=150) await amount_image.atext((0, 0), str(amount), fill=(209, 171, 108))
price.text((0, 0), amount_str, fill=(209, 171, 108))
# 金币中文 # 金币中文
head.paste(price, (int((1000 - size[0]) / 2) - 50, 460), alpha=True) await head.apaste(amount_image, (int((1000 - size[0]) / 2) - 50, 460), alpha=True)
head.text( await head.atext(
(int((1000 - size[0]) / 2 + size[0]) - 50, 500 + size[1] - 70), (int((1000 - size[0]) / 2 + size[0]) - 50, 500 + size[1] - 70),
"金币", "金币",
fill=(209, 171, 108), fill=(209, 171, 108),
) )
# 剩余数量和金额 # 剩余数量和金额
head.text((350, 900), text, (198, 198, 198)) text = (
return head.pic2bs4() f"已领取"
f"{red_bag.num - len(red_bag.open_user)}"
f"/{red_bag.num}个,"
f"{sum(red_bag.open_user.values())}/{red_bag.amount}金币"
)
await head.atext((350, 900), text, (198, 198, 198))
return head

View File

@ -1,3 +1,5 @@
from datetime import datetime
from tortoise import fields from tortoise import fields
from services.db_context import Model from services.db_context import Model
@ -39,7 +41,7 @@ class OpenCasesUser(Model):
"""赚取金币""" """赚取金币"""
today_open_total: int = fields.IntField(default=0) today_open_total: int = fields.IntField(default=0)
"""今日开箱数量""" """今日开箱数量"""
open_cases_time_last = fields.DatetimeField() open_cases_time_last: datetime = fields.DatetimeField()
"""最后开箱日期""" """最后开箱日期"""
knifes_name: str = fields.TextField(default="") knifes_name: str = fields.TextField(default="")
"""已获取金色""" """已获取金色"""

View File

@ -122,7 +122,10 @@ async def open_case(user_id: str, group_id: int, case_name: str) -> Union[str, M
case_price = case_skin.sell_min_price case_price = case_skin.sell_min_price
user.today_open_total += 1 user.today_open_total += 1
user.total_count += 1 user.total_count += 1
await user.save(update_fields=["today_open_total", "total_count"]) user.open_cases_time_last = datetime.now()
await user.save(
update_fields=["today_open_total", "total_count", "open_cases_time_last"]
)
add_count(user, skin, case_price) add_count(user, skin, case_price)
ridicule_result = random.choice(RESULT_MESSAGE[skin.color]) ridicule_result = random.choice(RESULT_MESSAGE[skin.color])
price_result = skin.sell_min_price price_result = skin.sell_min_price
@ -203,7 +206,10 @@ async def open_multiple_case(
now = datetime.now() now = datetime.now()
user.today_open_total += num user.today_open_total += num
user.total_count += num user.total_count += num
await user.save(update_fields=["today_open_total", "total_count"]) user.open_cases_time_last = datetime.now()
await user.save(
update_fields=["today_open_total", "total_count", "open_cases_time_last"]
)
case_price = 0 case_price = 0
if case_skin := await BuffSkin.get_or_none(case_name=case_name, color="CASE"): if case_skin := await BuffSkin.get_or_none(case_name=case_name, color="CASE"):
case_price = case_skin.sell_min_price case_price = case_skin.sell_min_price
@ -275,8 +281,12 @@ def _handle_is_MAX_COUNT() -> str:
return f"今天已达开箱上限了喔,明天再来吧\n(提升好感度可以增加每日开箱数 #疯狂暗示)" return f"今天已达开箱上限了喔,明天再来吧\n(提升好感度可以增加每日开箱数 #疯狂暗示)"
async def total_open_statistics(user_id: str, group: str) -> str: async def total_open_statistics(
user, _ = await OpenCasesUser.get_or_create(user_id=user_id, group_id=group) user_id: Union[str, int], group_id: Union[str, int]
) -> str:
user, _ = await OpenCasesUser.get_or_create(
user_id=str(user_id), group_id=str(group_id)
)
return ( return (
f"开箱总数:{user.total_count}\n" f"开箱总数:{user.total_count}\n"
f"今日开箱:{user.today_open_total}\n" f"今日开箱:{user.today_open_total}\n"
@ -296,8 +306,8 @@ async def total_open_statistics(user_id: str, group: str) -> str:
) )
async def group_statistics(group: str): async def group_statistics(group_id: Union[int, str]):
user_list = await OpenCasesUser.filter(group_id=group).all() user_list = await OpenCasesUser.filter(group_id=str(group_id)).all()
# lan zi fen hong jin pricei # lan zi fen hong jin pricei
uplist = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0] uplist = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0]
for user in user_list: for user in user_list:
@ -332,7 +342,9 @@ async def group_statistics(group: str):
) )
async def get_my_knifes(user_id: str, group_id: str) -> Union[str, MessageSegment]: async def get_my_knifes(
user_id: Union[str, int], group_id: Union[str, int]
) -> Union[str, MessageSegment]:
"""获取我的金色 """获取我的金色
Args: Args:
@ -342,7 +354,7 @@ async def get_my_knifes(user_id: str, group_id: str) -> Union[str, MessageSegmen
Returns: Returns:
Union[str, MessageSegment]: 回复消息或图片 Union[str, MessageSegment]: 回复消息或图片
""" """
data_list = await get_old_knife(user_id, group_id) data_list = await get_old_knife(str(user_id), str(group_id))
data_list += await OpenCasesLog.filter( data_list += await OpenCasesLog.filter(
user_id=user_id, group_id=group_id, color="KNIFE" user_id=user_id, group_id=group_id, color="KNIFE"
).all() ).all()

View File

@ -48,7 +48,11 @@ async def init():
if not i_bind: if not i_bind:
i_bind = f"{sql_name}://{user}:{password}@{address}:{port}/{database}" i_bind = f"{sql_name}://{user}:{password}@{address}:{port}/{database}"
try: try:
await Tortoise.init(db_url=i_bind, modules={"models": MODELS}) await Tortoise.init(
db_url=i_bind,
modules={"models": MODELS},
# timezone="Asia/Shanghai"
)
await Tortoise.generate_schemas() await Tortoise.generate_schemas()
logger.info(f"Database loaded successfully!") logger.info(f"Database loaded successfully!")
except Exception as e: except Exception as e:

View File

@ -411,7 +411,7 @@ def is_chinese(word: str) -> bool:
return True return True
async def get_user_avatar(qq: int) -> Optional[bytes]: async def get_user_avatar(qq: Union[int, str]) -> Optional[bytes]:
""" """
说明: 说明:
快捷获取用户头像 快捷获取用户头像
@ -423,8 +423,8 @@ async def get_user_avatar(qq: int) -> Optional[bytes]:
for _ in range(3): for _ in range(3):
try: try:
return (await client.get(url)).content return (await client.get(url)).content
except TimeoutError: except Exception as e:
pass logger.error("获取用户头像错误", "Util", target=qq)
return None return None
@ -440,8 +440,8 @@ async def get_group_avatar(group_id: int) -> Optional[bytes]:
for _ in range(3): for _ in range(3):
try: try:
return (await client.get(url)).content return (await client.get(url)).content
except TimeoutError: except Exception as e:
pass logger.error("获取群头像错误", "Util", target=group_id)
return None return None