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

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 typing import List
from tortoise import fields from tortoise import fields
from tortoise.models import Model from tortoise.models import Model
from LittlePaimon.config.models import Statistics
class PluginPermission(Model): class PluginPermission(Model):
id = fields.IntField(pk=True, generated=True, auto_increment=True) id = fields.IntField(pk=True, generated=True, auto_increment=True)
@ -18,9 +17,29 @@ class PluginPermission(Model):
"""插件总开关""" """插件总开关"""
ban: List[int] = fields.JSONField(default=[]) 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: class Meta:
table = 'plugin_permission' 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 asyncio
import datetime
from nonebot import on_regex, on_command from nonebot import on_regex, on_command
from nonebot.matcher import Matcher from nonebot.matcher import Matcher
@ -12,8 +13,9 @@ from nonebot.typing import T_State
from LittlePaimon import SUPERUSERS, DRIVER from LittlePaimon import SUPERUSERS, DRIVER
from LittlePaimon.utils import logger from LittlePaimon.utils import logger
from LittlePaimon.utils.message import CommandObjectID 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 .manager import PluginManager, hidden_plugins
from .model import MatcherInfo
from .draw_help import draw_help from .draw_help import draw_help
plugin_manager = PluginManager() plugin_manager = PluginManager()
@ -126,11 +128,26 @@ async def _(event: MessageEvent, matcher: Matcher):
session_type = 'group' session_type = 'group'
else: else:
return 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: if not perm:
return return
if not perm.status: if not perm.status:
raise IgnoredException('插件使用权限已禁用') raise IgnoredException('插件使用权限已禁用')
if isinstance(event, GroupMessageEvent) and event.user_id in perm.ban: if isinstance(event, GroupMessageEvent) and event.user_id in perm.ban:
raise IgnoredException('用户被禁止使用该插件') 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 = [ hidden_plugins = [
'LittlePaimon', 'LittlePaimon',
'config',
'nonebot_plugin_apscheduler', 'nonebot_plugin_apscheduler',
'nonebot_plugin_gocqhttp', 'nonebot_plugin_gocqhttp',
'nonebot_plugin_htmlrender', 'nonebot_plugin_htmlrender',
'nonebot_plugin_imageutils',
'plugin_manager', 'plugin_manager',
'database_manager',
'admin' 'admin'
] ]

View File

@ -57,3 +57,5 @@ class Config(BaseModel):
ssbq_begin: int = Field(0, alias='实时便签停止检查开始时间') ssbq_begin: int = Field(0, alias='实时便签停止检查开始时间')
ssbq_end: int = Field(6, alias='实时便签停止检查结束时间') ssbq_end: int = Field(6, alias='实时便签停止检查结束时间')
ssbq_check: int = Field(16, 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.plugin import PluginMetadata
from nonebot.params import RegexDict from nonebot.params import RegexDict
from nonebot.adapters.onebot.v11 import GroupMessageEvent, PrivateMessageEvent, MessageSegment 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( __plugin_meta__ = PluginMetadata(
name='原神语音合成', name='原神语音合成',
@ -16,26 +18,30 @@ __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_name': '原神语音合成',
'pm_description': 'AI语音合成让原神角色说任何话', 'pm_description': 'AI语音合成让原神角色说任何话',
'pm_usage': '<角色名>说<话>', 'pm_usage': '<角色名>说<话>',
'pm_priority': 10 'pm_priority': 10
}) })
@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()): 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']: if not regex_dict['chara']:
regex_dict['chara'] = '派蒙' regex_dict['chara'] = '派蒙'
elif regex_dict['chara'] not in ['派蒙', '凯亚', '安柏', '丽莎', '', '香菱', '枫原万叶', '迪卢克', '温迪', '可莉', '早柚', '托马', '芭芭拉', 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)
'诺艾尔', '迪奥娜', '鹿野院平藏']:
return
elif len(regex_dict['text']) > 20:
return
await voice_cmd.finish(MessageSegment.record( await voice_cmd.finish(MessageSegment.record(
f'http://233366.proxy.nscc-gz.cn:8888/?text={regex_dict["text"]}&speaker={regex_dict["chara"]}')) 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, records = await GuessVoiceRank.filter(group_id=group_id,
guess_time__gte=datetime.datetime.now() - datetime.timedelta(days=7)) guess_time__gte=datetime.datetime.now() - datetime.timedelta(days=7))
if not records: if not records:
return '暂无排行榜数据' return '本群本周暂无排行榜数据哦!'
rank = {} rank = {}
for record in records: for record in records:
if record.user_id in rank: if record.user_id in rank:
rank[record.user_id] += 1 rank[record.user_id] += 1
else: else:
rank[record.user_id] = 1 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): 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' msg += f'{i}.{user_id}: {count}\n'
return msg return msg
@ -116,7 +116,4 @@ async def get_character_voice(character: str, language: str = '中'):
async def get_voice_list(character: str, language: str = ''): async def get_voice_list(character: str, language: str = ''):
voice_list = await GenshinVoice.filter(character=character, language=language).all() voice_list = await GenshinVoice.filter(character=character, language=language).all()
if not voice_list: return await draw_voice_list(voice_list) if voice_list else MessageSegment.text(f'暂无{character}{language}语音资源,让超级用户[更新原神语音资源]吧!')
return MessageSegment.text(f'暂无{character}{language}语音资源,让超级用户[更新原神语音资源]吧!')
else:
return await draw_voice_list(voice_list)

View File

@ -133,9 +133,3 @@ async def check_note():
# 等待一会再检查下一个,防止检查过快 # 等待一会再检查下一个,防止检查过快
await asyncio.sleep(random.randint(4, 8)) await asyncio.sleep(random.randint(4, 8))
logger.info('原神实时便签', f'树脂检查完成,共花费<m>{round((time.time() - t) / 60, 2)}</m>分钟') 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 import pytz
from LittlePaimon.config import JSON_DATA from LittlePaimon.config import JSON_DATA
from LittlePaimon.database.models import PlayerInfo, Character, LastQuery, PrivateCookie, PublicCookie, CookieCache, \ from LittlePaimon.database.models import PlayerInfo, Character, LastQuery, PrivateCookie, AbyssInfo
AbyssInfo
from LittlePaimon.database.models import Artifact, CharacterProperty, Artifacts, Talents, Talent 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.files import load_json
from LittlePaimon.utils.api import get_enka_data, get_mihoyo_public_data, get_mihoyo_private_data from LittlePaimon.utils.api import get_enka_data, get_mihoyo_public_data, get_mihoyo_private_data
from LittlePaimon.utils.typing import DataSourceType from LittlePaimon.utils.typing import DataSourceType
@ -394,10 +393,3 @@ class GenshinTools:
if '防御力' in effective and '防御力' in prop_name: if '防御力' in effective and '防御力' in prop_name:
return True return True
return prop_name in effective 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 PIL import Image
from nonebot import get_bot from nonebot import get_bot
from nonebot.adapters.onebot.v11 import MessageEvent, Message, MessageSegment, GroupMessageEvent from nonebot.adapters.onebot.v11 import MessageEvent, Message, MessageSegment, GroupMessageEvent
from nonebot.internal.params import Arg
from nonebot.matcher import Matcher from nonebot.matcher import Matcher
from nonebot.params import CommandArg, Depends from nonebot.params import CommandArg, Depends
from nonebot.typing import T_State 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.alias import get_match_alias
from LittlePaimon.utils.image import PMImage from LittlePaimon.utils.image import PMImage
from LittlePaimon.utils.filter import filter_msg 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: class MessageBuild:
@ -60,48 +60,6 @@ class MessageBuild:
img.save(bio, format='JPEG' if img.mode == 'RGB' else 'PNG', quality=quality) img.save(bio, format='JPEG' if img.mode == 'RGB' else 'PNG', quality=quality)
return MessageSegment.image(bio) 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 @classmethod
def Text(cls, text: str) -> MessageSegment: def Text(cls, text: str) -> MessageSegment:
""" """
@ -134,21 +92,6 @@ class MessageBuild:
def Video(cls, path: str) -> MessageSegment: def Video(cls, path: str) -> MessageSegment:
return MessageSegment.video(path) 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]: 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 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): def check_time(time_stamp: float, days: int = 1):
""" """
检查时间戳是否在指定天数内 检查时间戳是否在指定天数内

View File

@ -1,6 +1,6 @@
from typing import Union, Tuple, Set 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 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) 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): def on_regex_(pattern: str, state: dict = None, *args, **kwargs):
if state is None: if state is None:
state = {} 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) return on_keyword(keywords=keywords, state=state, _depth=1, *args, **kwargs)
nonebot.on_command = on_command_ nonebot.on_command = on_command_
nonebot.on_regex = on_regex_ nonebot.on_regex = on_regex_
nonebot.on_startswith = on_startswith_
nonebot.on_endswith = on_endswith_ nonebot.on_endswith = on_endswith_
nonebot.on_keyword = on_keyword_ nonebot.on_keyword = on_keyword_