PyPtt 1.4.0.dev58619__tar.gz → 2.0.2__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 (79) hide show
  1. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PKG-INFO +23 -19
  2. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/PTT.py +1 -1
  3. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/__init__.py +1 -3
  4. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_loginout.py +1 -0
  5. pyptt-2.0.2/PyPtt/api_server.py +38 -0
  6. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/connect_core.py +6 -2
  7. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/lang_en_US.py +1 -1
  8. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/lang_zh_TW.py +1 -1
  9. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/service.py +1 -1
  10. pyptt-2.0.2/PyPtt/ssl_config.py +24 -0
  11. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt.egg-info/PKG-INFO +23 -19
  12. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt.egg-info/SOURCES.txt +25 -2
  13. pyptt-2.0.2/PyPtt.egg-info/requires.txt +20 -0
  14. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/README.md +2 -2
  15. pyptt-2.0.2/pyproject.toml +70 -0
  16. pyptt-2.0.2/setup.py +24 -0
  17. pyptt-2.0.2/tests/test_change_pw.py +10 -0
  18. pyptt-2.0.2/tests/test_comment.py +94 -0
  19. pyptt-2.0.2/tests/test_del_post.py +88 -0
  20. pyptt-2.0.2/tests/test_exceptions.py +62 -0
  21. pyptt-2.0.2/tests/test_get_board_info.py +37 -0
  22. pyptt-2.0.2/tests/test_get_board_list.py +17 -0
  23. pyptt-2.0.2/tests/test_get_bottom_post_list.py +14 -0
  24. pyptt-2.0.2/tests/test_get_favourite_boards.py +14 -0
  25. pyptt-2.0.2/tests/test_get_mail.py +40 -0
  26. pyptt-2.0.2/tests/test_get_newest_index.py +55 -0
  27. pyptt-2.0.2/tests/test_get_post.py +153 -0
  28. pyptt-2.0.2/tests/test_get_post_list.py +71 -0
  29. pyptt-2.0.2/tests/test_get_time.py +13 -0
  30. pyptt-2.0.2/tests/test_get_user.py +19 -0
  31. pyptt-2.0.2/tests/test_give_money.py +20 -0
  32. pyptt-2.0.2/tests/test_i18n.py +86 -0
  33. pyptt-2.0.2/tests/test_init.py +133 -0
  34. pyptt-2.0.2/tests/test_logger.py +35 -0
  35. pyptt-2.0.2/tests/test_post.py +85 -0
  36. pyptt-2.0.2/tests/test_reply_post.py +95 -0
  37. pyptt-2.0.2/tests/test_search_user.py +13 -0
  38. pyptt-2.0.2/tests/test_service.py +141 -0
  39. pyptt-1.4.0.dev58619/MANIFEST.in +0 -5
  40. pyptt-1.4.0.dev58619/PyPtt/ssl_config.py +0 -24
  41. pyptt-1.4.0.dev58619/PyPtt.egg-info/requires.txt +0 -6
  42. pyptt-1.4.0.dev58619/setup.py +0 -131
  43. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/LICENSE +0 -0
  44. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_bucket.py +0 -0
  45. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_call_status.py +0 -0
  46. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_change_pw.py +0 -0
  47. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_comment.py +0 -0
  48. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_del_post.py +0 -0
  49. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_board_info.py +0 -0
  50. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_board_list.py +0 -0
  51. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_bottom_post_list.py +0 -0
  52. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_favourite_board.py +0 -0
  53. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_newest_index.py +0 -0
  54. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_post.py +0 -0
  55. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_post_index.py +0 -0
  56. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_post_list.py +0 -0
  57. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_time.py +0 -0
  58. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_user.py +0 -0
  59. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_give_money.py +0 -0
  60. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_has_new_mail.py +0 -0
  61. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_mail.py +0 -0
  62. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_mark_post.py +0 -0
  63. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_post.py +0 -0
  64. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_reply_post.py +0 -0
  65. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_search_user.py +0 -0
  66. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_set_board_title.py +0 -0
  67. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_util.py +0 -0
  68. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/check_value.py +0 -0
  69. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/command.py +0 -0
  70. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/config.py +0 -0
  71. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/data_type.py +0 -0
  72. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/exceptions.py +0 -0
  73. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/i18n.py +0 -0
  74. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/lib_util.py +0 -0
  75. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/log.py +0 -0
  76. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/screens.py +0 -0
  77. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt.egg-info/dependency_links.txt +0 -0
  78. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt.egg-info/top_level.txt +0 -0
  79. {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/setup.cfg +0 -0
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPtt
3
- Version: 1.4.0.dev58619
4
- Summary: PyPtt
5
- Home-page: https://pyptt.cc/
6
- Author: CodingMan
7
- Author-email: pttcodingman@gmail.com
3
+ Version: 2.0.2
4
+ Summary: PyPtt github: https://github.com/PyPtt/PyPtt
5
+ Author-email: CodingMan <pttcodingman@gmail.com>
6
+ License: GNU Lesser General Public License v3 (LGPLv3)
7
+ Project-URL: Homepage, https://pyptt.cc/
8
+ Project-URL: Repository, https://github.com/PyPtt/PyPtt
8
9
  Keywords: PTT,crawler,bot,library,websockets
9
10
  Classifier: Development Status :: 5 - Production/Stable
10
11
  Classifier: Operating System :: OS Independent
@@ -18,33 +19,36 @@ Classifier: Programming Language :: Python :: 3.12
18
19
  Classifier: Programming Language :: Python :: 3.13
19
20
  Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3
21
23
  Classifier: Natural Language :: Chinese (Traditional)
22
24
  Classifier: Natural Language :: English
23
25
  Requires-Python: >=3.11
24
26
  Description-Content-Type: text/markdown
25
27
  License-File: LICENSE
26
28
  Requires-Dist: progressbar2
27
- Requires-Dist: websockets
29
+ Requires-Dist: websockets==12.0
28
30
  Requires-Dist: uao
29
- Requires-Dist: requests
31
+ Requires-Dist: requests==2.32.4
30
32
  Requires-Dist: AutoStrEnum
31
33
  Requires-Dist: PyYAML
32
- Dynamic: author
33
- Dynamic: author-email
34
- Dynamic: classifier
35
- Dynamic: description
36
- Dynamic: description-content-type
37
- Dynamic: home-page
38
- Dynamic: keywords
34
+ Provides-Extra: api
35
+ Requires-Dist: fastapi; extra == "api"
36
+ Requires-Dist: uvicorn[standard]; extra == "api"
37
+ Provides-Extra: dev
38
+ Requires-Dist: pytest; extra == "dev"
39
+ Requires-Dist: python-dotenv; extra == "dev"
40
+ Requires-Dist: flake8; extra == "dev"
41
+ Requires-Dist: sphinx; extra == "dev"
42
+ Requires-Dist: sphinx-copybutton; extra == "dev"
43
+ Requires-Dist: pygments==2.15.0; extra == "dev"
44
+ Requires-Dist: Furo; extra == "dev"
45
+ Requires-Dist: sphinx-sitemap; extra == "dev"
39
46
  Dynamic: license-file
40
- Dynamic: requires-dist
41
- Dynamic: requires-python
42
- Dynamic: summary
43
47
 
44
48
  ![](https://raw.githubusercontent.com/PttCodingMan/PyPtt/master/logo/facebook_cover_photo_2.png)
45
49
  [![Package Version](https://img.shields.io/pypi/v/PyPtt.svg)](https://pypi.python.org/pypi/PyPtt)
46
50
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/PyPtt)
47
- [![test](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml/badge.svg)](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml)
51
+ [![test](https://github.com/PyPtt/PyPtt/actions/workflows/deploy.yml/badge.svg)](https://github.com/PyPtt/PyPtt/actions/workflows/deploy.yml)
48
52
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/PyPtt)
49
53
  [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
50
54
  [![chatroom icon](https://patrolavia.github.io/telegram-badge/chat.png)](https://t.me/PyPtt)
@@ -52,7 +56,7 @@ Dynamic: summary
52
56
 
53
57
  ## PyPtt (PTT Library)
54
58
 
55
- PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了完整功能讓您就像真人操作一樣地使用 PTT。
59
+ PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了批踢踢完整功能讓您可以自由開發你的批踢踢機器人。
56
60
 
57
61
  ### 主要功能
58
62
  - 帳號管理: 登入登出、修改密碼
@@ -88,7 +88,7 @@ class API:
88
88
  raise TypeError('[PyPtt] log_level must be log.Level')
89
89
 
90
90
  logger_callback = kwargs.get('logger_callback', None)
91
- log.init(log_level, logger_callback=logger_callback)
91
+ log.init(log_level=log_level, logger_callback=logger_callback)
92
92
 
93
93
  language = kwargs.get('language', data_type.Language.MANDARIN)
94
94
  if not isinstance(language, str):
@@ -1,4 +1,4 @@
1
- __version__ = '1.4.0.dev58619'
1
+ __version__ = '2.0.2'
2
2
 
3
3
  from .PTT import API
4
4
  from .data_type import *
@@ -7,5 +7,3 @@ from .log import LogLevel
7
7
  from .service import Service
8
8
 
9
9
  LOG_LEVEL = LogLevel
10
-
11
- _main_version = '1.4'
@@ -54,6 +54,7 @@ def login(api, ptt_id: str, ptt_pw: str, kick_other_session: bool) -> None:
54
54
 
55
55
  if api._is_login:
56
56
  api.logout()
57
+ api._is_login = False
57
58
 
58
59
  api.config.kick_other_session = kick_other_session
59
60
 
@@ -0,0 +1,38 @@
1
+ from typing import Optional, Dict, Any
2
+
3
+ from fastapi import FastAPI
4
+ from pydantic import BaseModel
5
+ import logging
6
+ from . import __version__
7
+ from . import service
8
+
9
+ app = FastAPI()
10
+
11
+ pyptt_service = service.Service()
12
+
13
+
14
+ class ApiRequest(BaseModel):
15
+ api: str
16
+ args: Optional[Dict[str, Any]] = None
17
+
18
+
19
+ @app.get("/")
20
+ def root():
21
+ return {"pyptt_version": __version__}
22
+
23
+
24
+ @app.post("/api")
25
+ def api_func(request: ApiRequest):
26
+ try:
27
+ api = request.api
28
+ args = request.args
29
+ result = pyptt_service.call(api, args)
30
+
31
+ except Exception as e:
32
+ logging.exception(f"Error in API call {request.api} with args {request.args}")
33
+ return {"api": request.api, "args": request.args, "error": "An internal error has occurred."}
34
+
35
+ if result is None:
36
+ result = 'success without return value'
37
+
38
+ return {"api": request.api, "result": result}
@@ -430,9 +430,13 @@ class API(object):
430
430
  if self.config.connect_mode == data_type.ConnectMode.WEBSOCKETS:
431
431
  if self._core and self._core.open:
432
432
  loop = self._get_event_loop()
433
- loop.run_until_complete(self._core.close())
433
+ try:
434
+ loop.run_until_complete(asyncio.wait_for(self._core.close(), timeout=2.0))
435
+ except (asyncio.TimeoutError, RuntimeError):
436
+ pass
434
437
  else:
435
438
  self._core.close()
436
439
 
437
440
  def get_screen_queue(self) -> list:
438
- return self._RDQ.get(1)
441
+ return self._RDQ.get(1)
442
+
@@ -114,7 +114,7 @@ string_data = {
114
114
  "user_has_previously_been_banned": "User has previously been banned",
115
115
  "user_offline": "User offline",
116
116
  "wait_for_no_fast_comment": "Because no fast comment, wait 5 sec",
117
- "welcome": "PyPtt v _target0_ developed by CodingMan",
117
+ "welcome": "PyPtt v _target0_",
118
118
  "wrong_id_pw": "Wrong id or pw",
119
119
  "unknown_error": "Unknown error",
120
120
  }
@@ -114,7 +114,7 @@ string_data = {
114
114
  "user_has_previously_been_banned": "使用者之前已被禁言",
115
115
  "user_offline": "使用者離線",
116
116
  "wait_for_no_fast_comment": "因禁止快速連續推文,所以等待五秒",
117
- "welcome": "PyPtt v _target0_ 由 CodingMan 開發",
117
+ "welcome": "PyPtt v _target0_",
118
118
  "wrong_id_pw": "帳號密碼錯誤",
119
119
  "unknown_error": "未知錯誤",
120
120
  }
@@ -20,7 +20,7 @@ class Service:
20
20
  這是一個可以在多執行緒中使用的 PyPtt API 服務。
21
21
 
22
22
  | 請注意:這僅僅只是 Thread Safe 的實作,對效能並不會有實質上的幫助。
23
- | 如果你需要更好的效能,請在每一個線程都使用一個 PyPtt.API 本身。
23
+ | 如果你需要更好的效能,請在每一個線程都使用一個 PyPtt.API 物件。
24
24
 
25
25
  Args:
26
26
  pyptt_init_config (dict): PyPtt 初始化設定,請參考 :ref:`初始化設定 <api-init>`。
@@ -0,0 +1,24 @@
1
+ key = """-----BEGIN EC PARAMETERS-----
2
+ BggqhkjOPQMBBw==
3
+ -----END EC PARAMETERS-----
4
+ -----BEGIN EC PRIVATE KEY-----
5
+ MHcCAQEEIDQK/ozBPYdUxs3uATvIrm4S50rvqI75H/oy7xf7vTQDoAoGCCqGSM49
6
+ AwEHoUQDQgAEwnTpFe4WBm1AK7D3wsy57MQWFLJwc0ROp/wfILr+Ph8N//A5CGJj
7
+ bsCGmBvMstVL4H0fhiHttV71XzXKVNnczA==
8
+ -----END EC PRIVATE KEY-----
9
+ """
10
+
11
+ cert = """-----BEGIN CERTIFICATE-----
12
+ MIIB6TCCAY8CFDx5sqBAEood2FlSyOitvvzqd6htMAoGCCqGSM49BAMCMHcxCzAJ
13
+ BgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UE
14
+ CgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRQw
15
+ EgYDVQQDDAtDb21tb24gTmFtZTAeFw0yNTEwMTgxNDI2MDRaFw0zNTEwMTYxNDI2
16
+ MDRaMHcxCzAJBgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0
17
+ eTEVMBMGA1UECgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25h
18
+ bCBVbml0MRQwEgYDVQQDDAtDb21tb24gTmFtZTBZMBMGByqGSM49AgEGCCqGSM49
19
+ AwEHA0IABMJ06RXuFgZtQCuw98LMuezEFhSycHNETqf8HyC6/j4fDf/wOQhiY27A
20
+ hpgbzLLVS+B9H4Yh7bVe9V81ylTZ3MwwCgYIKoZIzj0EAwIDSAAwRQIhAJJcSvg8
21
+ DPiCKw+oNlMWmLBMbgcf78YTvR5zsMD/mzpwAiBmbfPK0Cda4mIqJBe3Ts/e03mm
22
+ R71Im7FZg+vYvMmDYQ==
23
+ -----END CERTIFICATE-----
24
+ """
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPtt
3
- Version: 1.4.0.dev58619
4
- Summary: PyPtt
5
- Home-page: https://pyptt.cc/
6
- Author: CodingMan
7
- Author-email: pttcodingman@gmail.com
3
+ Version: 2.0.2
4
+ Summary: PyPtt github: https://github.com/PyPtt/PyPtt
5
+ Author-email: CodingMan <pttcodingman@gmail.com>
6
+ License: GNU Lesser General Public License v3 (LGPLv3)
7
+ Project-URL: Homepage, https://pyptt.cc/
8
+ Project-URL: Repository, https://github.com/PyPtt/PyPtt
8
9
  Keywords: PTT,crawler,bot,library,websockets
9
10
  Classifier: Development Status :: 5 - Production/Stable
10
11
  Classifier: Operating System :: OS Independent
@@ -18,33 +19,36 @@ Classifier: Programming Language :: Python :: 3.12
18
19
  Classifier: Programming Language :: Python :: 3.13
19
20
  Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3
21
23
  Classifier: Natural Language :: Chinese (Traditional)
22
24
  Classifier: Natural Language :: English
23
25
  Requires-Python: >=3.11
24
26
  Description-Content-Type: text/markdown
25
27
  License-File: LICENSE
26
28
  Requires-Dist: progressbar2
27
- Requires-Dist: websockets
29
+ Requires-Dist: websockets==12.0
28
30
  Requires-Dist: uao
29
- Requires-Dist: requests
31
+ Requires-Dist: requests==2.32.4
30
32
  Requires-Dist: AutoStrEnum
31
33
  Requires-Dist: PyYAML
32
- Dynamic: author
33
- Dynamic: author-email
34
- Dynamic: classifier
35
- Dynamic: description
36
- Dynamic: description-content-type
37
- Dynamic: home-page
38
- Dynamic: keywords
34
+ Provides-Extra: api
35
+ Requires-Dist: fastapi; extra == "api"
36
+ Requires-Dist: uvicorn[standard]; extra == "api"
37
+ Provides-Extra: dev
38
+ Requires-Dist: pytest; extra == "dev"
39
+ Requires-Dist: python-dotenv; extra == "dev"
40
+ Requires-Dist: flake8; extra == "dev"
41
+ Requires-Dist: sphinx; extra == "dev"
42
+ Requires-Dist: sphinx-copybutton; extra == "dev"
43
+ Requires-Dist: pygments==2.15.0; extra == "dev"
44
+ Requires-Dist: Furo; extra == "dev"
45
+ Requires-Dist: sphinx-sitemap; extra == "dev"
39
46
  Dynamic: license-file
40
- Dynamic: requires-dist
41
- Dynamic: requires-python
42
- Dynamic: summary
43
47
 
44
48
  ![](https://raw.githubusercontent.com/PttCodingMan/PyPtt/master/logo/facebook_cover_photo_2.png)
45
49
  [![Package Version](https://img.shields.io/pypi/v/PyPtt.svg)](https://pypi.python.org/pypi/PyPtt)
46
50
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/PyPtt)
47
- [![test](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml/badge.svg)](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml)
51
+ [![test](https://github.com/PyPtt/PyPtt/actions/workflows/deploy.yml/badge.svg)](https://github.com/PyPtt/PyPtt/actions/workflows/deploy.yml)
48
52
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/PyPtt)
49
53
  [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
50
54
  [![chatroom icon](https://patrolavia.github.io/telegram-badge/chat.png)](https://t.me/PyPtt)
@@ -52,7 +56,7 @@ Dynamic: summary
52
56
 
53
57
  ## PyPtt (PTT Library)
54
58
 
55
- PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了完整功能讓您就像真人操作一樣地使用 PTT。
59
+ PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了批踢踢完整功能讓您可以自由開發你的批踢踢機器人。
56
60
 
57
61
  ### 主要功能
58
62
  - 帳號管理: 登入登出、修改密碼
@@ -1,6 +1,6 @@
1
1
  LICENSE
2
- MANIFEST.in
3
2
  README.md
3
+ pyproject.toml
4
4
  setup.py
5
5
  PyPtt/PTT.py
6
6
  PyPtt/__init__.py
@@ -29,6 +29,7 @@ PyPtt/_api_reply_post.py
29
29
  PyPtt/_api_search_user.py
30
30
  PyPtt/_api_set_board_title.py
31
31
  PyPtt/_api_util.py
32
+ PyPtt/api_server.py
32
33
  PyPtt/check_value.py
33
34
  PyPtt/command.py
34
35
  PyPtt/config.py
@@ -47,4 +48,26 @@ PyPtt.egg-info/PKG-INFO
47
48
  PyPtt.egg-info/SOURCES.txt
48
49
  PyPtt.egg-info/dependency_links.txt
49
50
  PyPtt.egg-info/requires.txt
50
- PyPtt.egg-info/top_level.txt
51
+ PyPtt.egg-info/top_level.txt
52
+ tests/test_change_pw.py
53
+ tests/test_comment.py
54
+ tests/test_del_post.py
55
+ tests/test_exceptions.py
56
+ tests/test_get_board_info.py
57
+ tests/test_get_board_list.py
58
+ tests/test_get_bottom_post_list.py
59
+ tests/test_get_favourite_boards.py
60
+ tests/test_get_mail.py
61
+ tests/test_get_newest_index.py
62
+ tests/test_get_post.py
63
+ tests/test_get_post_list.py
64
+ tests/test_get_time.py
65
+ tests/test_get_user.py
66
+ tests/test_give_money.py
67
+ tests/test_i18n.py
68
+ tests/test_init.py
69
+ tests/test_logger.py
70
+ tests/test_post.py
71
+ tests/test_reply_post.py
72
+ tests/test_search_user.py
73
+ tests/test_service.py
@@ -0,0 +1,20 @@
1
+ progressbar2
2
+ websockets==12.0
3
+ uao
4
+ requests==2.32.4
5
+ AutoStrEnum
6
+ PyYAML
7
+
8
+ [api]
9
+ fastapi
10
+ uvicorn[standard]
11
+
12
+ [dev]
13
+ pytest
14
+ python-dotenv
15
+ flake8
16
+ sphinx
17
+ sphinx-copybutton
18
+ pygments==2.15.0
19
+ Furo
20
+ sphinx-sitemap
@@ -1,7 +1,7 @@
1
1
  ![](https://raw.githubusercontent.com/PttCodingMan/PyPtt/master/logo/facebook_cover_photo_2.png)
2
2
  [![Package Version](https://img.shields.io/pypi/v/PyPtt.svg)](https://pypi.python.org/pypi/PyPtt)
3
3
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/PyPtt)
4
- [![test](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml/badge.svg)](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml)
4
+ [![test](https://github.com/PyPtt/PyPtt/actions/workflows/deploy.yml/badge.svg)](https://github.com/PyPtt/PyPtt/actions/workflows/deploy.yml)
5
5
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/PyPtt)
6
6
  [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
7
7
  [![chatroom icon](https://patrolavia.github.io/telegram-badge/chat.png)](https://t.me/PyPtt)
@@ -9,7 +9,7 @@
9
9
 
10
10
  ## PyPtt (PTT Library)
11
11
 
12
- PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了完整功能讓您就像真人操作一樣地使用 PTT。
12
+ PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了批踢踢完整功能讓您可以自由開發你的批踢踢機器人。
13
13
 
14
14
  ### 主要功能
15
15
  - 帳號管理: 登入登出、修改密碼
@@ -0,0 +1,70 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "PyPtt"
7
+ dynamic = ["version"]
8
+ description = "PyPtt github: https://github.com/PyPtt/PyPtt"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "GNU Lesser General Public License v3 (LGPLv3)" }
12
+ keywords = ["PTT", "crawler", "bot", "library", "websockets"]
13
+ authors = [
14
+ { name = "CodingMan", email = "pttcodingman@gmail.com" },
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 5 - Production/Stable",
18
+ "Operating System :: OS Independent",
19
+ "Intended Audience :: Developers",
20
+ "Topic :: Communications :: BBS",
21
+ "Topic :: Software Development :: Libraries :: Python Modules",
22
+ "Topic :: Internet",
23
+ "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
+ "Programming Language :: Python :: 3 :: Only",
29
+ "Programming Language :: Python :: 3",
30
+ "Natural Language :: Chinese (Traditional)",
31
+ "Natural Language :: English",
32
+ ]
33
+
34
+ dependencies = [
35
+ "progressbar2",
36
+ "websockets==12.0",
37
+ "uao",
38
+ "requests==2.32.4",
39
+ "AutoStrEnum",
40
+ "PyYAML",
41
+ ]
42
+
43
+ [project.urls]
44
+ Homepage = "https://pyptt.cc/"
45
+ Repository = "https://github.com/PyPtt/PyPtt"
46
+
47
+ [project.optional-dependencies]
48
+ api = [
49
+ "fastapi",
50
+ "uvicorn[standard]",
51
+ ]
52
+ dev = [
53
+ "pytest",
54
+ "python-dotenv",
55
+ "flake8",
56
+ "sphinx",
57
+ "sphinx-copybutton",
58
+ "pygments==2.15.0",
59
+ "Furo",
60
+ "sphinx-sitemap",
61
+ ]
62
+
63
+ [tool.setuptools]
64
+ packages = ["PyPtt"]
65
+
66
+ [tool.setuptools.dynamic]
67
+
68
+
69
+ [tool.setuptools.package-data]
70
+ PyPtt = ["ssl/*.pem"]
pyptt-2.0.2/setup.py ADDED
@@ -0,0 +1,24 @@
1
+ import os
2
+
3
+ from setuptools import setup
4
+
5
+ # read the main version from __init__.py
6
+ with open('PyPtt/__init__.py', 'r', encoding='utf-8') as f:
7
+ data = f.read().strip()
8
+ version = data.split('__version__ = ')[1].split('\n')[0].strip().strip('\'')
9
+ print('main_version:', version)
10
+
11
+ # Append dev version for non-master branches in GitHub Actions
12
+ github_ref = os.environ.get("GITHUB_REF")
13
+ github_run_number = os.environ.get("GITHUB_RUN_NUMBER")
14
+
15
+ if github_ref and github_run_number and not github_ref.endswith('/master'):
16
+ version = f"{version}.dev{github_run_number}"
17
+
18
+ print('final version:', version)
19
+
20
+ with open('version.txt', 'w', encoding='utf-8') as f:
21
+ f.write(version)
22
+
23
+ # This setup.py is needed for editable installs and for the dynamic version.
24
+ setup(version=version)
@@ -0,0 +1,10 @@
1
+ import pytest
2
+
3
+ def test_change_password(ptt_bots):
4
+ """Tests changing the password to the same password."""
5
+ for ptt_bot in ptt_bots:
6
+ try:
7
+ # Change password to the same one to test functionality
8
+ ptt_bot.change_pw(ptt_bot._ptt_pw)
9
+ except Exception as e:
10
+ pytest.fail(f"change_pw failed for host {ptt_bot.host} with error: {e}")
@@ -0,0 +1,94 @@
1
+ import json
2
+ import random
3
+
4
+ import PyPtt
5
+ import pytest
6
+ import time
7
+
8
+ def test_comment(ptt_bots):
9
+ """
10
+ Tests commenting on a post on both PTT1 and PTT2.
11
+ """
12
+ for ptt_bot in ptt_bots:
13
+ if ptt_bot.host == PyPtt.HOST.PTT1:
14
+ test_list = [('Test', None)]
15
+ else:
16
+ test_list = [('Test', None)]
17
+
18
+ for board, post_id in test_list:
19
+ post_info = None
20
+ if post_id is None:
21
+ try:
22
+ newest_index = ptt_bot.get_newest_index(PyPtt.NewIndex.BOARD, board)
23
+ for i in range(100):
24
+
25
+ current_post = ptt_bot.get_post(board, index=newest_index - i)
26
+ if current_post and current_post.get(PyPtt.PostField.post_status) == PyPtt.PostStatus.EXISTS:
27
+ post_info = current_post
28
+ break
29
+ except PyPtt.NoSearchResult:
30
+ pytest.skip(f"Board '{board}' seems to be empty, skipping comment test.")
31
+
32
+ elif isinstance(post_id, int):
33
+ post_info = ptt_bot.get_post(board, index=post_id, query=True)
34
+ elif isinstance(post_id, str):
35
+ post_info = ptt_bot.get_post(board, aid=post_id, query=True)
36
+
37
+ if not post_info or post_info.get(PyPtt.PostField.post_status) != PyPtt.PostStatus.EXISTS:
38
+ pytest.skip(f"Could not find a suitable post to comment on in board '{board}'.")
39
+
40
+ # Test comment by index
41
+ try:
42
+ ptt_bot.comment(
43
+ board=board,
44
+ comment_type=PyPtt.CommentType.ARROW,
45
+ content='PyPtt comment test by index',
46
+ index=post_info['index'],
47
+ )
48
+ except PyPtt.NoPermission as e:
49
+ pytest.fail(f"Failed to comment by index on {ptt_bot.host}: {e}")
50
+
51
+ # The API might have a cooldown
52
+ time.sleep(5)
53
+
54
+ # Test comment by aid
55
+ try:
56
+ ptt_bot.comment(
57
+ board=board,
58
+ comment_type=PyPtt.CommentType.ARROW,
59
+ content='PyPtt comment test by AID',
60
+ aid=post_info['aid'],
61
+ )
62
+ except PyPtt.NoPermission as e:
63
+ pytest.fail(f"Failed to comment by AID on {ptt_bot.host}: {e}")
64
+ except PyPtt.NoFastComment:
65
+ # If we hit this, the first comment was successful.
66
+ # We can consider this a pass for the second comment attempt under cooldown.
67
+ pass
68
+
69
+ print(json.dumps(post_info, indent=2, ensure_ascii=False))
70
+
71
+ for i in range(5):
72
+ ptt_bot.comment(
73
+ board=board,
74
+ comment_type=PyPtt.CommentType.ARROW,
75
+ content=f'PyPtt comment test {i}',
76
+ index=post_info['index'],
77
+ )
78
+
79
+ post_info = ptt_bot.get_post(
80
+ board=board, aid=post_info['aid']
81
+ )
82
+
83
+ check_count = 0
84
+ check_target = ['PyPtt comment test by index', 'PyPtt comment test by AID']
85
+ check_target.extend(
86
+ [f'PyPtt comment test {i}' for i in range(5)]
87
+ )
88
+
89
+ for comment in post_info.get(PyPtt.PostField.comments, [])[-len(check_target):]:
90
+ print('checking comment:', comment)
91
+ if comment.get('content') in check_target:
92
+ check_count += 1
93
+
94
+ assert check_count == len(check_target), f"Not all comments found in post on {ptt_bot.host}."
@@ -0,0 +1,88 @@
1
+ import json
2
+ import time
3
+
4
+ import pytest
5
+
6
+ import PyPtt
7
+
8
+
9
+ def test_del_own_post(ptt_bots):
10
+ """Tests that a user can delete their own post."""
11
+ for ptt_bot in ptt_bots:
12
+ # 1. Post an article to delete
13
+ post_title = f"PyPtt Delete Test Post {int(time.time())}"
14
+ post_content = "This is a test post for deletion."
15
+ ptt_bot.post(
16
+ board='Test',
17
+ title_index=1,
18
+ title=post_title,
19
+ content=post_content
20
+ )
21
+ time.sleep(1) # Allow time for post to be indexed
22
+
23
+ newest_index = ptt_bot.get_newest_index(PyPtt.NewIndex.BOARD, board='Test')
24
+
25
+ # 2. Verify we found our post
26
+ for i in range(5):
27
+ post_data = ptt_bot.get_post('Test', index=newest_index - i)
28
+
29
+ if post_data['author'].startswith(ptt_bot.ptt_id):
30
+ break
31
+
32
+ assert post_data['author'].startswith(ptt_bot.ptt_id)
33
+ assert post_data['title'] == f"[測試] {post_title}"
34
+
35
+ # 3. Delete the post
36
+
37
+ for i in range(5): # Retry up to 3 times in case of transient issues
38
+ try:
39
+ ptt_bot.del_post(board='Test', index=newest_index - i)
40
+ except PyPtt.exceptions.NoPermission:
41
+ pass
42
+
43
+ for i in range(5):
44
+ deleted_post_data = ptt_bot.get_post('Test', index=newest_index - i)
45
+
46
+ print(json.dumps(deleted_post_data, ensure_ascii=False, indent=2))
47
+
48
+ if deleted_post_data[PyPtt.PostField.post_status] in [
49
+ PyPtt.PostStatus.DELETED_BY_AUTHOR,
50
+ PyPtt.PostStatus.DELETED_BY_MODERATOR,
51
+ PyPtt.PostStatus.DELETED_BY_UNKNOWN
52
+ ]:
53
+ continue
54
+
55
+ if deleted_post_data[PyPtt.PostField.author].split(' ')[0] != post_data[PyPtt.PostField.author].split(' ')[
56
+ 0]:
57
+ continue
58
+
59
+ # author is myself but not deleted
60
+ assert False
61
+
62
+
63
+
64
+ def test_del_other_post_permission_error(ptt_bots):
65
+ """Tests that deleting another user's post raises NoPermission."""
66
+ for ptt_bot in ptt_bots:
67
+ # Find a post on a public board not authored by the bot
68
+ board = 'SYSOP'
69
+ newest_index = ptt_bot.get_newest_index(PyPtt.NewIndex.BOARD, board=board)
70
+
71
+ target_index = -1
72
+ for i in range(10): # Check the last 10 posts
73
+ index_to_check = newest_index - i
74
+ try:
75
+ post = ptt_bot.get_post(board, index=index_to_check, query=True)
76
+ if post[PyPtt.PostField.post_status] == PyPtt.PostStatus.EXISTS and not post['author'].startswith(
77
+ ptt_bot.ptt_id):
78
+ target_index = index_to_check
79
+ break
80
+ except PyPtt.NoSuchPost:
81
+ continue
82
+
83
+ if target_index == -1:
84
+ pytest.skip(f"Could not find a recent post by another user on {board} for host {ptt_bot.host}")
85
+
86
+ # 5. Try to delete it and expect a permission error
87
+ with pytest.raises(PyPtt.NoPermission):
88
+ ptt_bot.del_post(board=board, index=target_index)