PyPtt 1.3.3__tar.gz → 1.4.0.dev58619__tar.gz
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.
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PKG-INFO +5 -6
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/__init__.py +2 -2
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/connect_core.py +139 -136
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/ssl_config.py +9 -9
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt.egg-info/PKG-INFO +5 -6
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/README.md +2 -1
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/setup.py +2 -4
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/LICENSE +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/MANIFEST.in +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/PTT.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_bucket.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_call_status.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_change_pw.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_comment.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_del_post.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_board_info.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_board_list.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_bottom_post_list.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_favourite_board.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_newest_index.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_post.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_post_index.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_post_list.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_time.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_user.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_give_money.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_has_new_mail.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_loginout.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_mail.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_mark_post.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_post.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_reply_post.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_search_user.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_set_board_title.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_util.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/check_value.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/command.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/config.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/data_type.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/exceptions.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/i18n.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/lang_en_US.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/lang_zh_TW.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/lib_util.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/log.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/screens.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/service.py +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt.egg-info/SOURCES.txt +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt.egg-info/dependency_links.txt +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt.egg-info/requires.txt +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt.egg-info/top_level.txt +0 -0
- {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPtt
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0.dev58619
|
|
4
4
|
Summary: PyPtt
|
|
5
5
|
Home-page: https://pyptt.cc/
|
|
6
6
|
Author: CodingMan
|
|
@@ -13,16 +13,14 @@ Classifier: Topic :: Communications :: BBS
|
|
|
13
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
14
|
Classifier: Topic :: Internet
|
|
15
15
|
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
20
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
21
|
Classifier: Natural Language :: Chinese (Traditional)
|
|
24
22
|
Classifier: Natural Language :: English
|
|
25
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.11
|
|
26
24
|
Description-Content-Type: text/markdown
|
|
27
25
|
License-File: LICENSE
|
|
28
26
|
Requires-Dist: progressbar2
|
|
@@ -65,10 +63,11 @@ PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了完整
|
|
|
65
63
|
- 其他功能: 即時訊息(水球)、P幣轉帳
|
|
66
64
|
|
|
67
65
|
### 系統需求
|
|
68
|
-
- Python 3.
|
|
66
|
+
- Python 3.11 以上
|
|
69
67
|
- 非雲端環境 (因 PTT 封鎖雲端 IP)
|
|
70
68
|
|
|
71
69
|
### 快速開始
|
|
70
|
+
|
|
72
71
|
```bash
|
|
73
72
|
pip install PyPtt
|
|
74
73
|
```
|
|
@@ -4,7 +4,6 @@ import asyncio
|
|
|
4
4
|
import os.path
|
|
5
5
|
import ssl
|
|
6
6
|
import tempfile
|
|
7
|
-
import threading
|
|
8
7
|
import time
|
|
9
8
|
import traceback
|
|
10
9
|
import warnings
|
|
@@ -132,22 +131,6 @@ class TargetUnit:
|
|
|
132
131
|
return self._secret
|
|
133
132
|
|
|
134
133
|
|
|
135
|
-
class RecvData:
|
|
136
|
-
def __init__(self):
|
|
137
|
-
self.data = None
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
async def websocket_recv_func(core, recv_data_obj):
|
|
141
|
-
recv_data_obj.data = await core.recv()
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
async def websocket_receiver(core, screen_timeout, recv_data_obj):
|
|
145
|
-
# Wait for at most 1 second
|
|
146
|
-
await asyncio.wait_for(
|
|
147
|
-
websocket_recv_func(core, recv_data_obj),
|
|
148
|
-
timeout=screen_timeout)
|
|
149
|
-
|
|
150
|
-
|
|
151
134
|
class ReceiveDataQueue(object):
|
|
152
135
|
def __init__(self):
|
|
153
136
|
self._ReceiveDataQueue = []
|
|
@@ -169,6 +152,18 @@ class API(object):
|
|
|
169
152
|
self._RDQ = ReceiveDataQueue()
|
|
170
153
|
self._UseTooManyResources = TargetUnit(screens.Target.use_too_many_resources,
|
|
171
154
|
exceptions_=exceptions.UseTooManyResources())
|
|
155
|
+
self._loop = None
|
|
156
|
+
|
|
157
|
+
def _get_event_loop(self):
|
|
158
|
+
if self._loop and not self._loop.is_closed():
|
|
159
|
+
return self._loop
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
self._loop = asyncio.get_running_loop()
|
|
163
|
+
except RuntimeError:
|
|
164
|
+
self._loop = asyncio.new_event_loop()
|
|
165
|
+
asyncio.set_event_loop(self._loop)
|
|
166
|
+
return self._loop
|
|
172
167
|
|
|
173
168
|
def connect(self) -> None:
|
|
174
169
|
def _wait():
|
|
@@ -188,43 +183,34 @@ class API(object):
|
|
|
188
183
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
189
184
|
|
|
190
185
|
self.current_encoding = 'big5uao'
|
|
191
|
-
# self.log.py.info(i18n.connect_core, i18n.active)
|
|
192
186
|
|
|
193
187
|
if self.config.host == data_type.HOST.PTT1:
|
|
194
|
-
telnet_host = 'ptt.cc'
|
|
195
188
|
websocket_host = 'wss://ws.ptt.cc/bbs/'
|
|
196
189
|
websocket_origin = 'https://term.ptt.cc'
|
|
197
190
|
elif self.config.host == data_type.HOST.PTT2:
|
|
198
|
-
telnet_host = 'ptt2.cc'
|
|
199
191
|
websocket_host = 'wss://ws.ptt2.cc/bbs/'
|
|
200
192
|
websocket_origin = 'https://term.ptt2.cc'
|
|
201
193
|
elif self.config.host == data_type.HOST.LOCALHOST:
|
|
202
|
-
telnet_host = 'localhost'
|
|
203
194
|
websocket_host = 'wss://localhost'
|
|
204
195
|
websocket_origin = 'https://term.ptt.cc'
|
|
205
196
|
else:
|
|
206
|
-
telnet_host = self.config.host
|
|
207
197
|
websocket_host = f'wss://{self.config.host}'
|
|
208
198
|
websocket_origin = 'https://term.ptt.cc'
|
|
209
199
|
|
|
210
200
|
connect_success = False
|
|
201
|
+
loop = self._get_event_loop()
|
|
211
202
|
|
|
212
203
|
for _ in range(2):
|
|
213
|
-
|
|
214
204
|
try:
|
|
215
|
-
if threading.current_thread() is not threading.main_thread():
|
|
216
|
-
loop = asyncio.new_event_loop()
|
|
217
|
-
asyncio.set_event_loop(loop)
|
|
218
|
-
|
|
219
205
|
log.logger.debug('USER_AGENT',
|
|
220
206
|
websockets.http11.USER_AGENT if use_http11 else websockets.http.USER_AGENT)
|
|
221
|
-
self._core =
|
|
207
|
+
self._core = loop.run_until_complete(
|
|
222
208
|
websockets.connect(
|
|
223
209
|
websocket_host,
|
|
224
210
|
origin=websocket_origin,
|
|
225
211
|
ssl=ssl_context))
|
|
226
|
-
|
|
227
212
|
connect_success = True
|
|
213
|
+
break
|
|
228
214
|
except Exception as e:
|
|
229
215
|
traceback.print_tb(e.__traceback__)
|
|
230
216
|
print(e)
|
|
@@ -241,8 +227,6 @@ class API(object):
|
|
|
241
227
|
_wait()
|
|
242
228
|
continue
|
|
243
229
|
|
|
244
|
-
break
|
|
245
|
-
|
|
246
230
|
if not connect_success:
|
|
247
231
|
raise exceptions.ConnectError(self.config)
|
|
248
232
|
|
|
@@ -265,7 +249,6 @@ class API(object):
|
|
|
265
249
|
self._RDQ.add(screen)
|
|
266
250
|
if target == self._UseTooManyResources:
|
|
267
251
|
use_too_many_res = True
|
|
268
|
-
# print(f'1 {use_too_many_res}')
|
|
269
252
|
break
|
|
270
253
|
target.raise_exception()
|
|
271
254
|
|
|
@@ -286,150 +269,170 @@ class API(object):
|
|
|
286
269
|
elif refresh:
|
|
287
270
|
add_refresh = True
|
|
288
271
|
|
|
289
|
-
if add_refresh:
|
|
290
|
-
|
|
291
|
-
msg = msg + command.refresh
|
|
272
|
+
if add_refresh and not msg.endswith(command.refresh):
|
|
273
|
+
msg += command.refresh
|
|
292
274
|
|
|
293
275
|
is_secret = target.is_secret()
|
|
294
276
|
|
|
295
277
|
if target.is_break_after_send():
|
|
296
|
-
# break_index = target_list.index(target)
|
|
297
278
|
break_detect_after_send = True
|
|
298
279
|
break
|
|
299
280
|
return screen, find_target, is_secret, break_detect_after_send, use_too_many_res, msg, target_index
|
|
300
281
|
|
|
301
|
-
def
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if not all(isinstance(T, TargetUnit) for T in target_list):
|
|
305
|
-
raise exceptions.ParameterError('Item of TargetList must be TargetUnit')
|
|
306
|
-
|
|
307
|
-
if self._UseTooManyResources not in target_list:
|
|
308
|
-
target_list.append(self._UseTooManyResources)
|
|
309
|
-
|
|
310
|
-
if screen_timeout == 0:
|
|
311
|
-
current_screen_timeout = self.config.screen_timeout
|
|
312
|
-
else:
|
|
313
|
-
current_screen_timeout = screen_timeout
|
|
314
|
-
|
|
315
|
-
break_detect_after_send = False
|
|
282
|
+
async def _async_send(self, msg: str, target_list: list, screen_timeout: int, refresh: bool, secret: bool) -> int:
|
|
283
|
+
current_screen_timeout = self.config.screen_timeout if screen_timeout == 0 else screen_timeout
|
|
316
284
|
is_secret = secret
|
|
317
|
-
|
|
285
|
+
break_detect_after_send = False
|
|
318
286
|
use_too_many_res = False
|
|
319
|
-
while True:
|
|
320
287
|
|
|
321
|
-
|
|
322
|
-
|
|
288
|
+
while True:
|
|
289
|
+
if refresh and msg and not msg.endswith(command.refresh):
|
|
290
|
+
msg += command.refresh
|
|
323
291
|
|
|
324
292
|
try:
|
|
325
|
-
|
|
293
|
+
encoded_msg = msg.encode('utf-8', 'replace')
|
|
326
294
|
except AttributeError:
|
|
327
|
-
|
|
295
|
+
encoded_msg = msg
|
|
328
296
|
except Exception as e:
|
|
329
297
|
traceback.print_tb(e.__traceback__)
|
|
330
298
|
print(e)
|
|
331
|
-
|
|
299
|
+
encoded_msg = msg.encode('utf-8', 'replace')
|
|
332
300
|
|
|
333
301
|
if is_secret:
|
|
334
302
|
log.logger.debug(i18n.send_msg, i18n.hide_sensitive_info)
|
|
335
303
|
else:
|
|
336
|
-
log.logger.debug(i18n.send_msg, str(
|
|
304
|
+
log.logger.debug(i18n.send_msg, str(encoded_msg))
|
|
337
305
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
except EOFError:
|
|
343
|
-
raise exceptions.ConnectionClosed()
|
|
344
|
-
else:
|
|
345
|
-
try:
|
|
346
|
-
asyncio.get_event_loop().run_until_complete(
|
|
347
|
-
self._core.send(msg))
|
|
348
|
-
except websockets.exceptions.ConnectionClosedError:
|
|
349
|
-
raise exceptions.ConnectionClosed()
|
|
350
|
-
except RuntimeError:
|
|
351
|
-
raise exceptions.ConnectionClosed()
|
|
352
|
-
except websockets.exceptions.ConnectionClosedOK:
|
|
353
|
-
raise exceptions.ConnectionClosed()
|
|
306
|
+
try:
|
|
307
|
+
await self._core.send(encoded_msg)
|
|
308
|
+
except (websockets.exceptions.ConnectionClosed, websockets.exceptions.ConnectionClosedOK, RuntimeError):
|
|
309
|
+
raise exceptions.ConnectionClosed()
|
|
354
310
|
|
|
355
|
-
|
|
356
|
-
|
|
311
|
+
if break_detect_after_send:
|
|
312
|
+
return -1
|
|
357
313
|
|
|
358
314
|
msg = ''
|
|
359
315
|
receive_data_buffer = bytes()
|
|
360
|
-
|
|
361
316
|
start_time = time.time()
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
# print(1)
|
|
366
|
-
recv_data_obj = RecvData()
|
|
367
|
-
|
|
368
|
-
if self.config.connect_mode == data_type.ConnectMode.TELNET:
|
|
369
|
-
try:
|
|
370
|
-
recv_data_obj.data = self._core.read_very_eager()
|
|
371
|
-
except EOFError:
|
|
372
|
-
return -1
|
|
373
|
-
|
|
374
|
-
else:
|
|
375
|
-
try:
|
|
317
|
+
find_target = False
|
|
318
|
+
target_index = -1
|
|
376
319
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
320
|
+
try:
|
|
321
|
+
async with asyncio.timeout(current_screen_timeout):
|
|
322
|
+
while True:
|
|
323
|
+
try:
|
|
324
|
+
data_chunk = await self._core.recv()
|
|
325
|
+
except (websockets.exceptions.ConnectionClosed, websockets.exceptions.ConnectionClosedOK):
|
|
326
|
+
if use_too_many_res:
|
|
327
|
+
raise exceptions.UseTooManyResources()
|
|
328
|
+
raise exceptions.ConnectionClosed()
|
|
329
|
+
|
|
330
|
+
receive_data_buffer += data_chunk
|
|
331
|
+
|
|
332
|
+
screen, find_target, is_secret, break_detect_after_send, use_too_many_res, msg, target_index = \
|
|
333
|
+
self._decode_screen(receive_data_buffer, start_time, target_list, is_secret, refresh, msg)
|
|
334
|
+
|
|
335
|
+
if self.current_encoding == 'big5uao' and not find_target:
|
|
336
|
+
self.current_encoding = 'utf-8'
|
|
337
|
+
screen_, find_target, is_secret, break_detect_after_send, use_too_many_res, msg, target_index = \
|
|
338
|
+
self._decode_screen(receive_data_buffer, start_time, target_list, is_secret, refresh, msg)
|
|
339
|
+
if find_target:
|
|
340
|
+
screen = screen_
|
|
341
|
+
else:
|
|
342
|
+
self.current_encoding = 'big5uao'
|
|
343
|
+
|
|
344
|
+
if find_target:
|
|
345
|
+
break
|
|
346
|
+
except TimeoutError:
|
|
347
|
+
if len(receive_data_buffer) > 0:
|
|
348
|
+
vt100_p = screens.VT100Parser(receive_data_buffer, self.current_encoding)
|
|
349
|
+
screens.show(self.config, vt100_p.screen)
|
|
350
|
+
self._RDQ.add(vt100_p.screen)
|
|
351
|
+
return -1
|
|
396
352
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
screen_, find_target, is_secret, break_detect_after_send, use_too_many_res, msg, target_index = \
|
|
400
|
-
self._decode_screen(receive_data_buffer, start_time, target_list, is_secret, refresh, msg)
|
|
353
|
+
if target_index != -1:
|
|
354
|
+
return target_index
|
|
401
355
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
else:
|
|
405
|
-
self.current_encoding = 'big5uao'
|
|
356
|
+
if use_too_many_res:
|
|
357
|
+
continue
|
|
406
358
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
return target_index
|
|
359
|
+
if not find_target:
|
|
360
|
+
return -1
|
|
410
361
|
|
|
411
|
-
|
|
412
|
-
|
|
362
|
+
def send(self, msg: str, target_list: list, screen_timeout: int = 0, refresh: bool = True,
|
|
363
|
+
secret: bool = False) -> int:
|
|
413
364
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if len(screen) > 0:
|
|
417
|
-
screens.show(self.config, screen)
|
|
418
|
-
self._RDQ.add(screen)
|
|
365
|
+
if not all(isinstance(T, TargetUnit) for T in target_list):
|
|
366
|
+
raise exceptions.ParameterError('Item of TargetList must be TargetUnit')
|
|
419
367
|
|
|
420
|
-
|
|
368
|
+
if self._UseTooManyResources not in target_list:
|
|
369
|
+
target_list.append(self._UseTooManyResources)
|
|
421
370
|
|
|
371
|
+
if self.config.connect_mode == data_type.ConnectMode.TELNET:
|
|
372
|
+
# Original Telnet logic remains, as it doesn't use asyncio
|
|
373
|
+
if screen_timeout == 0:
|
|
374
|
+
current_screen_timeout = self.config.screen_timeout
|
|
375
|
+
else:
|
|
376
|
+
current_screen_timeout = screen_timeout
|
|
377
|
+
break_detect_after_send = False
|
|
378
|
+
is_secret = secret
|
|
379
|
+
use_too_many_res = False
|
|
380
|
+
while True:
|
|
381
|
+
if refresh and not msg.endswith(command.refresh):
|
|
382
|
+
msg = msg + command.refresh
|
|
383
|
+
try:
|
|
384
|
+
msg = msg.encode('utf-8', 'replace')
|
|
385
|
+
except AttributeError:
|
|
386
|
+
pass
|
|
387
|
+
if is_secret:
|
|
388
|
+
log.logger.debug(i18n.send_msg, i18n.hide_sensitive_info)
|
|
389
|
+
else:
|
|
390
|
+
log.logger.debug(i18n.send_msg, str(msg))
|
|
391
|
+
try:
|
|
392
|
+
self._core.read_very_eager()
|
|
393
|
+
self._core.write(msg)
|
|
394
|
+
except EOFError:
|
|
395
|
+
raise exceptions.ConnectionClosed()
|
|
396
|
+
if break_detect_after_send:
|
|
397
|
+
return -1
|
|
398
|
+
msg = ''
|
|
399
|
+
receive_data_buffer = bytes()
|
|
400
|
+
start_time = time.time()
|
|
422
401
|
mid_time = time.time()
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
402
|
+
while mid_time - start_time < current_screen_timeout:
|
|
403
|
+
try:
|
|
404
|
+
data = self._core.read_very_eager()
|
|
405
|
+
except EOFError:
|
|
406
|
+
return -1
|
|
407
|
+
receive_data_buffer += data
|
|
408
|
+
screen, find_target, is_secret, break_detect_after_send, use_too_many_res, msg, target_index = \
|
|
409
|
+
self._decode_screen(receive_data_buffer, start_time, target_list, is_secret, refresh, msg)
|
|
410
|
+
if target_index != -1:
|
|
411
|
+
return target_index
|
|
412
|
+
if use_too_many_res:
|
|
413
|
+
continue
|
|
414
|
+
if find_target:
|
|
415
|
+
break
|
|
416
|
+
if len(screen) > 0:
|
|
417
|
+
screens.show(self.config, screen)
|
|
418
|
+
self._RDQ.add(screen)
|
|
419
|
+
mid_time = time.time()
|
|
420
|
+
if not find_target:
|
|
421
|
+
return -1
|
|
422
|
+
return -2
|
|
423
|
+
else:
|
|
424
|
+
loop = self._get_event_loop()
|
|
425
|
+
return loop.run_until_complete(
|
|
426
|
+
self._async_send(msg, target_list, screen_timeout, refresh, secret)
|
|
427
|
+
)
|
|
427
428
|
|
|
428
429
|
def close(self):
|
|
429
430
|
if self.config.connect_mode == data_type.ConnectMode.WEBSOCKETS:
|
|
430
|
-
|
|
431
|
+
if self._core and self._core.open:
|
|
432
|
+
loop = self._get_event_loop()
|
|
433
|
+
loop.run_until_complete(self._core.close())
|
|
431
434
|
else:
|
|
432
435
|
self._core.close()
|
|
433
436
|
|
|
434
437
|
def get_screen_queue(self) -> list:
|
|
435
|
-
return self._RDQ.get(1)
|
|
438
|
+
return self._RDQ.get(1)
|
|
@@ -2,23 +2,23 @@ key = """-----BEGIN EC PARAMETERS-----
|
|
|
2
2
|
BggqhkjOPQMBBw==
|
|
3
3
|
-----END EC PARAMETERS-----
|
|
4
4
|
-----BEGIN EC PRIVATE KEY-----
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
MHcCAQEEINJgRPA04ePuXOw2n+nYcg+RycZ+xZJLlTBNDUmCIocToAoGCCqGSM49
|
|
6
|
+
AwEHoUQDQgAEpmbttSWJouIJzfJbZE65NP1ENBsqSiTAWS20LDvkLyYDAAKHJ5Sg
|
|
7
|
+
ppTu9Or5LRsvswxflEQCdXguMoSebJ1KXg==
|
|
8
8
|
-----END EC PRIVATE KEY-----
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
cert = """-----BEGIN CERTIFICATE-----
|
|
12
|
-
|
|
12
|
+
MIIB6TCCAY8CFFMS/4zt8Er+it0GilKNr5z+JumGMAoGCCqGSM49BAMCMHcxCzAJ
|
|
13
13
|
BgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UE
|
|
14
14
|
CgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRQw
|
|
15
|
-
|
|
15
|
+
EgYDVQQDDAtDb21tb24gTmFtZTAeFw0yNTEwMTExMzQ5MzhaFw0zNTEwMDkxMzQ5
|
|
16
16
|
MzhaMHcxCzAJBgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0
|
|
17
17
|
eTEVMBMGA1UECgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25h
|
|
18
18
|
bCBVbml0MRQwEgYDVQQDDAtDb21tb24gTmFtZTBZMBMGByqGSM49AgEGCCqGSM49
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
AwEHA0IABKZm7bUliaLiCc3yW2ROuTT9RDQbKkokwFkttCw75C8mAwAChyeUoKaU
|
|
20
|
+
7vTq+S0bL7MMX5REAnV4LjKEnmydSl4wCgYIKoZIzj0EAwIDSAAwRQIgCyyayA86
|
|
21
|
+
q1RCUtEs4qQXf4DuMHb+y5hLYjSHIm+kEIACIQDyflQToona8Aodd1Z+a3LKkExE
|
|
22
|
+
NtPJ1Kb4S+xaX6yIbw==
|
|
23
23
|
-----END CERTIFICATE-----
|
|
24
24
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPtt
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0.dev58619
|
|
4
4
|
Summary: PyPtt
|
|
5
5
|
Home-page: https://pyptt.cc/
|
|
6
6
|
Author: CodingMan
|
|
@@ -13,16 +13,14 @@ Classifier: Topic :: Communications :: BBS
|
|
|
13
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
14
|
Classifier: Topic :: Internet
|
|
15
15
|
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
20
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
21
|
Classifier: Natural Language :: Chinese (Traditional)
|
|
24
22
|
Classifier: Natural Language :: English
|
|
25
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.11
|
|
26
24
|
Description-Content-Type: text/markdown
|
|
27
25
|
License-File: LICENSE
|
|
28
26
|
Requires-Dist: progressbar2
|
|
@@ -65,10 +63,11 @@ PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了完整
|
|
|
65
63
|
- 其他功能: 即時訊息(水球)、P幣轉帳
|
|
66
64
|
|
|
67
65
|
### 系統需求
|
|
68
|
-
- Python 3.
|
|
66
|
+
- Python 3.11 以上
|
|
69
67
|
- 非雲端環境 (因 PTT 封鎖雲端 IP)
|
|
70
68
|
|
|
71
69
|
### 快速開始
|
|
70
|
+
|
|
72
71
|
```bash
|
|
73
72
|
pip install PyPtt
|
|
74
73
|
```
|
|
@@ -102,12 +102,10 @@ setup(
|
|
|
102
102
|
|
|
103
103
|
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
|
|
104
104
|
|
|
105
|
-
'Programming Language :: Python :: 3.8',
|
|
106
|
-
'Programming Language :: Python :: 3.9',
|
|
107
|
-
'Programming Language :: Python :: 3.10',
|
|
108
105
|
'Programming Language :: Python :: 3.11',
|
|
109
106
|
'Programming Language :: Python :: 3.12',
|
|
110
107
|
'Programming Language :: Python :: 3.13',
|
|
108
|
+
'Programming Language :: Python :: 3.14',
|
|
111
109
|
'Programming Language :: Python :: 3 :: Only',
|
|
112
110
|
|
|
113
111
|
'Natural Language :: Chinese (Traditional)',
|
|
@@ -115,7 +113,7 @@ setup(
|
|
|
115
113
|
],
|
|
116
114
|
keywords=['PTT', 'crawler', 'bot', 'library', 'websockets'], # Optional
|
|
117
115
|
|
|
118
|
-
python_requires='>=3.
|
|
116
|
+
python_requires='>=3.11',
|
|
119
117
|
packages=['PyPtt'],
|
|
120
118
|
install_requires=[
|
|
121
119
|
'progressbar2',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|