hyper-bot 0.7__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.
- Hyper/Adapters/OneBot.py +278 -0
- Hyper/Adapters/OneBotLib/Manager.py +93 -0
- Hyper/Configurator.py +83 -0
- Hyper/DataBase.py +81 -0
- Hyper/Errors.py +18 -0
- Hyper/Events.py +268 -0
- Hyper/Listener.py +7 -0
- Hyper/Logger.py +156 -0
- Hyper/Logic.py +167 -0
- Hyper/Manager.py +6 -0
- Hyper/ModuleClass.py +67 -0
- Hyper/Network.py +70 -0
- Hyper/Segments.py +289 -0
- Hyper/WordSafety.py +68 -0
- hyper_bot-0.7.dist-info/LICENSE +547 -0
- hyper_bot-0.7.dist-info/METADATA +9 -0
- hyper_bot-0.7.dist-info/RECORD +19 -0
- hyper_bot-0.7.dist-info/WHEEL +5 -0
- hyper_bot-0.7.dist-info/top_level.txt +1 -0
Hyper/Network.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import websocket
|
|
2
|
+
import httpx
|
|
3
|
+
import queue
|
|
4
|
+
import flask
|
|
5
|
+
import traceback
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import threading
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WebsocketConnection:
|
|
12
|
+
def __init__(self, url: str):
|
|
13
|
+
self.ws = websocket.WebSocket()
|
|
14
|
+
self.url = url
|
|
15
|
+
|
|
16
|
+
def connect(self) -> None:
|
|
17
|
+
self.ws.connect(self.url)
|
|
18
|
+
|
|
19
|
+
def send(self, message: str) -> None:
|
|
20
|
+
self.ws.send(message)
|
|
21
|
+
|
|
22
|
+
def close(self) -> None:
|
|
23
|
+
self.ws.close()
|
|
24
|
+
|
|
25
|
+
def recv(self) -> dict:
|
|
26
|
+
return json.loads(self.ws.recv())
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class HTTPConnection:
|
|
30
|
+
def __init__(self, url: str, listener_url: str):
|
|
31
|
+
self.url = url
|
|
32
|
+
listener_url = listener_url.replace("http://", "")
|
|
33
|
+
listener_url = listener_url.replace("https://", "")
|
|
34
|
+
self.listener_url = listener_url.split(":")[0]
|
|
35
|
+
try:
|
|
36
|
+
self.port = int(listener_url.split(":")[1])
|
|
37
|
+
except IndexError:
|
|
38
|
+
self.port = 8080
|
|
39
|
+
self.app = flask.Flask(__name__)
|
|
40
|
+
self.app.config["LOGGER_HANDLER_POLICY"] = "never"
|
|
41
|
+
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
|
42
|
+
self.reports = queue.Queue()
|
|
43
|
+
|
|
44
|
+
def connect(self) -> None:
|
|
45
|
+
@self.app.route("/", methods=["POST"])
|
|
46
|
+
def listener():
|
|
47
|
+
self.reports.put(flask.request.json)
|
|
48
|
+
return {}
|
|
49
|
+
|
|
50
|
+
# self.app.run(host=self.listener_url, port=self.port)
|
|
51
|
+
|
|
52
|
+
threading.Thread(target=lambda: self.app.run(host=self.listener_url, port=self.port)).start()
|
|
53
|
+
httpx.post(self.url)
|
|
54
|
+
traceback.print_exc()
|
|
55
|
+
|
|
56
|
+
def recv(self) -> dict:
|
|
57
|
+
return self.reports.get()
|
|
58
|
+
|
|
59
|
+
def send(self, endpoint: str, data: dict, echo: str) -> None:
|
|
60
|
+
response = httpx.post(f"{self.url}/{endpoint}", json=data)
|
|
61
|
+
res = response.json()
|
|
62
|
+
res["echo"] = echo
|
|
63
|
+
self.reports.put(res)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def close() -> None:
|
|
67
|
+
shutdown_func = flask.request.environ.get('werkzeug.server.shutdown')
|
|
68
|
+
if shutdown_func is None:
|
|
69
|
+
raise RuntimeError('Not running with the Werkzeug Server')
|
|
70
|
+
shutdown_func()
|
Hyper/Segments.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import typing
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
from Hyper.Errors import *
|
|
6
|
+
|
|
7
|
+
message_types = {}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def segment_builder(sg_type: str, summary_tmp: str = None):
|
|
11
|
+
# print(inspect.get_annotations(cls))
|
|
12
|
+
def inner_builder(cls):
|
|
13
|
+
var = dict(vars(cls))
|
|
14
|
+
anns: dict = var.get("__annotations__", False) or dict()
|
|
15
|
+
arg = {}
|
|
16
|
+
|
|
17
|
+
def init(self, *args, **kwargs):
|
|
18
|
+
if len(args) > 0:
|
|
19
|
+
for i in args:
|
|
20
|
+
arg[list(anns.keys())[list(args).index(i)]] = i
|
|
21
|
+
|
|
22
|
+
if len(kwargs) > 0:
|
|
23
|
+
for i in kwargs:
|
|
24
|
+
arg[i] = kwargs[i]
|
|
25
|
+
new_arg = arg.copy()
|
|
26
|
+
|
|
27
|
+
if len(anns) > len(arg):
|
|
28
|
+
for i in anns.keys():
|
|
29
|
+
if i not in arg.keys():
|
|
30
|
+
if i not in var.keys():
|
|
31
|
+
new_arg[i] = None
|
|
32
|
+
continue
|
|
33
|
+
new_arg[i] = anns[i](var[i])
|
|
34
|
+
|
|
35
|
+
for i in new_arg:
|
|
36
|
+
setattr(self, i, new_arg[i])
|
|
37
|
+
|
|
38
|
+
cls.__init__ = init
|
|
39
|
+
|
|
40
|
+
def to_json(self) -> dict:
|
|
41
|
+
base = {"type": sg_type, "data": {}}
|
|
42
|
+
for i in anns:
|
|
43
|
+
base["data"][i] = getattr(self, i)
|
|
44
|
+
|
|
45
|
+
return base
|
|
46
|
+
|
|
47
|
+
cls.to_json = to_json
|
|
48
|
+
|
|
49
|
+
def to_str(self) -> str:
|
|
50
|
+
text = summary_tmp
|
|
51
|
+
if text is None:
|
|
52
|
+
text = "[]"
|
|
53
|
+
if "<" not in text and ">" not in text:
|
|
54
|
+
return text
|
|
55
|
+
|
|
56
|
+
for i in anns:
|
|
57
|
+
if f"<{i}>" in summary_tmp:
|
|
58
|
+
try:
|
|
59
|
+
v = self.__getattribute__(i)
|
|
60
|
+
except AttributeError:
|
|
61
|
+
v = None
|
|
62
|
+
text = text.replace(f"<{i}>", str(v))
|
|
63
|
+
|
|
64
|
+
return text
|
|
65
|
+
cls.__str__ = to_str if cls().__str__() == "__not_set__" else cls.__str__
|
|
66
|
+
|
|
67
|
+
message_types[sg_type] = {
|
|
68
|
+
"type": cls,
|
|
69
|
+
"args": list(anns.keys())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return cls
|
|
73
|
+
|
|
74
|
+
return inner_builder
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Base:
|
|
78
|
+
def __init__(self, *args, **kwargs): ...
|
|
79
|
+
|
|
80
|
+
def to_json(self) -> dict: ...
|
|
81
|
+
|
|
82
|
+
def __str__(self) -> str: return "__not_set__"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@segment_builder("text", "<text>")
|
|
86
|
+
class Text(Base):
|
|
87
|
+
text: str
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@segment_builder("image", "[图片]")
|
|
91
|
+
class Image(Base):
|
|
92
|
+
file: str
|
|
93
|
+
url: str = ""
|
|
94
|
+
summary: str = "[图片]"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@segment_builder("at", f"@<qq>")
|
|
98
|
+
class At(Base):
|
|
99
|
+
qq: str
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@segment_builder("reply", "")
|
|
103
|
+
class Reply(Base):
|
|
104
|
+
id: str
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@segment_builder("face", "[表情: <id>]")
|
|
108
|
+
class Faces(Base):
|
|
109
|
+
id: str
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@segment_builder("location", "[位置: <lat>, <lon>]")
|
|
113
|
+
class Location(Base):
|
|
114
|
+
lat: str
|
|
115
|
+
lon: str
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@segment_builder("record", "[语音]")
|
|
119
|
+
class Record(Base):
|
|
120
|
+
file: str
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@segment_builder("video", "[视频]")
|
|
124
|
+
class Video(Base):
|
|
125
|
+
file: str
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@segment_builder("poke", "[拍一拍]")
|
|
129
|
+
class Poke(Base):
|
|
130
|
+
type: str
|
|
131
|
+
id: str
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@segment_builder("contact")
|
|
135
|
+
class Contact(Base):
|
|
136
|
+
type: str
|
|
137
|
+
id: str
|
|
138
|
+
|
|
139
|
+
def __str__(self) -> str:
|
|
140
|
+
return f"[推荐{'群' if self.type == 'group' else '用户'}: {self.id}]"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@segment_builder("forward", "[转发消息]")
|
|
144
|
+
class Forward(Base):
|
|
145
|
+
id: str
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@segment_builder("node", "[转发消息]")
|
|
149
|
+
class Node(Base):
|
|
150
|
+
id: str
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class CustomNode:
|
|
154
|
+
def __init__(self, user_id: str, nick_name: str, content):
|
|
155
|
+
self.content = {"type": "node",
|
|
156
|
+
"data": {"user_id": user_id, "nick_name": nick_name, "content": content.get_sync()}}
|
|
157
|
+
|
|
158
|
+
def to_json(self) -> dict:
|
|
159
|
+
return self.content
|
|
160
|
+
|
|
161
|
+
def __str__(self) -> str:
|
|
162
|
+
return f"[自定义节点]"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class KeyBoardButton:
|
|
166
|
+
def __init__(self,
|
|
167
|
+
text: str,
|
|
168
|
+
style: int = 1,
|
|
169
|
+
button_type: int = 2,
|
|
170
|
+
data: str = "Hello World",
|
|
171
|
+
enter: bool = False,
|
|
172
|
+
permission: int = 2,
|
|
173
|
+
specify_user_ids=None):
|
|
174
|
+
self.content = {
|
|
175
|
+
"id": str(uuid.uuid4()),
|
|
176
|
+
"render_data": {
|
|
177
|
+
"label": text,
|
|
178
|
+
"visited_label": text,
|
|
179
|
+
"style": style
|
|
180
|
+
},
|
|
181
|
+
"action": {
|
|
182
|
+
"type": button_type,
|
|
183
|
+
"permission": {
|
|
184
|
+
"type": permission,
|
|
185
|
+
"specify_user_ids": specify_user_ids
|
|
186
|
+
},
|
|
187
|
+
"enter": enter,
|
|
188
|
+
"unsupport_tips": "Harcic",
|
|
189
|
+
"data": data
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
def get(self) -> dict:
|
|
194
|
+
return self.content
|
|
195
|
+
|
|
196
|
+
def __str__(self) -> str:
|
|
197
|
+
return f"[按键{self.content['render_data']['label']}]"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class KeyBoardRow:
|
|
201
|
+
def __init__(self, buttons: list[KeyBoardButton] = None):
|
|
202
|
+
self.content = {
|
|
203
|
+
"buttons": []
|
|
204
|
+
}
|
|
205
|
+
buttons = [] if buttons is None else buttons
|
|
206
|
+
for i in buttons:
|
|
207
|
+
self.content["buttons"].append(i.get())
|
|
208
|
+
|
|
209
|
+
def add(self, button: KeyBoardButton) -> None:
|
|
210
|
+
if len(self.content["buttons"]) < 5:
|
|
211
|
+
self.content["buttons"].append(button.get())
|
|
212
|
+
else:
|
|
213
|
+
raise ButtonRowFulledError("This button row is full")
|
|
214
|
+
|
|
215
|
+
def get(self) -> dict:
|
|
216
|
+
return self.content
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class KeyBoard:
|
|
220
|
+
def __init__(self, button_rows: list[KeyBoardRow]):
|
|
221
|
+
self.content = {"type": "keyboard",
|
|
222
|
+
"data": {"content": {"rows": [i.get() for i in button_rows]}, "bot_appid": 0}}
|
|
223
|
+
|
|
224
|
+
def to_json(self) -> dict:
|
|
225
|
+
return self.content
|
|
226
|
+
|
|
227
|
+
def __str__(self) -> str:
|
|
228
|
+
return f"[自定按键版]"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class MarkdownContent:
|
|
232
|
+
def __init__(self, raw_content: str):
|
|
233
|
+
self.content = (
|
|
234
|
+
raw_content
|
|
235
|
+
.replace('"', r'\\\"')
|
|
236
|
+
.replace(r"\`", r"\`")
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class MarkDown:
|
|
241
|
+
def __init__(self, content: MarkdownContent):
|
|
242
|
+
self.content = {"type": "markdown",
|
|
243
|
+
"data": {"content": json.dumps({"content": content.content}).replace('"', '"')}}
|
|
244
|
+
|
|
245
|
+
def set(self, content: MarkdownContent) -> None:
|
|
246
|
+
self.__init__(content)
|
|
247
|
+
|
|
248
|
+
def to_json(self) -> dict:
|
|
249
|
+
return self.content
|
|
250
|
+
|
|
251
|
+
def __str__(self) -> str:
|
|
252
|
+
return f"[MarkDown]"
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@segment_builder("longmsg", "[<id>]")
|
|
256
|
+
class LongMessage(Base):
|
|
257
|
+
id: str
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@segment_builder("json", "[Json]")
|
|
261
|
+
class Json(Base):
|
|
262
|
+
data: typing.Union[dict, list, str]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@segment_builder("mface", "[表情]")
|
|
266
|
+
class MarketFace(Base):
|
|
267
|
+
face_id: str
|
|
268
|
+
tab_id: str
|
|
269
|
+
key: str
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@segment_builder("dice", "[骰子]")
|
|
273
|
+
class Dice(Base):
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@segment_builder("rps", "[猜拳]")
|
|
278
|
+
class Rps(Base):
|
|
279
|
+
pass
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@segment_builder("music", "[音乐]")
|
|
283
|
+
class Music(Base):
|
|
284
|
+
type: str
|
|
285
|
+
id: str = None
|
|
286
|
+
url: str = None
|
|
287
|
+
audio: str = None
|
|
288
|
+
title: str = None
|
|
289
|
+
|
Hyper/WordSafety.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import jieba
|
|
2
|
+
import logging
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from Hyper import Logic
|
|
6
|
+
|
|
7
|
+
logging.getLogger("jieba").setLevel(logging.ERROR)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Result:
|
|
11
|
+
def __init__(self, message: str, result: bool):
|
|
12
|
+
self.message = message
|
|
13
|
+
self.result = result
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AsyncChecker:
|
|
17
|
+
def __init__(self, hot_words: list[dict[str, str]]):
|
|
18
|
+
self.hot_words = hot_words
|
|
19
|
+
|
|
20
|
+
async def check(self, text: list) -> Result:
|
|
21
|
+
for j in self.hot_words:
|
|
22
|
+
if j["word"] in text:
|
|
23
|
+
ret = Result(f"检测到类型为{j['type']}的敏感用词", False)
|
|
24
|
+
return ret
|
|
25
|
+
|
|
26
|
+
ret = Result("未检出敏感词", True)
|
|
27
|
+
return ret
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
with open("assets/dict.txt", "r", encoding="utf-8") as f:
|
|
31
|
+
words: list[dict[str, str]] = []
|
|
32
|
+
word = f.read()
|
|
33
|
+
word = word.split("\n")
|
|
34
|
+
inner_words: list[AsyncChecker] = []
|
|
35
|
+
tmp: list[dict[str, str]] = []
|
|
36
|
+
for i in word:
|
|
37
|
+
temp = i.split(" ")
|
|
38
|
+
words.append({"word": temp[0], "type": temp[1]})
|
|
39
|
+
tmp.append({"word": temp[0], "type": temp[1]})
|
|
40
|
+
if len(tmp) == 50:
|
|
41
|
+
inner_words.append(AsyncChecker(tmp))
|
|
42
|
+
tmp = []
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@Logic.Cacher().cache
|
|
46
|
+
def check(text: str) -> Result:
|
|
47
|
+
texts = jieba.lcut(text)
|
|
48
|
+
for j in words:
|
|
49
|
+
if j["word"] in texts:
|
|
50
|
+
ret = Result(f"检测到类型为{j['type']}的敏感用词", False)
|
|
51
|
+
return ret
|
|
52
|
+
|
|
53
|
+
ret = Result("未检出敏感词", True)
|
|
54
|
+
return ret
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@Logic.Cacher().cache
|
|
58
|
+
async def check_async(text: str) -> Result:
|
|
59
|
+
texts = jieba.lcut(text)
|
|
60
|
+
__tasks: list[asyncio.Task] = []
|
|
61
|
+
for j in inner_words:
|
|
62
|
+
__tasks.append(asyncio.create_task(j.check(texts)))
|
|
63
|
+
await asyncio.gather(*__tasks)
|
|
64
|
+
for j in __tasks:
|
|
65
|
+
if not j.result().result:
|
|
66
|
+
return j.result()
|
|
67
|
+
ret = Result("未检出敏感词", True)
|
|
68
|
+
return ret
|