添加原神语音合成冷却配置项,优化代码

This commit is contained in:
CMHopeSunshine 2022-08-31 17:45:18 +08:00
parent 93e6097de5
commit 23a953190e
13 changed files with 110 additions and 155 deletions

View File

@ -1 +0,0 @@
from .plugin import *

View File

@ -1,37 +0,0 @@
from typing import Literal, DefaultDict
from collections import defaultdict
from pydantic import BaseModel
class Statistics(BaseModel):
"""
插件调用统计
"""
month: DefaultDict[int, int] = defaultdict(lambda: 0)
"""月统计"""
week: DefaultDict[int, int] = defaultdict(lambda: 0)
"""周统计"""
day: DefaultDict[int, int] = defaultdict(lambda: 0)
"""日统计"""
def add(self, user_id: int):
"""
增加统计数据
:param user_id: 用户id
"""
self.day[user_id] += 1
self.week[user_id] += 1
self.month[user_id] += 1
def clear(self, type: Literal['month', 'week', 'day']):
"""
清除统计数据
:param type: 统计类型
"""
if type == 'month':
self.month.clear()
elif type == 'week':
self.week.clear()
elif type == 'day':
self.day.clear()

View File

@ -1,10 +1,9 @@
import datetime
from typing import List
from tortoise import fields
from tortoise.models import Model
from LittlePaimon.config.models import Statistics
class PluginPermission(Model):
id = fields.IntField(pk=True, generated=True, auto_increment=True)
@ -18,9 +17,29 @@ class PluginPermission(Model):
"""插件总开关"""
ban: List[int] = fields.JSONField(default=[])
"""插件屏蔽列表"""
statistics: Statistics = fields.JSONField(encoder=Statistics.json, decoder=Statistics.parse_raw, default=Statistics())
"""插件调用统计"""
statistics: dict = fields.JSONField(default=dict)
"""插件调用统计,废弃选项,不再使用"""
class Meta:
table = 'plugin_permission'
class PluginStatistics(Model):
id = fields.IntField(pk=True, generated=True, auto_increment=True)
plugin_name: str = fields.CharField(max_length=255)
"""插件名称"""
matcher_name: str = fields.CharField(max_length=255)
"""命令名称"""
matcher_usage: str = fields.CharField(max_length=255, null=True)
"""命令用法"""
group_id: int = fields.IntField(null=True)
"""群id"""
user_id: int = fields.IntField()
"""用户id"""
message_type: str = fields.CharField(max_length=10)
"""消息类型group/user"""
time: datetime.datetime = fields.DatetimeField()
"""调用时间"""
class Meta:
table = 'plugin_statistics'

View File

@ -0,0 +1,22 @@
import datetime
from LittlePaimon.utils import scheduler, logger
from LittlePaimon.database.models import GuessVoiceRank, PluginStatistics, DailyNoteSub, CookieCache, PublicCookie
@scheduler.scheduled_job('cron', hour=0, minute=0, misfire_grace_time=10)
async def _():
now = datetime.datetime.now()
logger.info('原神实时便签', '重置每日提醒次数限制')
await DailyNoteSub.all().update(today_remind_num=0)
logger.info('原神Cookie', '清空每日Cookie缓存和限制')
await CookieCache.all().delete()
await PublicCookie.filter(status=2).update(status=1)
logger.info('功能调用统计', '清除超过一个月的统计数据')
await PluginStatistics.filter(time__lt=now - datetime.timedelta(days=30)).delete()
if now.weekday() == 0:
logger.info('原神猜语音', '清空每周排行榜')
await GuessVoiceRank.all().delete()

View File

@ -1,4 +1,5 @@
import asyncio
import datetime
from nonebot import on_regex, on_command
from nonebot.matcher import Matcher
@ -12,8 +13,9 @@ from nonebot.typing import T_State
from LittlePaimon import SUPERUSERS, DRIVER
from LittlePaimon.utils import logger
from LittlePaimon.utils.message import CommandObjectID
from LittlePaimon.database.models import PluginPermission
from LittlePaimon.database.models import PluginPermission, PluginStatistics
from .manager import PluginManager, hidden_plugins
from .model import MatcherInfo
from .draw_help import draw_help
plugin_manager = PluginManager()
@ -126,11 +128,26 @@ async def _(event: MessageEvent, matcher: Matcher):
session_type = 'group'
else:
return
perm = await PluginPermission.get_or_none(name=matcher.plugin_name, session_id=session_id, session_type=session_type)
# 权限检查
perm = await PluginPermission.get_or_none(name=matcher.plugin_name, session_id=session_id, session_type=session_type)
if not perm:
return
if not perm.status:
raise IgnoredException('插件使用权限已禁用')
if isinstance(event, GroupMessageEvent) and event.user_id in perm.ban:
raise IgnoredException('用户被禁止使用该插件')
# 命令调用统计
if matcher.plugin_name in plugin_manager.data and 'pm_name' in matcher.state:
if matcher_info := filter(lambda x: x.pm_name == matcher.state['pm_name'], plugin_manager.data[matcher.plugin_name].matchers):
matcher_info = list(matcher_info)[0]
await PluginStatistics.create(plugin_name=matcher.plugin_name,
matcher_name=matcher_info.pm_name,
matcher_usage=matcher_info.pm_usage,
group_id=event.group_id if isinstance(event, GroupMessageEvent) else None,
user_id=event.user_id,
message_type=session_type,
time=datetime.datetime.now())

View File

@ -11,10 +11,13 @@ from .model import MatcherInfo, PluginInfo, Config
hidden_plugins = [
'LittlePaimon',
'config',
'nonebot_plugin_apscheduler',
'nonebot_plugin_gocqhttp',
'nonebot_plugin_htmlrender',
'nonebot_plugin_imageutils',
'plugin_manager',
'database_manager',
'admin'
]

View File

@ -57,3 +57,5 @@ class Config(BaseModel):
ssbq_begin: int = Field(0, alias='实时便签停止检查开始时间')
ssbq_end: int = Field(6, alias='实时便签停止检查结束时间')
ssbq_check: int = Field(16, alias='实时便签检查间隔')
AI_voice_cooldown: int = Field(10, alias='原神AI语音合成冷却时间(秒)')

View File

@ -3,7 +3,9 @@ from nonebot import on_regex
from nonebot.plugin import PluginMetadata
from nonebot.params import RegexDict
from nonebot.adapters.onebot.v11 import GroupMessageEvent, PrivateMessageEvent, MessageSegment
from nonebot.adapters.onebot.v11.helpers import Cooldown, CooldownIsolateLevel
from LittlePaimon.utils.tool import freq_limiter
from LittlePaimon.utils.filter import filter_msg
from LittlePaimon.manager.plugin_manager import plugin_manager as pm
__plugin_meta__ = PluginMetadata(
name='原神语音合成',
@ -16,7 +18,16 @@ __plugin_meta__ = PluginMetadata(
}
)
voice_cmd = on_regex(r'(?P<chara>\w*)说(?P<text>[\w“”——!\?,\.`\'"\(\)\[\]{}~\s]+)', priority=20, block=True, state={
SUPPORTS_CHARA = ['派蒙', '凯亚', '安柏', '丽莎', '', '香菱', '枫原万叶', '迪卢克', '温迪', '可莉', '早柚', '托马', '芭芭拉',
'优菈', '云堇', '钟离', '', '凝光', '雷电将军', '北斗', '甘雨', '七七', '刻晴', '神里绫华', '戴因斯雷布', '雷泽',
'神里绫人', '罗莎莉亚', '阿贝多', '八重神子', '宵宫', '荒泷一斗', '九条裟罗', '夜兰', '珊瑚宫心海', '五郎', '散兵',
'女士', '达达利亚', '莫娜', '班尼特', '申鹤', '行秋', '烟绯', '久岐忍', '辛焱', '砂糖', '胡桃', '重云', '菲谢尔',
'诺艾尔', '迪奥娜', '鹿野院平藏']
CHARA_RE = '|'.join(SUPPORTS_CHARA)
voice_cmd = on_regex(rf'(?P<chara>({CHARA_RE})?)说(?P<text>[\w“”——!\?,\.`\'"\(\)\[\]{{}}~\s]+)', priority=90, block=True,
state={
'pm_name': '原神语音合成',
'pm_description': 'AI语音合成让原神角色说任何话',
'pm_usage': '<角色名>说<话>',
@ -24,18 +35,13 @@ voice_cmd = on_regex(r'(?P<chara>\w*)说(?P<text>[\w“”
})
@voice_cmd.handle(parameterless=[Cooldown(cooldown=6, isolate_level=CooldownIsolateLevel.GROUP, prompt='冷却中...')])
@voice_cmd.handle()
async def _(event: Union[GroupMessageEvent, PrivateMessageEvent], regex_dict: dict = RegexDict()):
regex_dict['text'] = regex_dict['text'].replace('\r', '').replace('\n', '')
if not regex_dict['chara']:
regex_dict['chara'] = '派蒙'
elif regex_dict['chara'] not in ['派蒙', '凯亚', '安柏', '丽莎', '', '香菱', '枫原万叶', '迪卢克', '温迪', '可莉', '早柚', '托马', '芭芭拉',
'优菈', '云堇', '钟离', '', '凝光', '雷电将军', '北斗', '甘雨', '七七', '刻晴', '神里绫华', '戴因斯雷布', '雷泽',
'神里绫人', '罗莎莉亚', '阿贝多', '八重神子', '宵宫', '荒泷一斗', '九条裟罗', '夜兰', '珊瑚宫心海', '五郎', '散兵',
'女士', '达达利亚', '莫娜', '班尼特', '申鹤', '行秋', '烟绯', '久岐忍', '辛焱', '砂糖', '胡桃', '重云', '菲谢尔',
'诺艾尔', '迪奥娜', '鹿野院平藏']:
return
elif len(regex_dict['text']) > 20:
return
regex_dict['text'] = filter_msg(regex_dict['text'].replace('\r', '').replace('\n', ''))
if not freq_limiter.check(f'genshin_ai_voice_{event.group_id if isinstance(event, GroupMessageEvent) else event.user_id}'):
await voice_cmd.finish(f'原神语音合成冷却中...剩余{freq_limiter.left(f"genshin_ai_voice_{event.group_id if isinstance(event, GroupMessageEvent) else event.user_id}")}')
freq_limiter.start(f'genshin_ai_voice_{event.group_id if isinstance(event, GroupMessageEvent) else event.user_id}', pm.config.AI_voice_cooldown)
await voice_cmd.finish(MessageSegment.record(
f'http://233366.proxy.nscc-gz.cn:8888/?text={regex_dict["text"]}&speaker={regex_dict["chara"]}'))

View File

@ -63,14 +63,14 @@ async def get_rank(group_id: int):
records = await GuessVoiceRank.filter(group_id=group_id,
guess_time__gte=datetime.datetime.now() - datetime.timedelta(days=7))
if not records:
return '暂无排行榜数据'
return '本群本周暂无排行榜数据哦!'
rank = {}
for record in records:
if record.user_id in rank:
rank[record.user_id] += 1
else:
rank[record.user_id] = 1
msg = '猜语音排行榜\n'
msg = '本周猜语音排行榜\n'
for i, (user_id, count) in enumerate(sorted(rank.items(), key=lambda x: x[1], reverse=True), start=1):
msg += f'{i}.{user_id}: {count}\n'
return msg
@ -116,7 +116,4 @@ async def get_character_voice(character: str, language: str = '中'):
async def get_voice_list(character: str, language: str = ''):
voice_list = await GenshinVoice.filter(character=character, language=language).all()
if not voice_list:
return MessageSegment.text(f'暂无{character}{language}语音资源,让超级用户[更新原神语音资源]吧!')
else:
return await draw_voice_list(voice_list)
return await draw_voice_list(voice_list) if voice_list else MessageSegment.text(f'暂无{character}{language}语音资源,让超级用户[更新原神语音资源]吧!')

View File

@ -133,9 +133,3 @@ async def check_note():
# 等待一会再检查下一个,防止检查过快
await asyncio.sleep(random.randint(4, 8))
logger.info('原神实时便签', f'树脂检查完成,共花费<m>{round((time.time() - t) / 60, 2)}</m>分钟')
@scheduler.scheduled_job('cron', hour=0, minute=0, misfire_grace_time=10)
async def _():
logger.info('原神实时便签', '清空每日提醒次数限制')
await DailyNoteSub.all().update(today_remind_num=0)

View File

@ -5,10 +5,9 @@ from typing import Optional, List, Union, Tuple
import pytz
from LittlePaimon.config import JSON_DATA
from LittlePaimon.database.models import PlayerInfo, Character, LastQuery, PrivateCookie, PublicCookie, CookieCache, \
AbyssInfo
from LittlePaimon.database.models import PlayerInfo, Character, LastQuery, PrivateCookie, AbyssInfo
from LittlePaimon.database.models import Artifact, CharacterProperty, Artifacts, Talents, Talent
from LittlePaimon.utils import logger, scheduler
from LittlePaimon.utils import logger
from LittlePaimon.utils.files import load_json
from LittlePaimon.utils.api import get_enka_data, get_mihoyo_public_data, get_mihoyo_private_data
from LittlePaimon.utils.typing import DataSourceType
@ -394,10 +393,3 @@ class GenshinTools:
if '防御力' in effective and '防御力' in prop_name:
return True
return prop_name in effective
@scheduler.scheduled_job('cron', hour=0, minute=0, misfire_grace_time=10)
async def _():
logger.info('原神Cookie', '清空每日Cookie缓存和限制')
await CookieCache.all().delete()
await PublicCookie.filter(status=2).update(status=1)

View File

@ -8,7 +8,6 @@ from typing import Union, Optional, Tuple, List
from PIL import Image
from nonebot import get_bot
from nonebot.adapters.onebot.v11 import MessageEvent, Message, MessageSegment, GroupMessageEvent
from nonebot.internal.params import Arg
from nonebot.matcher import Matcher
from nonebot.params import CommandArg, Depends
from nonebot.typing import T_State
@ -18,7 +17,8 @@ from LittlePaimon.utils import aiorequests, load_image
from LittlePaimon.utils.alias import get_match_alias
from LittlePaimon.utils.image import PMImage
from LittlePaimon.utils.filter import filter_msg
from LittlePaimon.utils.typing import CHARACTERS, MALE_CHARACTERS, FEMALE_CHARACTERS, GIRL_CHARACTERS, BOY_CHARACTERS, LOLI_CHARACTERS
from LittlePaimon.utils.typing import CHARACTERS, MALE_CHARACTERS, FEMALE_CHARACTERS, GIRL_CHARACTERS, BOY_CHARACTERS, \
LOLI_CHARACTERS
class MessageBuild:
@ -60,48 +60,6 @@ class MessageBuild:
img.save(bio, format='JPEG' if img.mode == 'RGB' else 'PNG', quality=quality)
return MessageSegment.image(bio)
@classmethod
async def StaticImage(cls,
url: str,
size: Optional[Tuple[int, int]] = None,
crop: Optional[Tuple[int, int, int, int]] = None,
quality: Optional[int] = 100,
mode: Optional[str] = None,
tips: Optional[str] = None,
is_check_time: Optional[bool] = True,
check_time_day: Optional[int] = 3
):
"""
从url下载图片并预处理并构造成MessageSegment如果url的图片已存在本地则直接读取本地图片
:param url: 图片url
:param size: 预处理尺寸
:param crop: 预处理裁剪大小
:param quality: 预处理图片质量
:param mode: 预处理图像模式
:param tips: url中不存在该图片时的提示语
:param is_check_time: 是否检查本地图片最后修改时间
:param check_time_day: 检查本地图片最后修改时间的天数超过该天数则重新下载图片
:return: MessageSegment.image
"""
path = Path() / 'resources' / url
if path.exists() and (
not is_check_time or (is_check_time and not check_time(path.stat().st_mtime, check_time_day))):
img = Image.open(path)
else:
path.parent.mkdir(parents=True, exist_ok=True)
img = await aiorequests.get_img(url='https://static.cherishmoon.fun/' + url, save_path=path)
if img == 'No Such File':
return MessageBuild.Text(tips or '缺少该静态资源')
if size:
img = img.resize(size)
if crop:
img = img.crop(crop)
if mode:
img = img.convert(mode)
bio = BytesIO()
img.save(bio, format='JPEG' if img.mode == 'RGB' else 'PNG', quality=quality)
return MessageSegment.image(bio)
@classmethod
def Text(cls, text: str) -> MessageSegment:
"""
@ -134,21 +92,6 @@ class MessageBuild:
def Video(cls, path: str) -> MessageSegment:
return MessageSegment.video(path)
@classmethod
async def StaticVideo(cls, url: str) -> MessageSegment:
"""
从url中下载视频文件并构造成MessageSegment如果本地已有该视频文件则直接读取本地文件
:param url: 视频url
:return: MessageSegment.video
"""
path = Path() / 'data' / url
if not path.exists():
path.parent.mkdir(parents=True, exist_ok=True)
resp = await aiorequests.get(url=f'https://static.cherishmoon.fun/{url}')
content = resp.content
path.write_bytes(content)
return MessageSegment.video(file=path)
def CommandPlayer(limit: int = 3, only_cn: bool = True) -> List[Player]:
"""
@ -385,18 +328,6 @@ def replace_all(raw_text: str, text_list: Union[str, list]) -> str:
return raw_text
# def transform_uid(msg: Union[Message, str]) -> Union[List[str], str, None]:
# if isinstance(msg, Message):
# msg = msg.extract_plain_text().strip()
# check_uid = msg.split(' ')
# uid_list = []
# for check in check_uid:
# uid = re.search(r'(?P<uid>(1|2|5)\d{8})', check)
# if uid:
# uid_list.append(uid['uid'])
# return uid_list if len(uid_list) > 1 else uid_list[0] if uid_list else None
def check_time(time_stamp: float, days: int = 1):
"""
检查时间戳是否在指定天数内

View File

@ -1,6 +1,6 @@
from typing import Union, Tuple, Set
from nonebot import on_command, on_regex, on_endswith, on_keyword
from nonebot import on_command, on_regex, on_endswith, on_keyword, on_startswith
import nonebot
"""
@ -24,6 +24,14 @@ def on_endswith_(msg: Union[str, Tuple[str, ...]], state: dict = None, *args, **
return on_endswith(msg=msg, state=state, _depth=1, *args, **kwargs)
def on_startswith_(msg: Union[str, Tuple[str, ...]], state: dict = None, *args, **kwargs):
if state is None:
state = {}
if 'pm_name' not in state:
state['pm_name'] = msg if isinstance(msg, str) else msg[0]
return on_startswith(msg=msg, state=state, _depth=1, *args, **kwargs)
def on_regex_(pattern: str, state: dict = None, *args, **kwargs):
if state is None:
state = {}
@ -40,7 +48,9 @@ def on_keyword_(keywords: Set[str], state: dict = None, *args, **kwargs):
return on_keyword(keywords=keywords, state=state, _depth=1, *args, **kwargs)
nonebot.on_command = on_command_
nonebot.on_regex = on_regex_
nonebot.on_startswith = on_startswith_
nonebot.on_endswith = on_endswith_
nonebot.on_keyword = on_keyword_