brominecore 0.1__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.
- brcore/__init__.py +5 -0
- brcore/core.py +365 -0
- brominecore-0.1.dist-info/LICENSE +21 -0
- brominecore-0.1.dist-info/METADATA +107 -0
- brominecore-0.1.dist-info/RECORD +7 -0
- brominecore-0.1.dist-info/WHEEL +5 -0
- brominecore-0.1.dist-info/top_level.txt +1 -0
brcore/__init__.py
ADDED
brcore/core.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import asyncio
|
|
3
|
+
import uuid
|
|
4
|
+
import logging
|
|
5
|
+
from functools import partial
|
|
6
|
+
from typing import Any, Callable, NoReturn, Optional, Union, Coroutine
|
|
7
|
+
|
|
8
|
+
import websockets
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["Bromine"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _BackgroundTasks(set):
|
|
15
|
+
"""`runner`のバックグラウンド実行するタスクの管理をする集合"""
|
|
16
|
+
def add(self, element: asyncio.Task) -> None:
|
|
17
|
+
if type(element) is asyncio.Task:
|
|
18
|
+
element.add_done_callback(self.discard)
|
|
19
|
+
return super().add(element)
|
|
20
|
+
else:
|
|
21
|
+
raise TypeError("element is not asyncio.Task")
|
|
22
|
+
|
|
23
|
+
def tasks_cancel(self) -> None:
|
|
24
|
+
"""タスク達をキャンセル"""
|
|
25
|
+
for i in self:
|
|
26
|
+
i.cancel()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Bromine:
|
|
30
|
+
"""misskeyのwebsocketAPIを使いやすくしたクラス
|
|
31
|
+
|
|
32
|
+
websocketの実装を一々作らなくても簡単にwebsocketの通信ができるようになります
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
instance: str
|
|
37
|
+
インスタンス名
|
|
38
|
+
token: :obj:`str`, optional
|
|
39
|
+
トークン"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, instance: str, token: Optional[str] = None) -> None:
|
|
42
|
+
self.__COOL_TIME = 5
|
|
43
|
+
|
|
44
|
+
# 値を保持するキューとか
|
|
45
|
+
# uuid:[channelname, awaitablefunc, params]
|
|
46
|
+
self.__channels: dict[str, tuple[str, Callable[[dict[str, Any]], Coroutine[Any, Any, None]], dict[str, Any]]] = {}
|
|
47
|
+
# uuid:tuple[isblock, awaitablefunc]
|
|
48
|
+
self.__on_comebacks: dict[str, tuple[bool, Callable[[], Coroutine[Any, Any, None]]]] = {}
|
|
49
|
+
# send_queueはここで作るとエラーが出るので型ヒントのみ
|
|
50
|
+
self.__send_queue: asyncio.Queue[tuple[str, dict]]
|
|
51
|
+
|
|
52
|
+
# 実行中かどうかの変数
|
|
53
|
+
self.__is_running: bool = False
|
|
54
|
+
|
|
55
|
+
# websocketのURL
|
|
56
|
+
if token is not None:
|
|
57
|
+
self.__WS_URL = f'wss://{instance}/streaming?i={token}'
|
|
58
|
+
else:
|
|
59
|
+
# トークンがないときはパラメーターを消しとく
|
|
60
|
+
self.__WS_URL = f'wss://{instance}/streaming'
|
|
61
|
+
|
|
62
|
+
# logger作成
|
|
63
|
+
self.__logger = logging.getLogger("Bromine")
|
|
64
|
+
# logを簡単にできるよう部分適用する
|
|
65
|
+
self.__log = partial(self.__logger.log, logging.DEBUG)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def loglevel(self) -> int:
|
|
69
|
+
"""現在のログレベル"""
|
|
70
|
+
return self.__log.args[0]
|
|
71
|
+
|
|
72
|
+
@loglevel.setter
|
|
73
|
+
def loglevel(self, level: int) -> None:
|
|
74
|
+
self.__log = partial(self.__logger.log, level)
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def cooltime(self) -> int:
|
|
78
|
+
"""websocketの接続が切れた時に再接続まで待つ時間"""
|
|
79
|
+
return self.__COOL_TIME
|
|
80
|
+
|
|
81
|
+
@cooltime.setter
|
|
82
|
+
def cooltime(self, time: int) -> None:
|
|
83
|
+
if time > 0:
|
|
84
|
+
self.__COOL_TIME = time
|
|
85
|
+
else:
|
|
86
|
+
ValueError("負の値です")
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def is_running(self) -> bool:
|
|
90
|
+
"""メイン関数が実行中かどうか"""
|
|
91
|
+
return self.__is_running
|
|
92
|
+
|
|
93
|
+
async def main(self) -> NoReturn:
|
|
94
|
+
"""開始する関数"""
|
|
95
|
+
self.__log("start main.")
|
|
96
|
+
# send_queueをinitで作るとattached to a different loopとかいうゴミでるのでここで宣言
|
|
97
|
+
self.__send_queue = asyncio.Queue()
|
|
98
|
+
self.__is_running = True
|
|
99
|
+
# バックグラウンドタスクの集合
|
|
100
|
+
backgrounds = _BackgroundTasks()
|
|
101
|
+
try:
|
|
102
|
+
await asyncio.create_task(self.__runner(backgrounds))
|
|
103
|
+
finally:
|
|
104
|
+
backgrounds.tasks_cancel()
|
|
105
|
+
self.__is_running = False
|
|
106
|
+
self.__log("finish main.")
|
|
107
|
+
|
|
108
|
+
async def __runner(self, background_tasks: _BackgroundTasks) -> NoReturn:
|
|
109
|
+
"""websocketとの交信を行うメインdaemon"""
|
|
110
|
+
# 何回連続で接続に失敗したかのカウンター
|
|
111
|
+
connect_fail_count = 0
|
|
112
|
+
# この変数たちは最初に接続失敗すると未定義になるから保険のため
|
|
113
|
+
# websocket_daemon(__ws_send_d)
|
|
114
|
+
wsd: Union[None, asyncio.Task] = None
|
|
115
|
+
# comebacks(asyncio.gather)
|
|
116
|
+
comebacks: Union[None, asyncio.Future] = None
|
|
117
|
+
while True:
|
|
118
|
+
try:
|
|
119
|
+
async with websockets.connect(self.__WS_URL) as ws:
|
|
120
|
+
# ちゃんと通ってるかpingで確認
|
|
121
|
+
ping_wait = await ws.ping()
|
|
122
|
+
pong_latency = await ping_wait
|
|
123
|
+
self.__log(f"websocket connect success. latency: {pong_latency}s")
|
|
124
|
+
|
|
125
|
+
# 送るdaemonの作成
|
|
126
|
+
wsd = asyncio.create_task(self.__ws_send_d(ws))
|
|
127
|
+
|
|
128
|
+
# comebacksの処理
|
|
129
|
+
cmbs: list[Coroutine[Any, Any, None]] = []
|
|
130
|
+
for i in self.__on_comebacks.values():
|
|
131
|
+
if i[0]:
|
|
132
|
+
# もしブロックしなければいけないcomebackなら待つ
|
|
133
|
+
await i[1]()
|
|
134
|
+
else:
|
|
135
|
+
# でなければ後で処理する
|
|
136
|
+
cmbs.append(i[1]())
|
|
137
|
+
if cmbs != []:
|
|
138
|
+
# 全部一気にgatherで管理
|
|
139
|
+
comebacks = asyncio.gather(*cmbs, return_exceptions=True)
|
|
140
|
+
|
|
141
|
+
# 接続に成功したということでfail_countを0に
|
|
142
|
+
connect_fail_count = 0
|
|
143
|
+
while True:
|
|
144
|
+
# データ受け取り
|
|
145
|
+
data = json.loads(await ws.recv())
|
|
146
|
+
if data['type'] == 'channel':
|
|
147
|
+
for i, v in self.__channels.items():
|
|
148
|
+
if data["body"]["id"] == i:
|
|
149
|
+
background_tasks.add(asyncio.create_task(v[1](data["body"])))
|
|
150
|
+
break
|
|
151
|
+
else:
|
|
152
|
+
self.__log("data come from unknown channel")
|
|
153
|
+
else:
|
|
154
|
+
# たまにchannel以外から来ることがある(謎)
|
|
155
|
+
self.__log(f"data come from not channel, datatype[{data['type']}]")
|
|
156
|
+
|
|
157
|
+
except (
|
|
158
|
+
asyncio.exceptions.TimeoutError,
|
|
159
|
+
websockets.exceptions.ConnectionClosed,
|
|
160
|
+
websockets.exceptions.ConnectionClosedError,
|
|
161
|
+
websockets.exceptions.ConnectionClosedOK,
|
|
162
|
+
) as e:
|
|
163
|
+
# websocketが死んだりタイムアウトした時の処理
|
|
164
|
+
self.__log(f"error occured:{e}")
|
|
165
|
+
connect_fail_count += 1
|
|
166
|
+
await asyncio.sleep(self.__COOL_TIME)
|
|
167
|
+
if connect_fail_count > 5:
|
|
168
|
+
# Todo: 例外を投げる?
|
|
169
|
+
# 5回以上連続で失敗したとき長く寝るようにする
|
|
170
|
+
# とりあえず30待つようにする
|
|
171
|
+
await asyncio.sleep(30)
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
# 予定外のエラー発生時。
|
|
175
|
+
self.__log(f"fatal Error:{type(e)}, args:{e.args}")
|
|
176
|
+
raise e
|
|
177
|
+
|
|
178
|
+
finally:
|
|
179
|
+
# 再接続する際、いろいろ初期化する
|
|
180
|
+
if type(wsd) is asyncio.Task:
|
|
181
|
+
# __ws_send_dを止める
|
|
182
|
+
wsd.cancel()
|
|
183
|
+
try:
|
|
184
|
+
await wsd
|
|
185
|
+
except asyncio.CancelledError:
|
|
186
|
+
pass
|
|
187
|
+
wsd = None
|
|
188
|
+
if comebacks is not None:
|
|
189
|
+
# ブロックしないcomebacksがもし生きていたら殺す
|
|
190
|
+
comebacks.cancel()
|
|
191
|
+
try:
|
|
192
|
+
await comebacks
|
|
193
|
+
except asyncio.CancelledError:
|
|
194
|
+
pass
|
|
195
|
+
comebacks = None
|
|
196
|
+
|
|
197
|
+
def add_comeback(self,
|
|
198
|
+
func: Callable[[], Coroutine[Any, Any, None]],
|
|
199
|
+
block: bool = False,
|
|
200
|
+
id: Optional[str] = None) -> str:
|
|
201
|
+
"""comebackを作る関数
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
func: CoroutineFunction
|
|
206
|
+
comeback時に実行する非同期関数
|
|
207
|
+
block: bool
|
|
208
|
+
websocketとの交信をブロッキングして実行するか
|
|
209
|
+
id: :obj:`str`, optional
|
|
210
|
+
識別id、ない場合自動生成される
|
|
211
|
+
|
|
212
|
+
Returns
|
|
213
|
+
-------
|
|
214
|
+
str
|
|
215
|
+
識別id
|
|
216
|
+
|
|
217
|
+
Raises
|
|
218
|
+
------
|
|
219
|
+
ValueError
|
|
220
|
+
非同期関数funcがcoroutinefunctionでない時
|
|
221
|
+
|
|
222
|
+
Note
|
|
223
|
+
----
|
|
224
|
+
返り値の識別idはdel_comebackで使用します"""
|
|
225
|
+
if id is None:
|
|
226
|
+
# もしIDがない時生成する
|
|
227
|
+
id = uuid.uuid4()
|
|
228
|
+
if not asyncio.iscoroutinefunction(func):
|
|
229
|
+
raise ValueError("非同期関数funcがcoroutinefunctionではありません。")
|
|
230
|
+
self.__on_comebacks[id] = (block, func)
|
|
231
|
+
return id
|
|
232
|
+
|
|
233
|
+
def del_comeback(self, id: str) -> None:
|
|
234
|
+
"""comeback消すやつ
|
|
235
|
+
|
|
236
|
+
Parameter
|
|
237
|
+
---------
|
|
238
|
+
id: str
|
|
239
|
+
識別id
|
|
240
|
+
|
|
241
|
+
Raises
|
|
242
|
+
------
|
|
243
|
+
KeyError
|
|
244
|
+
識別idが不適のとき"""
|
|
245
|
+
self.__on_comebacks.pop(id)
|
|
246
|
+
|
|
247
|
+
async def __ws_send_d(self, ws: websockets.WebSocketClientProtocol) -> NoReturn:
|
|
248
|
+
"""websocketを送るdaemon"""
|
|
249
|
+
# すでに接続済みのchannelにconnectしたりしないようにするやつ
|
|
250
|
+
already_connected_ids: set[str] = set()
|
|
251
|
+
# まずはchannelsの再接続から始める
|
|
252
|
+
for i, v in self.__channels.items():
|
|
253
|
+
already_connected_ids.add(i)
|
|
254
|
+
await ws.send(json.dumps({
|
|
255
|
+
"type": "connect",
|
|
256
|
+
"body": {
|
|
257
|
+
"channel": v[0],
|
|
258
|
+
"id": i,
|
|
259
|
+
"params": v[2]
|
|
260
|
+
}
|
|
261
|
+
}))
|
|
262
|
+
|
|
263
|
+
# queueの初期化
|
|
264
|
+
while not self.__send_queue.empty():
|
|
265
|
+
type_, body_ = await self.__send_queue.get()
|
|
266
|
+
if type_ == "connect":
|
|
267
|
+
if body_["id"] in already_connected_ids:
|
|
268
|
+
# もうすでに送ったやつなので送らない
|
|
269
|
+
continue
|
|
270
|
+
else:
|
|
271
|
+
# 追加する
|
|
272
|
+
already_connected_ids.add(body_["id"])
|
|
273
|
+
|
|
274
|
+
await ws.send(json.dumps({
|
|
275
|
+
"type": type_,
|
|
276
|
+
"body": body_
|
|
277
|
+
}))
|
|
278
|
+
|
|
279
|
+
# あとはずっとqueueからgetしてそれを送る。
|
|
280
|
+
while True:
|
|
281
|
+
type_, body_ = await self.__send_queue.get()
|
|
282
|
+
await ws.send(json.dumps({
|
|
283
|
+
"type": type_,
|
|
284
|
+
"body": body_
|
|
285
|
+
}))
|
|
286
|
+
|
|
287
|
+
def ws_send(self, type: str, body: dict[str, Any]) -> None:
|
|
288
|
+
"""ウェブソケットへ送るキューに情報を追加するやつ
|
|
289
|
+
|
|
290
|
+
Parameters
|
|
291
|
+
----------
|
|
292
|
+
type: str
|
|
293
|
+
type情報
|
|
294
|
+
body: dict[str, Any]
|
|
295
|
+
body情報"""
|
|
296
|
+
self.__send_queue.put_nowait((type, body))
|
|
297
|
+
|
|
298
|
+
def ws_connect(self,
|
|
299
|
+
channel: str,
|
|
300
|
+
func: Callable[[dict[str, Any]], Coroutine[Any, Any, None]],
|
|
301
|
+
id: Optional[str] = None,
|
|
302
|
+
**params: Any) -> str:
|
|
303
|
+
"""channelに接続する関数
|
|
304
|
+
|
|
305
|
+
Parameters
|
|
306
|
+
----------
|
|
307
|
+
channel: str
|
|
308
|
+
チャンネル名
|
|
309
|
+
func: CoroutineFunction
|
|
310
|
+
反応があった時に実行される非同期関数
|
|
311
|
+
id: :obj:`str`, optional
|
|
312
|
+
識別id、もし指定されていない場合、自動生成される
|
|
313
|
+
**params: Any
|
|
314
|
+
接続する際のパラメーター
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
str
|
|
319
|
+
識別id
|
|
320
|
+
|
|
321
|
+
Raises
|
|
322
|
+
-------
|
|
323
|
+
ValueError
|
|
324
|
+
非同期関数funcがcoroutinefunctionでない時
|
|
325
|
+
|
|
326
|
+
Note
|
|
327
|
+
----
|
|
328
|
+
返り値の識別idはws_disconnectで使用します"""
|
|
329
|
+
if not asyncio.iscoroutinefunction(func):
|
|
330
|
+
raise ValueError("非同期関数funcがcoroutinefunctionではありません。")
|
|
331
|
+
if id is None:
|
|
332
|
+
# idがなかったら自動生成
|
|
333
|
+
id = str(uuid.uuid4())
|
|
334
|
+
# channelsに追加
|
|
335
|
+
self.__channels[id] = (channel, func, params)
|
|
336
|
+
body = {
|
|
337
|
+
"channel": channel,
|
|
338
|
+
"id": id,
|
|
339
|
+
"params": params
|
|
340
|
+
}
|
|
341
|
+
if self.__is_running:
|
|
342
|
+
# もしsend_queueがある時(実行中の時)
|
|
343
|
+
self.ws_send("connect", body)
|
|
344
|
+
self.__log(f"connect channel: {channel}, id: {id}")
|
|
345
|
+
else:
|
|
346
|
+
# ない時(実行前)
|
|
347
|
+
self.__log(f"connect channel before run: {channel}, id: {id}")
|
|
348
|
+
return id
|
|
349
|
+
|
|
350
|
+
def ws_disconnect(self, id: str) -> None:
|
|
351
|
+
"""チャンネルを接続解除する関数
|
|
352
|
+
|
|
353
|
+
Parameters
|
|
354
|
+
----------
|
|
355
|
+
id: str
|
|
356
|
+
識別id
|
|
357
|
+
|
|
358
|
+
Raises
|
|
359
|
+
------
|
|
360
|
+
KeyError
|
|
361
|
+
識別idが不適のとき"""
|
|
362
|
+
channel = self.__channels.pop(id)[0]
|
|
363
|
+
body = {"id": id}
|
|
364
|
+
self.ws_send("disconnect", body)
|
|
365
|
+
self.__log(f"disconnect channel: {channel}, id: {id}")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 35enidoi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: brominecore
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: Misskey websocketAPI wrapper.
|
|
5
|
+
Author: iodine53
|
|
6
|
+
Maintainer: iodine53
|
|
7
|
+
License: MIT License
|
|
8
|
+
Project-URL: Repository, https://github.com/35enidoi/BromineCore
|
|
9
|
+
Project-URL: Issues, https://github.com/35enidoi/BromineCore/issues
|
|
10
|
+
Keywords: misskey
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: websockets
|
|
20
|
+
|
|
21
|
+
# BromineCore
|
|
22
|
+
[ぶろみね](https://github.com/35enidoi/bromine35bot)くんのコア部分の実装、そしてmisskeyのwebsocketAPI単体の実装です。
|
|
23
|
+
一々websocketの実装を作らなくても良くなります!
|
|
24
|
+
|
|
25
|
+
ローカルのノートを講読したり、通知を取得したり。リバーシも頑張れば実装できます。
|
|
26
|
+
|
|
27
|
+
何か問題が発生したり追加してほしい機能があったらissueに書いてください
|
|
28
|
+
頑張って実装したり解決します
|
|
29
|
+
# Example
|
|
30
|
+
簡単なタイムライン閲覧クライアントです。
|
|
31
|
+
トークン無しでタイムラインをリアルタイムで閲覧できます。
|
|
32
|
+
```py
|
|
33
|
+
import asyncio
|
|
34
|
+
from brcore import Bromine
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
INSTANCE = "misskey.io"
|
|
38
|
+
TL = "localTimeline"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def note_printer(note: dict) -> None:
|
|
42
|
+
"""ノートの情報を受け取って表示する関数"""
|
|
43
|
+
NOBASIBOU_LENGTH = 20
|
|
44
|
+
user = note["user"]
|
|
45
|
+
username = user["name"] if user["name"] is not None else user["username"]
|
|
46
|
+
print("-"*NOBASIBOU_LENGTH)
|
|
47
|
+
if note.get("renoteId") and note["text"] is None:
|
|
48
|
+
# リノートのときはリノート先だけ書く
|
|
49
|
+
print(f"{username}がリノート")
|
|
50
|
+
note_printer(note["renote"])
|
|
51
|
+
# リノートはリアクション数とか書きたくないので
|
|
52
|
+
# ここで返す
|
|
53
|
+
print("-"*NOBASIBOU_LENGTH)
|
|
54
|
+
return
|
|
55
|
+
else:
|
|
56
|
+
# 普通のノート
|
|
57
|
+
print(f"{username}がノート ノートid: {note['id']}")
|
|
58
|
+
if note.get("reply"):
|
|
59
|
+
# リプライがある場合
|
|
60
|
+
print("リプライ:")
|
|
61
|
+
note_printer(note["reply"])
|
|
62
|
+
if note.get("text"):
|
|
63
|
+
print("テキスト:")
|
|
64
|
+
print(note["text"])
|
|
65
|
+
if note.get("renoteId"):
|
|
66
|
+
# 引用
|
|
67
|
+
print("引用:")
|
|
68
|
+
note_printer(note["renote"])
|
|
69
|
+
if len(note["files"]) != 0:
|
|
70
|
+
# ファイルがある時
|
|
71
|
+
print(f"ファイル数: {len(note['files'])}")
|
|
72
|
+
# リアクションとかを書く
|
|
73
|
+
print(f"リプライ数: {note['repliesCount']}, リノート数: {note['renoteCount']}, リアクション数: {note['reactionCount']}")
|
|
74
|
+
reactions = []
|
|
75
|
+
for reactionid, val in note["reactions"].items():
|
|
76
|
+
if reactionid[-3:] == "@.:":
|
|
77
|
+
# ローカルのカスタム絵文字のidはへんなのついてるので
|
|
78
|
+
# それを消す
|
|
79
|
+
reactionid = reactionid[:-3] + ":"
|
|
80
|
+
reactions.append(f"({reactionid}, {val})")
|
|
81
|
+
if len(reactions) != 0:
|
|
82
|
+
print("リアクション達: ", ", ".join(reactions))
|
|
83
|
+
print("-"*NOBASIBOU_LENGTH)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def note_async(note: dict) -> None:
|
|
87
|
+
"""上のprinterの引数を調整するやつ
|
|
88
|
+
|
|
89
|
+
asyncにするのはws_connectでは非同期関数が求められるので(見た目非同期っていう体にしているだけ)"""
|
|
90
|
+
note_printer(note["body"])
|
|
91
|
+
print() # 空白をノート後に入れておく
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def main() -> None:
|
|
95
|
+
brm = Bromine(instance=INSTANCE)
|
|
96
|
+
brm.ws_connect(TL, note_async)
|
|
97
|
+
print("start...")
|
|
98
|
+
await brm.main()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
try:
|
|
103
|
+
asyncio.run(main())
|
|
104
|
+
except KeyboardInterrupt:
|
|
105
|
+
print("fin")
|
|
106
|
+
|
|
107
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
brcore/__init__.py,sha256=vbZ2-tcnFCu14zO9FuKqMgt7HyEEr3xrFAKqTVSCcQY,64
|
|
2
|
+
brcore/core.py,sha256=4nZ64agGSUwmYrS_XMtxRymcHu4i0kqhZbRmH8Z2zmU,13070
|
|
3
|
+
brominecore-0.1.dist-info/LICENSE,sha256=9z-qttWTySG7CeV72m9XyX8AEO-VG-xogWlFLQVLSQw,1065
|
|
4
|
+
brominecore-0.1.dist-info/METADATA,sha256=_1yRgCtFKssjSKf22HEy0bLNWa_5C_nrM4TWzKaSOt8,3806
|
|
5
|
+
brominecore-0.1.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
|
6
|
+
brominecore-0.1.dist-info/top_level.txt,sha256=q-RFjELVjiz1gXVNoUP1MamVSQBUjEd5QZ3swzSU5X8,7
|
|
7
|
+
brominecore-0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
brcore
|