mirror of
https://github.com/TriM-Organization/LiteyukiBot-TriM.git
synced 2025-09-10 14:16:24 +00:00
Ⓜ️手动从旧梦 81a191f merge
This commit is contained in:
13
src/nonebot_plugins/trimo_plugin_handle/LICENSE.md
Normal file
13
src/nonebot_plugins/trimo_plugin_handle/LICENSE.md
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
## 版权声明
|
||||
|
||||
本插件由 汉钰律许可协议 授权开源,兼容并继承自 MIT 许可协议。
|
||||
|
||||
Copyright (c) 2022 MeetWq
|
||||
|
||||
版权所有 © 2024 EillesWan & MeetWq
|
||||
|
||||
猜成语-睿乐特别版(trimo_plugin_handle)根据 第一版 汉钰律许可协议(“本协议”)授权。\
|
||||
任何人皆可从以下地址获得本协议副本:[汉钰律许可协议 第一版](https://gitee.com/EillesWan/YulvLicenses/raw/master/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE.MD)。\
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。\
|
||||
详细的准许和限制条款请见原协议文本。
|
274
src/nonebot_plugins/trimo_plugin_handle/__init__.py
Normal file
274
src/nonebot_plugins/trimo_plugin_handle/__init__.py
Normal file
@ -0,0 +1,274 @@
|
||||
import asyncio
|
||||
from asyncio import TimerHandle
|
||||
from typing import Any, Dict
|
||||
|
||||
import nonebot
|
||||
from nonebot import on_regex, require, on_command
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import RegexDict
|
||||
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.utils import run_sync
|
||||
from nonebot.permission import SUPERUSER
|
||||
from typing_extensions import Annotated
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_session")
|
||||
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
AlconnaQuery,
|
||||
Image,
|
||||
Option,
|
||||
Query,
|
||||
Text,
|
||||
UniMessage,
|
||||
on_alconna,
|
||||
store_true,
|
||||
Args,
|
||||
Arparma,
|
||||
)
|
||||
from nonebot_plugin_session import SessionId, SessionIdType
|
||||
|
||||
from .config import Config, handle_config
|
||||
from .data_source import GuessResult, Handle
|
||||
from .utils import random_idiom
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="猜成语",
|
||||
description="猜成语-睿乐特别版",
|
||||
usage=(
|
||||
"@我 + “猜成语”开始游戏;\n"
|
||||
"你有十次的机会猜一个四字词语;\n"
|
||||
"每次猜测后,汉字与拼音的颜色将会标识其与正确答案的区别;\n"
|
||||
"青色 表示其出现在答案中且在正确的位置;\n"
|
||||
"橙色 表示其出现在答案中但不在正确的位置;\n"
|
||||
"每个格子的 汉字、声母、韵母、声调 都会独立进行颜色的指示。\n"
|
||||
"当四个格子都为青色时,你便赢得了游戏!\n"
|
||||
"可发送“结束”结束游戏;可发送“提示”查看提示。\n"
|
||||
"使用 --strict 选项开启非默认的成语检查,即猜测的短语必须是成语,\n"
|
||||
"如:@我 猜成语 --strict"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/noneplugin/nonebot-plugin-handle",
|
||||
config=Config,
|
||||
supported_adapters=inherit_supported_adapters(
|
||||
"nonebot_plugin_alconna", "nonebot_plugin_session"
|
||||
),
|
||||
extra={
|
||||
"example": "@小Q 猜成语",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
games: Dict[str, Handle] = {}
|
||||
timers: Dict[str, TimerHandle] = {}
|
||||
|
||||
UserId = Annotated[str, SessionId(SessionIdType.GROUP)]
|
||||
|
||||
|
||||
def game_is_running(user_id: UserId) -> bool:
|
||||
return user_id in games
|
||||
|
||||
|
||||
def game_not_running(user_id: UserId) -> bool:
|
||||
return user_id not in games
|
||||
|
||||
|
||||
handle = on_alconna(
|
||||
Alconna(
|
||||
"handle",
|
||||
Option("-s|--strict", default=False, action=store_true),
|
||||
Option("-d|--hard", default=False, action=store_true),
|
||||
),
|
||||
aliases=("猜成语",),
|
||||
rule=to_me() & game_not_running,
|
||||
use_cmd_start=True,
|
||||
block=True,
|
||||
priority=13,
|
||||
)
|
||||
handle_hint = on_alconna(
|
||||
"提示",
|
||||
rule=game_is_running,
|
||||
use_cmd_start=True,
|
||||
block=True,
|
||||
priority=13,
|
||||
)
|
||||
handle_stop = on_alconna(
|
||||
"结束",
|
||||
aliases=("结束游戏", "结束猜成语"),
|
||||
rule=game_is_running,
|
||||
use_cmd_start=True,
|
||||
block=True,
|
||||
priority=13,
|
||||
)
|
||||
|
||||
handle_answer = on_alconna(
|
||||
Alconna(
|
||||
"答案",
|
||||
Option(
|
||||
"-g|--group",
|
||||
default="Now",
|
||||
args=Args["group", str, "Now"],
|
||||
),
|
||||
Option(
|
||||
"-l|--list",
|
||||
default=False,
|
||||
action=store_true,
|
||||
),
|
||||
),
|
||||
# rule=game_is_running,
|
||||
use_cmd_start=True,
|
||||
permission=SUPERUSER,
|
||||
block=True,
|
||||
priority=13,
|
||||
)
|
||||
|
||||
# handle_update = on_alconna(
|
||||
# "更新词库",
|
||||
# aliases=("刷新词库", "猜成语刷新词库"),
|
||||
# rule=to_me(),
|
||||
# use_cmd_start=True,
|
||||
# block=True,
|
||||
# priority=13,
|
||||
# )
|
||||
|
||||
|
||||
handle_idiom = on_regex(
|
||||
r"^(?P<idiom>[\u4e00-\u9fa5]{4})$",
|
||||
rule=game_is_running,
|
||||
block=True,
|
||||
priority=14,
|
||||
)
|
||||
|
||||
|
||||
def stop_game(user_id: str):
|
||||
if timer := timers.pop(user_id, None):
|
||||
timer.cancel()
|
||||
games.pop(user_id, None)
|
||||
|
||||
|
||||
async def stop_game_timeout(matcher: Matcher, user_id: str):
|
||||
game = games.get(user_id, None)
|
||||
stop_game(user_id)
|
||||
if game:
|
||||
msg = "猜成语超时,游戏结束。"
|
||||
if len(game.guessed_idiom) >= 1:
|
||||
msg += f"\n{game.result}"
|
||||
await matcher.finish(msg)
|
||||
|
||||
|
||||
def set_timeout(matcher: Matcher, user_id: str, timeout: float = 300):
|
||||
if timer := timers.get(user_id, None):
|
||||
timer.cancel()
|
||||
loop = asyncio.get_running_loop()
|
||||
timer = loop.call_later(
|
||||
timeout, lambda: asyncio.ensure_future(stop_game_timeout(matcher, user_id))
|
||||
)
|
||||
timers[user_id] = timer
|
||||
|
||||
|
||||
@handle.handle()
|
||||
async def _(
|
||||
result: Arparma,
|
||||
matcher: Matcher,
|
||||
user_id: UserId,
|
||||
):
|
||||
nonebot.logger.info(result.options)
|
||||
is_strict = handle_config.handle_strict_mode or result.options["strict"].value
|
||||
idiom, explanation = random_idiom(result.options["hard"].value)
|
||||
game = Handle(idiom, explanation, strict=is_strict)
|
||||
|
||||
games[user_id] = game
|
||||
set_timeout(matcher, user_id)
|
||||
|
||||
msg = Text(
|
||||
f"你有{game.times}次机会猜一个四字成语,"
|
||||
+ ("发送有效成语以参与游戏。" if is_strict else "发送任意四字词语以参与游戏。")
|
||||
) + Image(raw=await run_sync(game.draw)())
|
||||
await msg.send()
|
||||
|
||||
|
||||
@handle_hint.handle()
|
||||
async def _(matcher: Matcher, user_id: UserId):
|
||||
game = games[user_id]
|
||||
set_timeout(matcher, user_id)
|
||||
|
||||
await UniMessage.image(raw=await run_sync(game.draw_hint)()).send()
|
||||
|
||||
|
||||
@handle_stop.handle()
|
||||
async def _(matcher: Matcher, user_id: UserId):
|
||||
game = games[user_id]
|
||||
stop_game(user_id)
|
||||
|
||||
msg = "游戏已结束"
|
||||
if len(game.guessed_idiom) >= 1:
|
||||
msg += f"\n{game.result}"
|
||||
await matcher.finish(msg)
|
||||
|
||||
|
||||
# @handle_update.handle()
|
||||
|
||||
|
||||
@handle_idiom.handle()
|
||||
async def _(matcher: Matcher, user_id: UserId, matched: Dict[str, Any] = RegexDict()):
|
||||
game = games[user_id]
|
||||
set_timeout(matcher, user_id)
|
||||
|
||||
idiom = str(matched["idiom"])
|
||||
result = game.guess(idiom)
|
||||
|
||||
if result in [GuessResult.WIN, GuessResult.LOSS]:
|
||||
stop_game(user_id)
|
||||
msg = Text(
|
||||
(
|
||||
"恭喜你猜出了成语!"
|
||||
if result == GuessResult.WIN
|
||||
else "很遗憾,没有人猜出来呢"
|
||||
)
|
||||
+ f"\n{game.result}"
|
||||
) + Image(raw=await run_sync(game.draw)())
|
||||
await msg.send()
|
||||
|
||||
elif result == GuessResult.DUPLICATE:
|
||||
await matcher.finish("你已经猜过这个成语了呢")
|
||||
|
||||
elif result == GuessResult.ILLEGAL:
|
||||
await matcher.finish(f"你确定“{idiom}”是个成语吗?")
|
||||
|
||||
else:
|
||||
await UniMessage.image(raw=await run_sync(game.draw)()).send()
|
||||
|
||||
|
||||
@handle_answer.handle()
|
||||
async def _(
|
||||
result: Arparma,
|
||||
matcher: Matcher,
|
||||
user_id: UserId,
|
||||
):
|
||||
|
||||
if result.options["list"].value:
|
||||
await handle_answer.finish(
|
||||
UniMessage.text(
|
||||
"\n".join("{}-{}".format(i, j.idiom) for i, j in games.items())
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
if result.options["group"].args["group"] == "Now":
|
||||
session_numstr = user_id
|
||||
else:
|
||||
session_numstr = "qq_OneBot V11_2378756507_{}".format(
|
||||
result.options["group"].args["group"]
|
||||
)
|
||||
except:
|
||||
session_numstr = user_id
|
||||
|
||||
if session_numstr in games.keys():
|
||||
await handle_answer.finish(UniMessage.text(games[session_numstr].idiom))
|
||||
else:
|
||||
await handle_answer.finish(
|
||||
UniMessage.text("{} 不存在开局的游戏".format(session_numstr))
|
||||
)
|
10
src/nonebot_plugins/trimo_plugin_handle/config.py
Normal file
10
src/nonebot_plugins/trimo_plugin_handle/config.py
Normal file
@ -0,0 +1,10 @@
|
||||
from nonebot import get_plugin_config
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
handle_strict_mode: bool = False
|
||||
handle_color_enhance: bool = False
|
||||
|
||||
|
||||
handle_config = get_plugin_config(Config)
|
284
src/nonebot_plugins/trimo_plugin_handle/data_source.py
Normal file
284
src/nonebot_plugins/trimo_plugin_handle/data_source.py
Normal file
@ -0,0 +1,284 @@
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from io import BytesIO
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
from PIL.Image import Image as IMG
|
||||
|
||||
from .config import handle_config
|
||||
from .utils import get_pinyin, legal_idiom, load_font, save_jpg
|
||||
|
||||
|
||||
class GuessResult(Enum):
|
||||
WIN = 0 # 猜出正确成语
|
||||
LOSS = 1 # 达到最大可猜次数,未猜出正确成语
|
||||
DUPLICATE = 2 # 成语重复
|
||||
ILLEGAL = 3 # 成语不合法
|
||||
|
||||
|
||||
class GuessState(Enum):
|
||||
CORRECT = 0 # 存在且位置正确
|
||||
EXIST = 1 # 存在但位置不正确
|
||||
WRONG = 2 # 不存在
|
||||
|
||||
|
||||
@dataclass
|
||||
class ColorGroup:
|
||||
bg_color: str # 背景颜色
|
||||
block_color: str # 方块颜色
|
||||
correct_color: str # 存在且位置正确时的颜色
|
||||
exist_color: str # 存在但位置不正确时的颜色
|
||||
wrong_color_pinyin: str # 不存在时的颜色
|
||||
wrong_color_char: str # 不存在时的颜色
|
||||
|
||||
|
||||
NORMAL_COLOR = ColorGroup(
|
||||
"#ffffff", "#f7f8f9", "#1d9c9c", "#de7525", "#b4b8be", "#5d6673"
|
||||
)
|
||||
|
||||
ENHANCED_COLOR = ColorGroup(
|
||||
"#ffffff", "#f7f8f9", "#5ba554", "#ff46ff", "#b4b8be", "#5d6673"
|
||||
)
|
||||
|
||||
|
||||
class Handle:
|
||||
def __init__(self, idiom: str, explanation: str, strict: bool = False):
|
||||
self.idiom: str = idiom # 成语
|
||||
self.explanation: str = explanation # 释义
|
||||
self.strict: bool = strict # 是否判断输入词语为成语
|
||||
self.result = f"【成语】:{idiom}\n【释义】:{explanation}"
|
||||
self.pinyin: List[Tuple[str, str, str]] = get_pinyin(idiom) # 拼音
|
||||
self.length = 4
|
||||
self.times: int = 10 # 可猜次数
|
||||
self.guessed_idiom: List[str] = [] # 记录已猜成语
|
||||
self.guessed_pinyin: List[List[Tuple[str, str, str]]] = [] # 记录已猜成语的拼音
|
||||
|
||||
self.block_size = (160, 160) # 文字块尺寸
|
||||
self.block_padding = (20, 20) # 文字块之间间距
|
||||
self.padding = (40, 40) # 边界间距
|
||||
font_size_char = 60 # 汉字字体大小
|
||||
font_size_pinyin = 30 # 拼音字体大小
|
||||
font_size_tone = 22 # 声调字体大小
|
||||
self.font_char = load_font("NotoSerifSC-Regular.otf", font_size_char)
|
||||
self.font_pinyin = load_font("NotoSansMono-Regular.ttf", font_size_pinyin)
|
||||
self.font_tone = load_font("NotoSansMono-Regular.ttf", font_size_tone)
|
||||
|
||||
self.colors = (
|
||||
ENHANCED_COLOR if handle_config.handle_color_enhance else NORMAL_COLOR
|
||||
)
|
||||
|
||||
def guess(self, idiom: str) -> Optional[GuessResult]:
|
||||
if self.strict and not legal_idiom(idiom):
|
||||
return GuessResult.ILLEGAL
|
||||
if idiom in self.guessed_idiom:
|
||||
return GuessResult.DUPLICATE
|
||||
self.guessed_idiom.append(idiom)
|
||||
self.guessed_pinyin.append(get_pinyin(idiom))
|
||||
if idiom == self.idiom:
|
||||
return GuessResult.WIN
|
||||
if len(self.guessed_idiom) == self.times:
|
||||
return GuessResult.LOSS
|
||||
|
||||
def draw_block(
|
||||
self,
|
||||
block_color: str,
|
||||
char: str = "",
|
||||
char_color: str = "",
|
||||
initial: str = "",
|
||||
initial_color: str = "",
|
||||
final: str = "",
|
||||
final_color: str = "",
|
||||
tone: str = "",
|
||||
tone_color: str = "",
|
||||
underline: bool = False,
|
||||
underline_color: str = "",
|
||||
) -> IMG:
|
||||
block = Image.new("RGB", self.block_size, block_color)
|
||||
if not char:
|
||||
return block
|
||||
draw = ImageDraw.Draw(block)
|
||||
|
||||
char_size = self.font_char.getbbox(char)[2:]
|
||||
x = (self.block_size[0] - char_size[0]) / 2
|
||||
y = (self.block_size[1] - char_size[1]) / 5 * 3
|
||||
draw.text((x, y), char, font=self.font_char, fill=char_color)
|
||||
|
||||
space = 5
|
||||
need_space = bool(initial and final)
|
||||
py_length = self.font_pinyin.getlength(initial + final)
|
||||
if need_space:
|
||||
py_length += space
|
||||
py_start = (self.block_size[0] - py_length) / 2
|
||||
x = py_start
|
||||
y = self.block_size[0] / 8
|
||||
draw.text((x, y), initial, font=self.font_pinyin, fill=initial_color)
|
||||
x += self.font_pinyin.getlength(initial)
|
||||
if need_space:
|
||||
x += space
|
||||
draw.text((x, y), final, font=self.font_pinyin, fill=final_color)
|
||||
|
||||
tone_size = self.font_tone.getbbox(tone)[2:]
|
||||
x = (self.block_size[0] + py_length) / 2 + tone_size[0] / 3
|
||||
y -= tone_size[1] / 3
|
||||
draw.text((x, y), tone, font=self.font_tone, fill=tone_color)
|
||||
|
||||
if underline:
|
||||
x = py_start
|
||||
py_size = self.font_pinyin.getbbox(initial + final)[2:]
|
||||
y = self.block_size[0] / 8 + py_size[1] + 2
|
||||
draw.line((x, y, x + py_length, y), fill=underline_color, width=1)
|
||||
y += 3
|
||||
draw.line((x, y, x + py_length, y), fill=underline_color, width=1)
|
||||
|
||||
return block
|
||||
|
||||
def draw(self) -> BytesIO:
|
||||
rows = min(len(self.guessed_idiom) + 1, self.times)
|
||||
board_w = self.length * self.block_size[0]
|
||||
board_w += (self.length - 1) * self.block_padding[0] + 2 * self.padding[0]
|
||||
board_h = rows * self.block_size[1]
|
||||
board_h += (rows - 1) * self.block_padding[1] + 2 * self.padding[1]
|
||||
board_size = (board_w, board_h)
|
||||
board = Image.new("RGB", board_size, self.colors.bg_color)
|
||||
|
||||
def get_states(guessed: List[str], answer: List[str]) -> List[GuessState]:
|
||||
states = []
|
||||
incorrect = []
|
||||
for i in range(self.length):
|
||||
if guessed[i] != answer[i]:
|
||||
incorrect.append(answer[i])
|
||||
else:
|
||||
incorrect.append("_")
|
||||
for i in range(self.length):
|
||||
if guessed[i] == answer[i]:
|
||||
states.append(GuessState.CORRECT)
|
||||
elif guessed[i] in incorrect:
|
||||
states.append(GuessState.EXIST)
|
||||
incorrect[incorrect.index(guessed[i])] = "_"
|
||||
else:
|
||||
states.append(GuessState.WRONG)
|
||||
return states
|
||||
|
||||
def get_pinyin_color(state: GuessState) -> str:
|
||||
if state == GuessState.CORRECT:
|
||||
return self.colors.correct_color
|
||||
elif state == GuessState.EXIST:
|
||||
return self.colors.exist_color
|
||||
else:
|
||||
return self.colors.wrong_color_pinyin
|
||||
|
||||
def get_char_color(state: GuessState) -> str:
|
||||
if state == GuessState.CORRECT:
|
||||
return self.colors.correct_color
|
||||
elif state == GuessState.EXIST:
|
||||
return self.colors.exist_color
|
||||
else:
|
||||
return self.colors.wrong_color_char
|
||||
|
||||
def block_pos(row: int, col: int) -> Tuple[int, int]:
|
||||
x = self.padding[0] + (self.block_size[0] + self.block_padding[0]) * col
|
||||
y = self.padding[1] + (self.block_size[1] + self.block_padding[1]) * row
|
||||
return x, y
|
||||
|
||||
for i in range(len(self.guessed_idiom)):
|
||||
idiom = self.guessed_idiom[i]
|
||||
pinyin = self.guessed_pinyin[i]
|
||||
char_states = get_states(list(idiom), list(self.idiom))
|
||||
initial_states = get_states(
|
||||
[p[0] for p in pinyin], [p[0] for p in self.pinyin]
|
||||
)
|
||||
final_states = get_states(
|
||||
[p[1] for p in pinyin], [p[1] for p in self.pinyin]
|
||||
)
|
||||
tone_states = get_states(
|
||||
[p[2] for p in pinyin], [p[2] for p in self.pinyin]
|
||||
)
|
||||
underline_states = get_states(
|
||||
[p[0] + p[1] for p in pinyin], [p[0] + p[1] for p in self.pinyin]
|
||||
)
|
||||
for j in range(self.length):
|
||||
char = idiom[j]
|
||||
i2, f2, t2 = pinyin[j]
|
||||
if char == self.idiom[j]:
|
||||
block_color = self.colors.correct_color
|
||||
char_color = initial_color = final_color = tone_color = (
|
||||
self.colors.bg_color
|
||||
)
|
||||
underline = False
|
||||
underline_color = ""
|
||||
else:
|
||||
block_color = self.colors.block_color
|
||||
char_color = get_char_color(char_states[j])
|
||||
initial_color = get_pinyin_color(initial_states[j])
|
||||
final_color = get_pinyin_color(final_states[j])
|
||||
tone_color = get_pinyin_color(tone_states[j])
|
||||
underline_color = get_pinyin_color(underline_states[j])
|
||||
underline = underline_color in (
|
||||
self.colors.correct_color,
|
||||
self.colors.exist_color,
|
||||
)
|
||||
block = self.draw_block(
|
||||
block_color,
|
||||
char,
|
||||
char_color,
|
||||
i2,
|
||||
initial_color,
|
||||
f2,
|
||||
final_color,
|
||||
t2,
|
||||
tone_color,
|
||||
underline,
|
||||
underline_color,
|
||||
)
|
||||
board.paste(block, block_pos(i, j))
|
||||
|
||||
for i in range(len(self.guessed_idiom), rows):
|
||||
for j in range(self.length):
|
||||
block = self.draw_block(self.colors.block_color)
|
||||
board.paste(block, block_pos(i, j))
|
||||
|
||||
return save_jpg(board)
|
||||
|
||||
def draw_hint(self) -> BytesIO:
|
||||
guessed_char = set("".join(self.guessed_idiom))
|
||||
guessed_initial = set()
|
||||
guessed_final = set()
|
||||
guessed_tone = set()
|
||||
for pinyin in self.guessed_pinyin:
|
||||
for p in pinyin:
|
||||
guessed_initial.add(p[0])
|
||||
guessed_final.add(p[1])
|
||||
guessed_tone.add(p[2])
|
||||
|
||||
board_w = self.length * self.block_size[0]
|
||||
board_w += (self.length - 1) * self.block_padding[0] + 2 * self.padding[0]
|
||||
board_h = self.block_size[1] + 2 * self.padding[1]
|
||||
board = Image.new("RGB", (board_w, board_h), self.colors.bg_color)
|
||||
|
||||
for i in range(self.length):
|
||||
char = self.idiom[i]
|
||||
hi, hf, ht = self.pinyin[i]
|
||||
color = char_c = initial_c = final_c = tone_c = self.colors.correct_color
|
||||
if char not in guessed_char:
|
||||
char = "?"
|
||||
color = self.colors.block_color
|
||||
char_c = self.colors.wrong_color_char
|
||||
else:
|
||||
char_c = initial_c = final_c = tone_c = self.colors.bg_color
|
||||
if hi not in guessed_initial:
|
||||
hi = "?"
|
||||
initial_c = self.colors.wrong_color_pinyin
|
||||
if hf not in guessed_final:
|
||||
hf = "?"
|
||||
final_c = self.colors.wrong_color_pinyin
|
||||
if ht not in guessed_tone:
|
||||
ht = "?"
|
||||
tone_c = self.colors.wrong_color_pinyin
|
||||
block = self.draw_block(
|
||||
color, char, char_c, hi, initial_c, hf, final_c, ht, tone_c
|
||||
)
|
||||
x = self.padding[0] + (self.block_size[0] + self.block_padding[0]) * i
|
||||
y = self.padding[1]
|
||||
board.paste(block, (x, y))
|
||||
return save_jpg(board)
|
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 MeetWq
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
5686
src/nonebot_plugins/trimo_plugin_handle/resources/data/answers.json
Normal file
5686
src/nonebot_plugins/trimo_plugin_handle/resources/data/answers.json
Normal file
File diff suppressed because it is too large
Load Diff
73010
src/nonebot_plugins/trimo_plugin_handle/resources/data/answers_hard.json
Normal file
73010
src/nonebot_plugins/trimo_plugin_handle/resources/data/answers_hard.json
Normal file
File diff suppressed because it is too large
Load Diff
30091
src/nonebot_plugins/trimo_plugin_handle/resources/data/idioms.txt
Normal file
30091
src/nonebot_plugins/trimo_plugin_handle/resources/data/idioms.txt
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
118
src/nonebot_plugins/trimo_plugin_handle/utils.py
Normal file
118
src/nonebot_plugins/trimo_plugin_handle/utils.py
Normal file
@ -0,0 +1,118 @@
|
||||
import json
|
||||
import random
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
# from watchdog.observers import Observer
|
||||
# from watchdog.events import FileSystemEventHandler, FileModifiedEvent
|
||||
|
||||
from PIL import ImageFont
|
||||
from PIL.Image import Image as IMG
|
||||
from PIL.ImageFont import FreeTypeFont
|
||||
from pypinyin import Style, pinyin
|
||||
|
||||
resource_dir = Path(__file__).parent / "resources"
|
||||
fonts_dir = resource_dir / "fonts"
|
||||
data_dir = resource_dir / "data"
|
||||
idiom_path = data_dir / "idioms.txt"
|
||||
answer_path = data_dir / "answers.json"
|
||||
answer_hard_path = data_dir / "answers_hard.json"
|
||||
|
||||
|
||||
LEGAL_PHRASES = [
|
||||
idiom.strip() for idiom in idiom_path.open("r", encoding="utf-8").readlines()
|
||||
]
|
||||
ANSWER_PHRASES: List[Dict[str, str]] = json.load(
|
||||
answer_path.open("r", encoding="utf-8")
|
||||
)
|
||||
HARD_ANSWER_PHRASES: List[Dict[str, str]] = json.load(
|
||||
answer_hard_path.open("r", encoding="utf-8")
|
||||
)
|
||||
|
||||
# class LegalPhrasesModifiedHandler(FileSystemEventHandler):
|
||||
# """
|
||||
# Handler for resource file changes
|
||||
# """
|
||||
|
||||
# def on_modified(self, event):
|
||||
# print(f"{event.src_path} modified, reloading resource...")
|
||||
# if "idioms.txt" in event.src_path:
|
||||
# global LEGAL_PHRASES
|
||||
# LEGAL_PHRASES = [
|
||||
# idiom.strip()
|
||||
# for idiom in idiom_path.open("r", encoding="utf-8").readlines()
|
||||
# ]
|
||||
# elif "answers.json" in event.src_path:
|
||||
# global ANSWER_PHRASES
|
||||
# ANSWER_PHRASES = json.load(
|
||||
# answer_path.open("r", encoding="utf-8")
|
||||
# )
|
||||
|
||||
|
||||
# Observer().schedule(
|
||||
# LegalPhrasesModifiedHandler(),
|
||||
# data_dir,
|
||||
# recursive=False,
|
||||
# event_filter=FileModifiedEvent,
|
||||
# )
|
||||
|
||||
|
||||
def legal_idiom(word: str) -> bool:
|
||||
return word in LEGAL_PHRASES
|
||||
|
||||
|
||||
def random_idiom(is_hard: bool = False) -> Tuple[str, str]:
|
||||
answer = random.choice(HARD_ANSWER_PHRASES if is_hard else ANSWER_PHRASES)
|
||||
return answer["word"], answer["explanation"]
|
||||
|
||||
|
||||
# fmt: off
|
||||
# 声母
|
||||
INITIALS = [
|
||||
"zh", "z", "y", "x", "w", "t", "sh", "s", "r", "q", "p",
|
||||
"n", "m", "l", "k", "j", "h", "g", "f", "d", "ch", "c", "b"
|
||||
]
|
||||
# 韵母
|
||||
FINALS = [
|
||||
"ün", "üe", "üan", "ü", "uo", "un", "ui", "ue", "uang",
|
||||
"uan", "uai","ua", "ou", "iu", "iong", "ong", "io", "ing",
|
||||
"in", "ie", "iao", "iang", "ian", "ia", "er", "eng", "en",
|
||||
"ei", "ao", "ang", "an", "ai", "u", "o", "i", "e", "a"
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
|
||||
def get_pinyin(idiom: str) -> List[Tuple[str, str, str]]:
|
||||
pys = pinyin(idiom, style=Style.TONE3, v_to_u=True)
|
||||
results = []
|
||||
for p in pys:
|
||||
py = p[0]
|
||||
if py[-1].isdigit():
|
||||
tone = py[-1]
|
||||
py = py[:-1]
|
||||
else:
|
||||
tone = ""
|
||||
initial = ""
|
||||
for i in INITIALS:
|
||||
if py.startswith(i):
|
||||
initial = i
|
||||
break
|
||||
final = ""
|
||||
for f in FINALS:
|
||||
if py.endswith(f):
|
||||
final = f
|
||||
break
|
||||
results.append((initial, final, tone)) # 声母,韵母,声调
|
||||
return results
|
||||
|
||||
|
||||
def save_jpg(frame: IMG) -> BytesIO:
|
||||
output = BytesIO()
|
||||
frame = frame.convert("RGB")
|
||||
frame.save(output, format="jpeg")
|
||||
return output
|
||||
|
||||
|
||||
def load_font(name: str, fontsize: int) -> FreeTypeFont:
|
||||
return ImageFont.truetype(str(fonts_dir / name), fontsize, encoding="utf-8")
|
Reference in New Issue
Block a user