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.
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PKG-INFO +23 -19
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/PTT.py +1 -1
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/__init__.py +1 -3
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_loginout.py +1 -0
- pyptt-2.0.2/PyPtt/api_server.py +38 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/connect_core.py +6 -2
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/lang_en_US.py +1 -1
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/lang_zh_TW.py +1 -1
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/service.py +1 -1
- pyptt-2.0.2/PyPtt/ssl_config.py +24 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt.egg-info/PKG-INFO +23 -19
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt.egg-info/SOURCES.txt +25 -2
- pyptt-2.0.2/PyPtt.egg-info/requires.txt +20 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/README.md +2 -2
- pyptt-2.0.2/pyproject.toml +70 -0
- pyptt-2.0.2/setup.py +24 -0
- pyptt-2.0.2/tests/test_change_pw.py +10 -0
- pyptt-2.0.2/tests/test_comment.py +94 -0
- pyptt-2.0.2/tests/test_del_post.py +88 -0
- pyptt-2.0.2/tests/test_exceptions.py +62 -0
- pyptt-2.0.2/tests/test_get_board_info.py +37 -0
- pyptt-2.0.2/tests/test_get_board_list.py +17 -0
- pyptt-2.0.2/tests/test_get_bottom_post_list.py +14 -0
- pyptt-2.0.2/tests/test_get_favourite_boards.py +14 -0
- pyptt-2.0.2/tests/test_get_mail.py +40 -0
- pyptt-2.0.2/tests/test_get_newest_index.py +55 -0
- pyptt-2.0.2/tests/test_get_post.py +153 -0
- pyptt-2.0.2/tests/test_get_post_list.py +71 -0
- pyptt-2.0.2/tests/test_get_time.py +13 -0
- pyptt-2.0.2/tests/test_get_user.py +19 -0
- pyptt-2.0.2/tests/test_give_money.py +20 -0
- pyptt-2.0.2/tests/test_i18n.py +86 -0
- pyptt-2.0.2/tests/test_init.py +133 -0
- pyptt-2.0.2/tests/test_logger.py +35 -0
- pyptt-2.0.2/tests/test_post.py +85 -0
- pyptt-2.0.2/tests/test_reply_post.py +95 -0
- pyptt-2.0.2/tests/test_search_user.py +13 -0
- pyptt-2.0.2/tests/test_service.py +141 -0
- pyptt-1.4.0.dev58619/MANIFEST.in +0 -5
- pyptt-1.4.0.dev58619/PyPtt/ssl_config.py +0 -24
- pyptt-1.4.0.dev58619/PyPtt.egg-info/requires.txt +0 -6
- pyptt-1.4.0.dev58619/setup.py +0 -131
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/LICENSE +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_bucket.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_call_status.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_change_pw.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_comment.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_del_post.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_board_info.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_board_list.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_bottom_post_list.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_favourite_board.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_newest_index.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_post.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_post_index.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_post_list.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_time.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_get_user.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_give_money.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_has_new_mail.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_mail.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_mark_post.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_post.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_reply_post.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_search_user.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_set_board_title.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/_api_util.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/check_value.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/command.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/config.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/data_type.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/exceptions.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/i18n.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/lib_util.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/log.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt/screens.py +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt.egg-info/dependency_links.txt +0 -0
- {pyptt-1.4.0.dev58619 → pyptt-2.0.2}/PyPtt.egg-info/top_level.txt +0 -0
- {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:
|
|
4
|
-
Summary: PyPtt
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|

|
|
45
49
|
[](https://pypi.python.org/pypi/PyPtt)
|
|
46
50
|

|
|
47
|
-
[](https://github.com/PyPtt/PyPtt/actions/workflows/deploy.yml)
|
|
48
52
|

|
|
49
53
|
[](https://www.gnu.org/licenses/lgpl-3.0)
|
|
50
54
|
[](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
|
|
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):
|
|
@@ -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
|
-
|
|
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_
|
|
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_
|
|
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:
|
|
4
|
-
Summary: PyPtt
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|

|
|
45
49
|
[](https://pypi.python.org/pypi/PyPtt)
|
|
46
50
|

|
|
47
|
-
[](https://github.com/PyPtt/PyPtt/actions/workflows/deploy.yml)
|
|
48
52
|

|
|
49
53
|
[](https://www.gnu.org/licenses/lgpl-3.0)
|
|
50
54
|
[](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
|
|
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
|

|
|
2
2
|
[](https://pypi.python.org/pypi/PyPtt)
|
|
3
3
|

|
|
4
|
-
[](https://github.com/PyPtt/PyPtt/actions/workflows/deploy.yml)
|
|
5
5
|

|
|
6
6
|
[](https://www.gnu.org/licenses/lgpl-3.0)
|
|
7
7
|
[](https://t.me/PyPtt)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
## PyPtt (PTT Library)
|
|
11
11
|
|
|
12
|
-
PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK
|
|
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)
|