nonebot-plugin-lsay 26.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nonebot_plugin_lsay/__init__.py +263 -0
- nonebot_plugin_lsay/config.py +26 -0
- nonebot_plugin_lsay/data/__init__.py +0 -0
- nonebot_plugin_lsay/data/generate_homophone_json.py +118 -0
- nonebot_plugin_lsay/data/homophone.json +64602 -0
- nonebot_plugin_lsay/data/main_magic_transform.py +220 -0
- nonebot_plugin_lsay/parser.py +98 -0
- nonebot_plugin_lsay/state.py +123 -0
- nonebot_plugin_lsay/transformer.py +166 -0
- nonebot_plugin_lsay/version.py +1 -0
- nonebot_plugin_lsay-26.8.0.dist-info/METADATA +211 -0
- nonebot_plugin_lsay-26.8.0.dist-info/RECORD +15 -0
- nonebot_plugin_lsay-26.8.0.dist-info/WHEEL +5 -0
- nonebot_plugin_lsay-26.8.0.dist-info/licenses/LICENSE +504 -0
- nonebot_plugin_lsay-26.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""
|
|
2
|
+
胡乱说 · NoneBot2 插件
|
|
3
|
+
插这件说都不会话了 —— 谐音恶搞 + 语义块乱序整活。
|
|
4
|
+
|
|
5
|
+
被动功能:监听文本消息,按概率(单用户独立)输出魔改文本。
|
|
6
|
+
主动功能:lsay 系列命令(用户层 / 超管层 / 全局),命令有无空格均可识别。
|
|
7
|
+
"""
|
|
8
|
+
import random
|
|
9
|
+
|
|
10
|
+
from nonebot import get_driver, get_plugin_config, on_message
|
|
11
|
+
from nonebot.adapters.onebot.v11 import Bot, MessageEvent
|
|
12
|
+
from nonebot.log import logger
|
|
13
|
+
from nonebot.plugin import PluginMetadata
|
|
14
|
+
|
|
15
|
+
from .config import Config
|
|
16
|
+
from .parser import parse
|
|
17
|
+
from .state import StateStore
|
|
18
|
+
from .transformer import magic_transform
|
|
19
|
+
from .version import __version__
|
|
20
|
+
|
|
21
|
+
__plugin_meta__ = PluginMetadata(
|
|
22
|
+
name="胡乱说",
|
|
23
|
+
description="插这件说都不会话了 —— 谐音恶搞 + 语义块乱序整活",
|
|
24
|
+
usage="lsay 文本 | lsay on/off | lsay gl set N | lsay help",
|
|
25
|
+
type="application",
|
|
26
|
+
homepage="https://github.com/chaichaisi/nonebot-plugin-lsay",
|
|
27
|
+
supported_adapters={"~onebot.v11"},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
driver = get_driver()
|
|
31
|
+
global_config = driver.config
|
|
32
|
+
plugin_config = get_plugin_config(Config)
|
|
33
|
+
|
|
34
|
+
store = StateStore(global_config.lsay_state_file)
|
|
35
|
+
|
|
36
|
+
lsay_matcher = on_message(priority=1, block=False)
|
|
37
|
+
|
|
38
|
+
# ---------------- 文案 ----------------
|
|
39
|
+
|
|
40
|
+
_INTRO = (
|
|
41
|
+
"胡乱说 ~ 插这件说都不会话了\n"
|
|
42
|
+
"用法:lsay 文本 | lsay on/off | lsay gl set N | lsay help"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
_INFO = (
|
|
46
|
+
"胡乱说插件\n"
|
|
47
|
+
"──────────────\n"
|
|
48
|
+
"插件名:nonebot-plugin-lsay\n"
|
|
49
|
+
"包名:nonebot_plugin_lsay\n"
|
|
50
|
+
"作者:Chaichaisi\n"
|
|
51
|
+
"项目主页:https://github.com/chaichaisi/nonebot-plugin-lsay\n"
|
|
52
|
+
"──────────────\n"
|
|
53
|
+
"版权所有,严禁商用"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
_USER_MENU = (
|
|
57
|
+
"胡乱说 · 用户菜单\n"
|
|
58
|
+
"────────────────────\n"
|
|
59
|
+
"lsay 文本 魔改文本并输出\n"
|
|
60
|
+
"lsay on 开启插件(默认开启)\n"
|
|
61
|
+
"lsay off 关闭插件\n"
|
|
62
|
+
"lsay gl show 查看你的触发概率\n"
|
|
63
|
+
"lsay gl set N 自定义概率(N: 1~15)\n"
|
|
64
|
+
"lsay gl noset 恢复全局默认概率\n"
|
|
65
|
+
"lsay help 显示本菜单\n"
|
|
66
|
+
"lsay info 插件信息\n"
|
|
67
|
+
"────────────────────\n"
|
|
68
|
+
"概率越大越易触发;群聊/私聊均可用"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
_SUPER_MENU = (
|
|
72
|
+
"\n\n胡乱说 · 超管菜单\n"
|
|
73
|
+
"────────────────────\n"
|
|
74
|
+
"lsay allon 全局开启(默认开启)\n"
|
|
75
|
+
"lsay alloff 全局关闭\n"
|
|
76
|
+
"lsay showgl 查看全局概率\n"
|
|
77
|
+
"lsay setgl N 设置全局概率(N: 1~50)\n"
|
|
78
|
+
"lsay nosetgl 恢复默认概率(5)\n"
|
|
79
|
+
"lsay ason ID 代用户开启\n"
|
|
80
|
+
"lsay asoff ID 代用户关闭\n"
|
|
81
|
+
"────────────────────\n"
|
|
82
|
+
"被 asoff 关闭的用户需联系超管才能重新开启"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
_CANT_HANDLE = "(胡乱说)这个我实在魔改不了,换个说法再试试~"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _is_superuser(uid: str) -> bool:
|
|
89
|
+
supers = getattr(global_config, "superusers", set())
|
|
90
|
+
return str(uid) in {str(s) for s in supers}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _is_pure_text(message) -> bool:
|
|
94
|
+
return bool(message) and all(getattr(seg, "type", None) == "text" for seg in message)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------- 被动功能 ----------------
|
|
98
|
+
|
|
99
|
+
async def _passive_handle(bot: Bot, event: MessageEvent) -> None:
|
|
100
|
+
try:
|
|
101
|
+
uid = event.get_user_id()
|
|
102
|
+
if uid == str(bot.self_id):
|
|
103
|
+
return
|
|
104
|
+
if not store.should_trigger(uid):
|
|
105
|
+
return
|
|
106
|
+
if not _is_pure_text(event.message):
|
|
107
|
+
return
|
|
108
|
+
text = event.message.extract_plain_text().strip()
|
|
109
|
+
if not text:
|
|
110
|
+
return
|
|
111
|
+
result = magic_transform(text, mode=plugin_config.lsay_default_mode,
|
|
112
|
+
prob=plugin_config.lsay_homo_prob)
|
|
113
|
+
if not result or result == text:
|
|
114
|
+
return
|
|
115
|
+
await bot.send(event, result)
|
|
116
|
+
except Exception as e: # noqa: BLE001
|
|
117
|
+
logger.opt(exception=False).warning(f"lsay 被动处理异常: {e}")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ---------------- 主动命令 ----------------
|
|
121
|
+
|
|
122
|
+
async def _handle_command(bot: Bot, event: MessageEvent, cmd: dict) -> None:
|
|
123
|
+
uid = event.get_user_id()
|
|
124
|
+
is_super = _is_superuser(uid)
|
|
125
|
+
kind = cmd["kind"]
|
|
126
|
+
|
|
127
|
+
async def reply(msg: str):
|
|
128
|
+
await bot.send(event, msg)
|
|
129
|
+
|
|
130
|
+
if kind == "intro":
|
|
131
|
+
return await reply(_INTRO)
|
|
132
|
+
|
|
133
|
+
if kind == "help":
|
|
134
|
+
menu = _USER_MENU
|
|
135
|
+
if is_super:
|
|
136
|
+
menu += _SUPER_MENU
|
|
137
|
+
return await reply(menu)
|
|
138
|
+
|
|
139
|
+
if kind == "info":
|
|
140
|
+
return await reply(_INFO)
|
|
141
|
+
|
|
142
|
+
if kind == "text":
|
|
143
|
+
if not _is_pure_text(event.message):
|
|
144
|
+
return # 非文本类型抛弃
|
|
145
|
+
text = cmd["text"]
|
|
146
|
+
if not text:
|
|
147
|
+
return await reply(_CANT_HANDLE)
|
|
148
|
+
result = None
|
|
149
|
+
for _ in range(5):
|
|
150
|
+
try:
|
|
151
|
+
r = magic_transform(text, mode=plugin_config.lsay_default_mode,
|
|
152
|
+
prob=plugin_config.lsay_homo_prob)
|
|
153
|
+
except Exception:
|
|
154
|
+
r = None
|
|
155
|
+
if r and r != text:
|
|
156
|
+
result = r
|
|
157
|
+
break
|
|
158
|
+
if result is None:
|
|
159
|
+
return await reply(_CANT_HANDLE)
|
|
160
|
+
return await reply(result)
|
|
161
|
+
|
|
162
|
+
if kind == "user_on":
|
|
163
|
+
u = store.get_user(uid)
|
|
164
|
+
if u.get("forced_off"):
|
|
165
|
+
return await reply("(胡乱说)你已被超管关闭该功能,请联系超管解决。")
|
|
166
|
+
store.set_user_enabled(uid, True)
|
|
167
|
+
return await reply("(胡乱说)已开启,我来给你整活~")
|
|
168
|
+
|
|
169
|
+
if kind == "user_off":
|
|
170
|
+
store.set_user_enabled(uid, False)
|
|
171
|
+
return await reply("(胡乱说)已关闭,安静如鸡。")
|
|
172
|
+
|
|
173
|
+
if kind == "gl_show":
|
|
174
|
+
u = store.get_user(uid)
|
|
175
|
+
g = store.get_global()
|
|
176
|
+
if u.get("custom_prob"):
|
|
177
|
+
text = f"(胡乱说)你的自定义概率:{u['custom_prob']}(优先于全局)"
|
|
178
|
+
else:
|
|
179
|
+
text = f"(胡乱说)你未自定义,使用全局概率:{g['prob']}"
|
|
180
|
+
return await reply(text)
|
|
181
|
+
|
|
182
|
+
if kind == "gl_set":
|
|
183
|
+
val = cmd["value"]
|
|
184
|
+
lo, hi = plugin_config.lsay_user_prob_min, plugin_config.lsay_user_prob_max
|
|
185
|
+
if not (lo <= val <= hi):
|
|
186
|
+
return await reply(
|
|
187
|
+
f"(胡乱说)概率只支持 {lo}~{hi} 的整数,0/100 不允许:"
|
|
188
|
+
f"一个是不会触发,一个是每条都触发(会刷屏)。你输入的 {val} 超范围了。")
|
|
189
|
+
store.set_user_custom_prob(uid, val)
|
|
190
|
+
return await reply(f"(胡乱说)已把你的触发概率设为 {val}。")
|
|
191
|
+
|
|
192
|
+
if kind == "gl_noset":
|
|
193
|
+
store.set_user_custom_prob(uid, None)
|
|
194
|
+
g = store.get_global()
|
|
195
|
+
return await reply(f"(胡乱说)已恢复全局默认概率(当前全局:{g['prob']})。")
|
|
196
|
+
|
|
197
|
+
if kind == "need_number":
|
|
198
|
+
return await reply(f"(胡乱说)缺少数值参数,用法:{cmd['usage']}")
|
|
199
|
+
|
|
200
|
+
if kind == "unknown":
|
|
201
|
+
return await reply(f"(胡乱说)看不懂「{cmd.get('rest', '')}」,试试 lsay help")
|
|
202
|
+
|
|
203
|
+
# ---------- 超管命令 ----------
|
|
204
|
+
if not is_super:
|
|
205
|
+
return await reply("(胡乱说)这是超管专属命令,你没有权限哦。")
|
|
206
|
+
|
|
207
|
+
if kind == "all_on":
|
|
208
|
+
store.set_global_enabled(True)
|
|
209
|
+
return await reply("(胡乱说)全局已开启,所有会话都能玩!")
|
|
210
|
+
|
|
211
|
+
if kind == "all_off":
|
|
212
|
+
store.set_global_enabled(False)
|
|
213
|
+
return await reply("(胡乱说)全局已关闭,所有人都安静了。")
|
|
214
|
+
|
|
215
|
+
if kind == "showgl":
|
|
216
|
+
g = store.get_global()
|
|
217
|
+
state_txt = "开启" if g["enabled"] else "关闭"
|
|
218
|
+
return await reply(f"(胡乱说)全局状态:{state_txt},触发概率:{g['prob']}。")
|
|
219
|
+
|
|
220
|
+
if kind == "setgl":
|
|
221
|
+
val = cmd["value"]
|
|
222
|
+
lo, hi = plugin_config.lsay_global_prob_min, plugin_config.lsay_global_prob_max
|
|
223
|
+
if not (lo <= val <= hi):
|
|
224
|
+
return await reply(
|
|
225
|
+
f"(胡乱说)全局概率只支持 {lo}~{hi} 的整数。"
|
|
226
|
+
f"你输入的 {val} 超范围:0 不触发,100 每条都触发(会刷屏)。")
|
|
227
|
+
store.set_global_prob(val)
|
|
228
|
+
return await reply(f"(胡乱说)全局概率已设为 {val}。")
|
|
229
|
+
|
|
230
|
+
if kind == "nosetgl":
|
|
231
|
+
store.set_global_prob(5)
|
|
232
|
+
return await reply("(胡乱说)已恢复插件默认概率:5。")
|
|
233
|
+
|
|
234
|
+
if kind == "ason":
|
|
235
|
+
store.force_on_user(cmd["uid"])
|
|
236
|
+
return await reply(f"(胡乱说)已代用户 {cmd['uid']} 开启插件。")
|
|
237
|
+
|
|
238
|
+
if kind == "asoff":
|
|
239
|
+
store.force_off_user(cmd["uid"])
|
|
240
|
+
return await reply(
|
|
241
|
+
f"(胡乱说)已代用户 {cmd['uid']} 关闭插件。"
|
|
242
|
+
f"该用户之后无法自行 lsay on 开启,需联系超管解决。")
|
|
243
|
+
|
|
244
|
+
return await reply("(胡乱说)未知指令,试试 lsay help")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@lsay_matcher.handle()
|
|
248
|
+
async def _handler(bot: Bot, event: MessageEvent) -> None:
|
|
249
|
+
try:
|
|
250
|
+
raw = event.message.extract_plain_text()
|
|
251
|
+
except Exception:
|
|
252
|
+
raw = event.get_plaintext()
|
|
253
|
+
cmd = parse(raw)
|
|
254
|
+
if cmd is None:
|
|
255
|
+
await _passive_handle(bot, event)
|
|
256
|
+
else:
|
|
257
|
+
await _handle_command(bot, event, cmd)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@driver.on_startup
|
|
261
|
+
async def _startup() -> None:
|
|
262
|
+
logger.info(f"胡乱说 nonebot-plugin-lsay v{__version__} 已加载")
|
|
263
|
+
logger.info(f"全局概率 {store.get_global()['prob']},插件版本 {__version__}")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
from .version import __version__
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Config(BaseModel):
|
|
7
|
+
"""胡乱说插件配置项(可在 .env 中覆盖)"""
|
|
8
|
+
|
|
9
|
+
# 全局默认开启状态
|
|
10
|
+
lsay_global_enabled: bool = True
|
|
11
|
+
# 全局默认触发概率(1~50,越大越容易触发;默认 5)
|
|
12
|
+
lsay_global_prob: int = 5
|
|
13
|
+
# 普通用户可设置的概率范围(1~15)
|
|
14
|
+
lsay_user_prob_min: int = 1
|
|
15
|
+
lsay_user_prob_max: int = 15
|
|
16
|
+
# 超管可设置的全局概率范围(1~50)
|
|
17
|
+
lsay_global_prob_min: int = 1
|
|
18
|
+
lsay_global_prob_max: int = 50
|
|
19
|
+
# 谐音替换概率(0~1,魔改内部参数)
|
|
20
|
+
lsay_homo_prob: float = 0.35
|
|
21
|
+
# 魔改模式: shuffle | homo | mix
|
|
22
|
+
lsay_default_mode: str = "mix"
|
|
23
|
+
# 状态文件保存路径(相对 NoneBot 运行目录)
|
|
24
|
+
lsay_state_file: str = "lsay_state.json"
|
|
25
|
+
# 插件版本号
|
|
26
|
+
lsay_version: str = Field(default=__version__)
|
|
File without changes
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
胡乱说 · 谐音字典生成器
|
|
5
|
+
独立生成外部 json 谐音字典文件 homophone.json,供主业务脚本读取。
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
python generate_homophone_json.py
|
|
9
|
+
python generate_homophone_json.py --level common --output homophone.json
|
|
10
|
+
python generate_homophone_json.py --level all --polyphonic --compact
|
|
11
|
+
"""
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
from pypinyin import pinyin, Style
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def level1_chars():
|
|
18
|
+
"""GB2312 一级汉字(约 3755 个,按拼音排序的常用字)"""
|
|
19
|
+
chars = []
|
|
20
|
+
for hi in range(0xB0, 0xD8):
|
|
21
|
+
for lo in range(0xA1, 0xFE):
|
|
22
|
+
try:
|
|
23
|
+
c = bytes([hi, lo]).decode("gb2312")
|
|
24
|
+
except UnicodeDecodeError:
|
|
25
|
+
continue
|
|
26
|
+
if c and "\u4e00" <= c <= "\u9fa6":
|
|
27
|
+
chars.append(c)
|
|
28
|
+
return chars
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def level12_chars():
|
|
32
|
+
"""GB2312 一级 + 二级汉字(约 6763 个)"""
|
|
33
|
+
chars = []
|
|
34
|
+
for hi in range(0xB0, 0xF8):
|
|
35
|
+
for lo in range(0xA1, 0xFE):
|
|
36
|
+
try:
|
|
37
|
+
c = bytes([hi, lo]).decode("gb2312")
|
|
38
|
+
except UnicodeDecodeError:
|
|
39
|
+
continue
|
|
40
|
+
if c and "\u4e00" <= c <= "\u9fa6":
|
|
41
|
+
chars.append(c)
|
|
42
|
+
return chars
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def all_chars():
|
|
46
|
+
"""Unicode 基本区全部汉字(约 2 万,含大量生僻字)"""
|
|
47
|
+
return [chr(c) for c in range(0x4e00, 0x9fa6)]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
CHAR_LOADERS = {
|
|
51
|
+
"common": level1_chars,
|
|
52
|
+
"extended": level12_chars,
|
|
53
|
+
"all": all_chars,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_homophone_dict(chars, polyphonic=False):
|
|
58
|
+
"""
|
|
59
|
+
遍历汉字,用 pypinyin 取读音,构建:
|
|
60
|
+
{原汉字: [同音字列表, ...]}
|
|
61
|
+
过滤:如果没有其他同音字,则此 key 不写入。
|
|
62
|
+
"""
|
|
63
|
+
char_pys = {}
|
|
64
|
+
py_chars = {}
|
|
65
|
+
for char in chars:
|
|
66
|
+
res = pinyin(char, style=Style.NORMAL, heteronym=polyphonic, errors="ignore")
|
|
67
|
+
if not res or not res[0]:
|
|
68
|
+
continue
|
|
69
|
+
pys = set(p for p in res[0] if p)
|
|
70
|
+
if not pys:
|
|
71
|
+
continue
|
|
72
|
+
char_pys[char] = pys
|
|
73
|
+
for py in pys:
|
|
74
|
+
py_chars.setdefault(py, set()).add(char)
|
|
75
|
+
|
|
76
|
+
char2homos = {}
|
|
77
|
+
for char, pys in char_pys.items():
|
|
78
|
+
others = []
|
|
79
|
+
seen = set()
|
|
80
|
+
for py in pys:
|
|
81
|
+
for x in py_chars.get(py, ()):
|
|
82
|
+
if x != char and x not in seen:
|
|
83
|
+
seen.add(x)
|
|
84
|
+
others.append(x)
|
|
85
|
+
if others:
|
|
86
|
+
char2homos[char] = others
|
|
87
|
+
return char2homos
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main():
|
|
91
|
+
ap = argparse.ArgumentParser(description="生成谐音字典 homophone.json")
|
|
92
|
+
ap.add_argument("-o", "--output", default="homophone.json",
|
|
93
|
+
help="输出文件路径(默认 homophone.json)")
|
|
94
|
+
ap.add_argument("--level", choices=list(CHAR_LOADERS.keys()), default="common",
|
|
95
|
+
help="字库级别: common=GB2312一级常用字(约3755), "
|
|
96
|
+
"extended=一级+二级(约6763), all=全部汉字(约2万,含生僻字)")
|
|
97
|
+
ap.add_argument("--polyphonic", action="store_true",
|
|
98
|
+
help="按多音字展开(一个字按全部读音收集同音字)")
|
|
99
|
+
ap.add_argument("--compact", action="store_true",
|
|
100
|
+
help="压缩输出(不缩进),减小 json 体积")
|
|
101
|
+
args = ap.parse_args()
|
|
102
|
+
|
|
103
|
+
chars = CHAR_LOADERS[args.level]()
|
|
104
|
+
print(f"字库级别 [{args.level}],共 {len(chars)} 个汉字,开始注音构建...")
|
|
105
|
+
d = build_homophone_dict(chars, polyphonic=args.polyphonic)
|
|
106
|
+
|
|
107
|
+
with open(args.output, "w", encoding="utf-8") as f:
|
|
108
|
+
json.dump(d, f, ensure_ascii=False, indent=None if args.compact else 2)
|
|
109
|
+
|
|
110
|
+
total = sum(len(v) for v in d.values())
|
|
111
|
+
avg = total / len(d) if d else 0
|
|
112
|
+
size = len(json.dumps(d, ensure_ascii=False))
|
|
113
|
+
print(f"已生成 {args.output}:有效可替换汉字 {len(d)} 个,"
|
|
114
|
+
f"平均每个字 {avg:.1f} 个同音字,文件约 {size/1024:.1f} KB。")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
main()
|