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.
Files changed (52) hide show
  1. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PKG-INFO +5 -6
  2. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/__init__.py +2 -2
  3. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/connect_core.py +139 -136
  4. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/ssl_config.py +9 -9
  5. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt.egg-info/PKG-INFO +5 -6
  6. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/README.md +2 -1
  7. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/setup.py +2 -4
  8. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/LICENSE +0 -0
  9. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/MANIFEST.in +0 -0
  10. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/PTT.py +0 -0
  11. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_bucket.py +0 -0
  12. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_call_status.py +0 -0
  13. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_change_pw.py +0 -0
  14. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_comment.py +0 -0
  15. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_del_post.py +0 -0
  16. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_board_info.py +0 -0
  17. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_board_list.py +0 -0
  18. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_bottom_post_list.py +0 -0
  19. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_favourite_board.py +0 -0
  20. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_newest_index.py +0 -0
  21. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_post.py +0 -0
  22. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_post_index.py +0 -0
  23. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_post_list.py +0 -0
  24. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_time.py +0 -0
  25. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_get_user.py +0 -0
  26. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_give_money.py +0 -0
  27. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_has_new_mail.py +0 -0
  28. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_loginout.py +0 -0
  29. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_mail.py +0 -0
  30. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_mark_post.py +0 -0
  31. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_post.py +0 -0
  32. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_reply_post.py +0 -0
  33. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_search_user.py +0 -0
  34. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_set_board_title.py +0 -0
  35. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/_api_util.py +0 -0
  36. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/check_value.py +0 -0
  37. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/command.py +0 -0
  38. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/config.py +0 -0
  39. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/data_type.py +0 -0
  40. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/exceptions.py +0 -0
  41. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/i18n.py +0 -0
  42. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/lang_en_US.py +0 -0
  43. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/lang_zh_TW.py +0 -0
  44. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/lib_util.py +0 -0
  45. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/log.py +0 -0
  46. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/screens.py +0 -0
  47. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt/service.py +0 -0
  48. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt.egg-info/SOURCES.txt +0 -0
  49. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt.egg-info/dependency_links.txt +0 -0
  50. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt.egg-info/requires.txt +0 -0
  51. {pyptt-1.3.3 → pyptt-1.4.0.dev58619}/PyPtt.egg-info/top_level.txt +0 -0
  52. {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.3
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.8
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.8 以上
66
+ - Python 3.11 以上
69
67
  - 非雲端環境 (因 PTT 封鎖雲端 IP)
70
68
 
71
69
  ### 快速開始
70
+
72
71
  ```bash
73
72
  pip install PyPtt
74
73
  ```
@@ -1,4 +1,4 @@
1
- __version__ = '1.3.3'
1
+ __version__ = '1.4.0.dev58619'
2
2
 
3
3
  from .PTT import API
4
4
  from .data_type import *
@@ -8,4 +8,4 @@ from .service import Service
8
8
 
9
9
  LOG_LEVEL = LogLevel
10
10
 
11
- _main_version = '1.3'
11
+ _main_version = '1.4'
@@ -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 = asyncio.get_event_loop().run_until_complete(
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
- if not msg.endswith(command.refresh):
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 send(self, msg: str, target_list: list, screen_timeout: int = 0, refresh: bool = True,
302
- secret: bool = False) -> int:
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
- if refresh and not msg.endswith(command.refresh):
322
- msg = msg + command.refresh
288
+ while True:
289
+ if refresh and msg and not msg.endswith(command.refresh):
290
+ msg += command.refresh
323
291
 
324
292
  try:
325
- msg = msg.encode('utf-8', 'replace')
293
+ encoded_msg = msg.encode('utf-8', 'replace')
326
294
  except AttributeError:
327
- pass
295
+ encoded_msg = msg
328
296
  except Exception as e:
329
297
  traceback.print_tb(e.__traceback__)
330
298
  print(e)
331
- msg = msg.encode('utf-8', 'replace')
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(msg))
304
+ log.logger.debug(i18n.send_msg, str(encoded_msg))
337
305
 
338
- if self.config.connect_mode == data_type.ConnectMode.TELNET:
339
- try:
340
- self._core.read_very_eager()
341
- self._core.write(msg)
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
- if break_detect_after_send:
356
- return -1
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
- mid_time = time.time()
363
- while mid_time - start_time < current_screen_timeout:
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
- asyncio.get_event_loop().run_until_complete(
378
- websocket_receiver(
379
- self._core, current_screen_timeout, recv_data_obj))
380
-
381
- except websockets.exceptions.ConnectionClosed:
382
- if use_too_many_res:
383
- raise exceptions.UseTooManyResources()
384
- raise exceptions.ConnectionClosed()
385
- except websockets.exceptions.ConnectionClosedOK:
386
- raise exceptions.ConnectionClosed()
387
- except asyncio.TimeoutError:
388
- return -1
389
- except RuntimeError:
390
- raise exceptions.ConnectionClosed()
391
-
392
- receive_data_buffer += recv_data_obj.data
393
-
394
- screen, find_target, is_secret, break_detect_after_send, use_too_many_res, msg, target_index = \
395
- self._decode_screen(receive_data_buffer, start_time, target_list, is_secret, refresh, msg)
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
- if self.current_encoding == 'big5uao' and not find_target:
398
- self.current_encoding = 'utf-8'
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
- if find_target:
403
- screen = screen_
404
- else:
405
- self.current_encoding = 'big5uao'
356
+ if use_too_many_res:
357
+ continue
406
358
 
407
- # print(4)
408
- if target_index != -1:
409
- return target_index
359
+ if not find_target:
360
+ return -1
410
361
 
411
- if use_too_many_res:
412
- continue
362
+ def send(self, msg: str, target_list: list, screen_timeout: int = 0, refresh: bool = True,
363
+ secret: bool = False) -> int:
413
364
 
414
- if find_target:
415
- break
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
- # print(6)
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
- if not find_target:
425
- return -1
426
- return -2
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
- asyncio.get_event_loop().run_until_complete(self._core.close())
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
- MHcCAQEEIDsVaXxAeTEqnC3V2BjBZepjfx3lG0UNiBb6IGs1nqo5oAoGCCqGSM49
6
- AwEHoUQDQgAExzhLL99T9DDmDW8my+dVvremZ3lYo9l+D6sBkcaD+KxYkd+eHhCp
7
- DBfIKnDILG7XNOpl3PBa4AOC4BkGkrqq0w==
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
- MIIB6TCCAY8CFAIPufXDoRbmCDs53mUkr/QCZ1wnMAoGCCqGSM49BAMCMHcxCzAJ
12
+ MIIB6TCCAY8CFFMS/4zt8Er+it0GilKNr5z+JumGMAoGCCqGSM49BAMCMHcxCzAJ
13
13
  BgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UE
14
14
  CgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRQw
15
- EgYDVQQDDAtDb21tb24gTmFtZTAeFw0yNTA5MjYwOTE4MzhaFw0zNTA5MjQwOTE4
15
+ EgYDVQQDDAtDb21tb24gTmFtZTAeFw0yNTEwMTExMzQ5MzhaFw0zNTEwMDkxMzQ5
16
16
  MzhaMHcxCzAJBgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0
17
17
  eTEVMBMGA1UECgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25h
18
18
  bCBVbml0MRQwEgYDVQQDDAtDb21tb24gTmFtZTBZMBMGByqGSM49AgEGCCqGSM49
19
- AwEHA0IABMc4Sy/fU/Qw5g1vJsvnVb63pmd5WKPZfg+rAZHGg/isWJHfnh4QqQwX
20
- yCpwyCxu1zTqZdzwWuADguAZBpK6qtMwCgYIKoZIzj0EAwIDSAAwRQIhAMTUyNcu
21
- iVbprDZNcnP58QoqQd3tIyod4eusUqIYjnIUAiAQsnjz0Ffer7IcrXG6pfscfi5O
22
- JbGxeRO0TFgSybUMBg==
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.3
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.8
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.8 以上
66
+ - Python 3.11 以上
69
67
  - 非雲端環境 (因 PTT 封鎖雲端 IP)
70
68
 
71
69
  ### 快速開始
70
+
72
71
  ```bash
73
72
  pip install PyPtt
74
73
  ```
@@ -20,10 +20,11 @@ PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了完整
20
20
  - 其他功能: 即時訊息(水球)、P幣轉帳
21
21
 
22
22
  ### 系統需求
23
- - Python 3.8 以上
23
+ - Python 3.11 以上
24
24
  - 非雲端環境 (因 PTT 封鎖雲端 IP)
25
25
 
26
26
  ### 快速開始
27
+
27
28
  ```bash
28
29
  pip install PyPtt
29
30
  ```
@@ -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.8',
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