mirror of
https://github.com/xuthus83/LittlePaimon.git
synced 2024-10-21 16:27:15 +08:00
✨ 添加原神语音合成
冷却配置项,优化代码
This commit is contained in:
parent
93e6097de5
commit
23a953190e
@ -1 +0,0 @@
|
||||
from .plugin import *
|
@ -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()
|
||||
|
@ -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'
|
||||
|
22
LittlePaimon/manager/database_manager/__init__.py
Normal file
22
LittlePaimon/manager/database_manager/__init__.py
Normal 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()
|
@ -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())
|
||||
|
||||
|
||||
|
@ -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'
|
||||
]
|
||||
|
||||
|
@ -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语音合成冷却时间(秒)')
|
||||
|
@ -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"]}'))
|
||||
|
@ -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}语音资源,让超级用户[更新原神语音资源]吧!')
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
"""
|
||||
检查时间戳是否在指定天数内
|
||||
|
@ -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_
|
||||
|
Loading…
Reference in New Issue
Block a user