PyPtt 1.2.16__tar.gz → 1.2.18__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.2.16 → pyptt-1.2.18}/PKG-INFO +24 -11
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/PTT.py +6 -4
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/__init__.py +1 -1
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_comment.py +6 -6
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_del_post.py +5 -5
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_board_info.py +7 -12
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_newest_index.py +2 -1
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_post.py +4 -4
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_post_index.py +0 -1
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_user.py +1 -1
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_loginout.py +1 -1
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_mail.py +2 -2
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_mark_post.py +7 -7
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_post.py +1 -1
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_reply_post.py +3 -3
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_util.py +11 -6
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/check_value.py +9 -8
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/connect_core.py +15 -9
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/exceptions.py +8 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/i18n.py +4 -5
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/lib_util.py +5 -5
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/screens.py +5 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/service.py +3 -2
- pyptt-1.2.18/PyPtt/ssl_config.py +24 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt.egg-info/PKG-INFO +24 -11
- {pyptt-1.2.16 → pyptt-1.2.18}/README.md +21 -9
- pyptt-1.2.16/PyPtt/ssl_config.py +0 -24
- {pyptt-1.2.16 → pyptt-1.2.18}/LICENSE +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/MANIFEST.in +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_bucket.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_call_status.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_change_pw.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_board_list.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_bottom_post_list.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_favourite_board.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_time.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_give_money.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_has_new_mail.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_search_user.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_set_board_title.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/command.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/config.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/data_type.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/lang_en_US.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/lang_zh_TW.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/log.py +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt.egg-info/SOURCES.txt +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt.egg-info/dependency_links.txt +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt.egg-info/requires.txt +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt.egg-info/top_level.txt +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/setup.cfg +0 -0
- {pyptt-1.2.16 → pyptt-1.2.18}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPtt
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.18
|
|
4
4
|
Summary: PyPtt
|
|
5
5
|
Home-page: https://pyptt.cc/
|
|
6
6
|
Author: CodingMan
|
|
@@ -37,12 +37,12 @@ Dynamic: description
|
|
|
37
37
|
Dynamic: description-content-type
|
|
38
38
|
Dynamic: home-page
|
|
39
39
|
Dynamic: keywords
|
|
40
|
+
Dynamic: license-file
|
|
40
41
|
Dynamic: requires-dist
|
|
41
42
|
Dynamic: requires-python
|
|
42
43
|
Dynamic: summary
|
|
43
44
|
|
|
44
45
|

|
|
45
|
-
# PyPtt
|
|
46
46
|
[](https://pypi.python.org/pypi/PyPtt)
|
|
47
47
|

|
|
48
48
|
[](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml)
|
|
@@ -51,18 +51,27 @@ Dynamic: summary
|
|
|
51
51
|
[](https://t.me/PyPtt)
|
|
52
52
|
[](http://paypal.me/CodingMan)
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
## PyPtt (PTT Library)
|
|
55
|
+
|
|
56
|
+
PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了完整功能讓您就像真人操作一樣地使用 PTT。
|
|
57
|
+
|
|
58
|
+
### 主要功能
|
|
59
|
+
- 帳號管理: 登入登出、修改密碼
|
|
60
|
+
- 文章相關: 發文、推文、回覆、刪文
|
|
61
|
+
- 看板相關: 搜尋文章、取得看板資訊
|
|
62
|
+
- 信件系統: 寄信、收信、刪信
|
|
63
|
+
- 管理功能: 水桶、修改看板標題
|
|
64
|
+
- 其他功能: 即時訊息(水球)、P幣轉帳
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
### 系統需求
|
|
67
|
+
- Python 3.6+
|
|
68
|
+
- 非雲端環境 (因 PTT 封鎖雲端 IP)
|
|
69
|
+
|
|
70
|
+
### 快速開始
|
|
63
71
|
```bash
|
|
64
72
|
pip install PyPtt
|
|
65
73
|
```
|
|
74
|
+
<img src="https://raw.githubusercontent.com/PyPtt/PyPtt/master/docs/_static/login_1.0.gif" width="560">
|
|
66
75
|
|
|
67
76
|
## 回報問題
|
|
68
77
|
#### 請參考 [常見問題](https://pyptt.cc/faq.html) 章節
|
|
@@ -75,6 +84,10 @@ pip install PyPtt
|
|
|
75
84
|
####
|
|
76
85
|
#### [](http://paypal.me/CodingMan)
|
|
77
86
|
|
|
87
|
+
## Star History
|
|
88
|
+
|
|
89
|
+
[](https://www.star-history.com/#PyPtt/PyPtt&Date)
|
|
90
|
+
|
|
78
91
|
## 贊助清單
|
|
79
92
|
|
|
80
93
|
#### leftc
|
|
@@ -29,6 +29,7 @@ from . import check_value
|
|
|
29
29
|
from . import config
|
|
30
30
|
from . import connect_core
|
|
31
31
|
from . import data_type
|
|
32
|
+
from . import exceptions
|
|
32
33
|
from . import i18n
|
|
33
34
|
from . import lib_util
|
|
34
35
|
from . import log
|
|
@@ -139,16 +140,17 @@ class API:
|
|
|
139
140
|
|
|
140
141
|
check_value.check_type(connect_mode, data_type.ConnectMode, 'connect_mode')
|
|
141
142
|
if host in [data_type.HOST.PTT1, data_type.HOST.PTT2] and connect_mode is data_type.ConnectMode.TELNET:
|
|
142
|
-
raise
|
|
143
|
+
raise exceptions.ParameterError('[PyPtt] TELNET is not available on PTT1 and PTT2')
|
|
143
144
|
self.config.connect_mode = connect_mode
|
|
144
145
|
|
|
145
|
-
self.connect_core = connect_core.API(self
|
|
146
|
-
self._exist_board_list =
|
|
146
|
+
self.connect_core = connect_core.API(self)
|
|
147
|
+
self._exist_board_list = set()
|
|
147
148
|
self._moderators = dict()
|
|
148
149
|
self._thread_id = threading.get_ident()
|
|
149
|
-
self._goto_board_list =
|
|
150
|
+
self._goto_board_list = set()
|
|
150
151
|
self._board_info_list = dict()
|
|
151
152
|
self._newest_index_data = data_type.TimedDict(timeout=2)
|
|
153
|
+
self.cursor = None
|
|
152
154
|
|
|
153
155
|
log.logger.debug('thread_id', self._thread_id)
|
|
154
156
|
|
|
@@ -35,7 +35,7 @@ def _comment(api,
|
|
|
35
35
|
elif post_index != 0:
|
|
36
36
|
cmd_list.append(str(post_index))
|
|
37
37
|
else:
|
|
38
|
-
raise
|
|
38
|
+
raise exceptions.ParameterError('post_aid and post_index cannot be None at the same time')
|
|
39
39
|
|
|
40
40
|
cmd_list.append(command.enter)
|
|
41
41
|
cmd_list.append(command.comment)
|
|
@@ -138,13 +138,13 @@ def comment(api, board: str, push_type: data_type.CommentType, push_content: str
|
|
|
138
138
|
check_value.check_type(post_index, int, 'index')
|
|
139
139
|
|
|
140
140
|
if len(board) == 0:
|
|
141
|
-
raise
|
|
141
|
+
raise exceptions.ParameterError(f'wrong parameter board: {board}')
|
|
142
142
|
|
|
143
143
|
if post_index != 0 and isinstance(post_aid, str):
|
|
144
|
-
raise
|
|
144
|
+
raise exceptions.ParameterError('wrong parameter index and aid can\'t both input')
|
|
145
145
|
|
|
146
146
|
if post_index == 0 and post_aid is None:
|
|
147
|
-
raise
|
|
147
|
+
raise exceptions.ParameterError('wrong parameter index or aid must input')
|
|
148
148
|
|
|
149
149
|
if post_index != 0:
|
|
150
150
|
newest_index = api.get_newest_index(
|
|
@@ -175,7 +175,7 @@ def comment(api, board: str, push_type: data_type.CommentType, push_content: str
|
|
|
175
175
|
|
|
176
176
|
push_content = push_content.strip()
|
|
177
177
|
push_list = []
|
|
178
|
-
|
|
178
|
+
|
|
179
179
|
while push_content:
|
|
180
180
|
# 先找出符合長度限制的最大子字串
|
|
181
181
|
test_content = push_content
|
|
@@ -183,7 +183,7 @@ def comment(api, board: str, push_type: data_type.CommentType, push_content: str
|
|
|
183
183
|
if len(test_content.encode('big5uao', 'replace')) <= max_push_length:
|
|
184
184
|
break
|
|
185
185
|
test_content = test_content[:-1]
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
# 找換行符號
|
|
188
188
|
newline_pos = test_content.find('\n')
|
|
189
189
|
if newline_pos != -1:
|
|
@@ -29,13 +29,13 @@ def del_post(api, board: str, post_aid: Optional[str] = None, post_index: int =
|
|
|
29
29
|
check_value.check_type(post_index, int, 'PostIndex')
|
|
30
30
|
|
|
31
31
|
if len(board) == 0:
|
|
32
|
-
raise
|
|
32
|
+
raise exceptions.ParameterError(f'board error parameter: {board}')
|
|
33
33
|
|
|
34
34
|
if post_index != 0 and isinstance(post_aid, str):
|
|
35
|
-
raise
|
|
35
|
+
raise exceptions.ParameterError('wrong parameter index and aid can\'t both input')
|
|
36
36
|
|
|
37
37
|
if post_index == 0 and post_aid is None:
|
|
38
|
-
raise
|
|
38
|
+
raise exceptions.ParameterError('wrong parameter index or aid must input')
|
|
39
39
|
|
|
40
40
|
if post_index != 0:
|
|
41
41
|
newest_index = api.get_newest_index(
|
|
@@ -78,7 +78,7 @@ def del_post(api, board: str, post_aid: Optional[str] = None, post_index: int =
|
|
|
78
78
|
elif post_index != 0:
|
|
79
79
|
cmd_list.append(str(post_index))
|
|
80
80
|
else:
|
|
81
|
-
raise
|
|
81
|
+
raise exceptions.ParameterError('post_aid and post_index cannot be None at the same time')
|
|
82
82
|
|
|
83
83
|
cmd_list.append(command.enter)
|
|
84
84
|
cmd_list.append('d')
|
|
@@ -106,4 +106,4 @@ def del_post(api, board: str, post_aid: Optional[str] = None, post_index: int =
|
|
|
106
106
|
log.logger.info(i18n.delete_post, '...', i18n.fail)
|
|
107
107
|
raise exceptions.NoPermission(i18n.no_permission)
|
|
108
108
|
|
|
109
|
-
log.logger.info(i18n.delete_post, '...', i18n.success)
|
|
109
|
+
log.logger.info(i18n.delete_post, '...', i18n.success)
|
|
@@ -30,31 +30,26 @@ def get_board_info(api, board: str, get_post_kind: bool, call_by_others: bool) -
|
|
|
30
30
|
ori_screen = api.connect_core.get_screen_queue()[-1]
|
|
31
31
|
# print(ori_screen)
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
online_user_line = None
|
|
34
34
|
for line in ori_screen.split('\n'):
|
|
35
|
-
if '
|
|
35
|
+
if '作 者' not in line:
|
|
36
36
|
continue
|
|
37
37
|
if '日 期' not in line:
|
|
38
38
|
continue
|
|
39
|
-
if '人氣' not in line:
|
|
40
|
-
continue
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
online_user_line = line
|
|
43
41
|
break
|
|
44
42
|
|
|
45
|
-
if
|
|
43
|
+
if online_user_line is None:
|
|
46
44
|
raise exceptions.NoSuchBoard(api.config, board)
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
# print('nuser', nuser)
|
|
50
|
-
# print('------------------------')
|
|
51
|
-
if '[靜]' in nuser:
|
|
46
|
+
if '[靜]' in online_user_line:
|
|
52
47
|
online_user = 0
|
|
53
48
|
else:
|
|
54
|
-
if '
|
|
49
|
+
if '日 期' not in online_user_line or '作 者' not in online_user_line:
|
|
55
50
|
raise exceptions.NoSuchBoard(api.config, board)
|
|
56
51
|
pattern = re.compile(r'[\d]+')
|
|
57
|
-
r = pattern.search(
|
|
52
|
+
r = pattern.search(online_user_line)
|
|
58
53
|
if r is None:
|
|
59
54
|
raise exceptions.NoSuchBoard(api.config, board)
|
|
60
55
|
# 減一是把自己本身拿掉
|
|
@@ -96,6 +96,7 @@ def get_newest_index(api, index_type: data_type.NewIndex, board: Optional[str] =
|
|
|
96
96
|
target_list = [
|
|
97
97
|
connect_core.TargetUnit('沒有文章...', log_level=log.DEBUG, break_detect=True),
|
|
98
98
|
connect_core.TargetUnit(screens.Target.InBoard, log_level=log.DEBUG, break_detect=True),
|
|
99
|
+
connect_core.TargetUnit(screens.Target.InBoardWithCursor, log_level=log.DEBUG, break_detect=True),
|
|
99
100
|
connect_core.TargetUnit(screens.Target.MainMenu_Exiting,
|
|
100
101
|
exceptions_=exceptions.NoSuchBoard(api.config, board)),
|
|
101
102
|
]
|
|
@@ -143,7 +144,7 @@ def get_newest_index(api, index_type: data_type.NewIndex, board: Optional[str] =
|
|
|
143
144
|
raise exceptions.UnregisteredUser(lib_util.get_current_func_name())
|
|
144
145
|
|
|
145
146
|
if board is not None:
|
|
146
|
-
raise
|
|
147
|
+
raise exceptions.ParameterError('board should not input at NewIndex.MAIL.')
|
|
147
148
|
|
|
148
149
|
cmd_list = []
|
|
149
150
|
cmd_list.append(command.go_main_menu)
|
|
@@ -48,17 +48,17 @@ def get_post(api, board: str, aid: Optional[str] = None, index: Optional[int] =
|
|
|
48
48
|
check_value.check_type(search_condition, str, 'search_condition')
|
|
49
49
|
|
|
50
50
|
if len(board) == 0:
|
|
51
|
-
raise
|
|
51
|
+
raise exceptions.ParameterError(f'board error parameter: {board}')
|
|
52
52
|
|
|
53
53
|
if (index is not None and index > 0) and aid is not None:
|
|
54
|
-
raise
|
|
54
|
+
raise exceptions.ParameterError('wrong parameter index and aid can\'t both input')
|
|
55
55
|
|
|
56
56
|
if aid is not None:
|
|
57
57
|
pass
|
|
58
58
|
elif index > 0:
|
|
59
59
|
pass
|
|
60
60
|
else:
|
|
61
|
-
raise
|
|
61
|
+
raise exceptions.ParameterError('wrong parameter index or aid must input')
|
|
62
62
|
|
|
63
63
|
search_cmd = None
|
|
64
64
|
if search_list is not None and len(search_list) > 0:
|
|
@@ -113,7 +113,7 @@ def _get_post(api, board: str, post_aid: Optional[str] = None, post_index: int =
|
|
|
113
113
|
cmd_list.append(command.enter)
|
|
114
114
|
cmd_list.append(str(post_index))
|
|
115
115
|
else:
|
|
116
|
-
raise
|
|
116
|
+
raise exceptions.ParameterError('post_aid and post_index cannot be None at the same time')
|
|
117
117
|
|
|
118
118
|
cmd_list.append(command.enter)
|
|
119
119
|
cmd_list.append(command.query_post)
|
|
@@ -28,7 +28,7 @@ def get_user(api, ptt_id: str) -> Dict:
|
|
|
28
28
|
|
|
29
29
|
check_value.check_type(ptt_id, str, 'UserID')
|
|
30
30
|
if len(ptt_id) < 2:
|
|
31
|
-
raise
|
|
31
|
+
raise exceptions.ParameterError(f'wrong parameter user_id: {ptt_id}')
|
|
32
32
|
|
|
33
33
|
cmd_list = []
|
|
34
34
|
cmd_list.append(command.go_main_menu)
|
|
@@ -65,7 +65,7 @@ def login(api, ptt_id: str, ptt_pw: str, kick_other_session: bool) -> None:
|
|
|
65
65
|
def register_processing(screen):
|
|
66
66
|
pattern = re.compile(r'[\d]+')
|
|
67
67
|
api.process_picks = int(pattern.search(screen).group(0))
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
ptt_id = ptt_id[:12].strip()
|
|
70
70
|
ptt_pw = ptt_pw[:8].strip()
|
|
71
71
|
|
|
@@ -45,7 +45,7 @@ def mail(api,
|
|
|
45
45
|
|
|
46
46
|
if not check_sign_file:
|
|
47
47
|
if sign_file.lower() != 'x':
|
|
48
|
-
raise
|
|
48
|
+
raise exceptions.ParameterError(f'wrong parameter sign_file: {sign_file}')
|
|
49
49
|
|
|
50
50
|
cmd_list = []
|
|
51
51
|
# 回到主選單
|
|
@@ -130,7 +130,7 @@ def get_mail(api, index: int, search_type: Optional[data_type.SearchType] = None
|
|
|
130
130
|
log.logger.info(i18n.get_mail)
|
|
131
131
|
|
|
132
132
|
if not isinstance(index, int):
|
|
133
|
-
raise
|
|
133
|
+
raise exceptions.ParameterError('index must be int')
|
|
134
134
|
|
|
135
135
|
current_index = api.get_newest_index(data_type.NewIndex.MAIL)
|
|
136
136
|
if index <= 0 or current_index < index:
|
|
@@ -35,28 +35,28 @@ def mark_post(api, mark_type: int, board: str, post_aid: str, post_index: int, s
|
|
|
35
35
|
check_value.check_type(search_condition, str, 'SearchCondition')
|
|
36
36
|
|
|
37
37
|
if len(board) == 0:
|
|
38
|
-
raise
|
|
38
|
+
raise exceptions.ParameterError(f'board error parameter: {board}')
|
|
39
39
|
|
|
40
40
|
if mark_type != data_type.MarkType.DELETE_D:
|
|
41
41
|
if post_index != 0 and isinstance(post_aid, str):
|
|
42
|
-
raise
|
|
42
|
+
raise exceptions.ParameterError('wrong parameter index and aid can\'t both input')
|
|
43
43
|
|
|
44
44
|
if post_index == 0 and post_aid is None:
|
|
45
|
-
raise
|
|
45
|
+
raise exceptions.ParameterError('wrong parameter index or aid must input')
|
|
46
46
|
|
|
47
47
|
if search_condition is not None and search_type == 0:
|
|
48
|
-
raise
|
|
48
|
+
raise exceptions.ParameterError('wrong parameter index or aid must input')
|
|
49
49
|
|
|
50
50
|
if search_type == data_type.SearchType.COMMENT:
|
|
51
51
|
try:
|
|
52
52
|
S = int(search_condition)
|
|
53
53
|
except ValueError:
|
|
54
|
-
raise
|
|
54
|
+
raise exceptions.ParameterError(f'wrong parameter search_condition: {search_condition}')
|
|
55
55
|
|
|
56
56
|
check_value.check_range(S, -100, 100, 'search_condition')
|
|
57
57
|
|
|
58
58
|
if post_aid is not None and search_condition is not None:
|
|
59
|
-
raise
|
|
59
|
+
raise exceptions.ParameterError('wrong parameter aid and search_condition can\'t both input')
|
|
60
60
|
|
|
61
61
|
if post_index != 0:
|
|
62
62
|
newest_index = api.get_newest_index(
|
|
@@ -104,7 +104,7 @@ def mark_post(api, mark_type: int, board: str, post_aid: str, post_index: int, s
|
|
|
104
104
|
|
|
105
105
|
cmd_list.append(command.enter)
|
|
106
106
|
else:
|
|
107
|
-
raise
|
|
107
|
+
raise exceptions.ParameterError('post_aid and post_index cannot be None at the same time')
|
|
108
108
|
|
|
109
109
|
if mark_type == data_type.MarkType.S:
|
|
110
110
|
cmd_list.append('L')
|
|
@@ -107,7 +107,7 @@ def post(api, board: str, title: str, content: str, title_index: int, sign_file:
|
|
|
107
107
|
check_value.check_type(content, str, 'content')
|
|
108
108
|
|
|
109
109
|
if str(sign_file).lower() not in sign_file_list:
|
|
110
|
-
raise
|
|
110
|
+
raise exceptions.ParameterError(f'wrong parameter sign_file: {sign_file}')
|
|
111
111
|
|
|
112
112
|
_api_util.check_board(api, board)
|
|
113
113
|
_api_util.goto_board(api, board)
|
|
@@ -37,10 +37,10 @@ def reply_post(api, reply_to: data_type.ReplyTo, board: str, content: str, sign_
|
|
|
37
37
|
sign_file_list.extend([str(x) for x in range(0, 10)])
|
|
38
38
|
|
|
39
39
|
if str(sign_file).lower() not in sign_file_list:
|
|
40
|
-
raise
|
|
40
|
+
raise exceptions.ParameterError(f'wrong parameter sign_file: {sign_file}')
|
|
41
41
|
|
|
42
42
|
if post_aid is not None and post_index != 0:
|
|
43
|
-
raise
|
|
43
|
+
raise exceptions.ParameterError('wrong parameter aid and index can\'t both input')
|
|
44
44
|
|
|
45
45
|
_api_util.check_board(api, board)
|
|
46
46
|
|
|
@@ -53,7 +53,7 @@ def reply_post(api, reply_to: data_type.ReplyTo, board: str, content: str, sign_
|
|
|
53
53
|
elif post_index != 0:
|
|
54
54
|
cmd_list.append(str(post_index))
|
|
55
55
|
else:
|
|
56
|
-
raise
|
|
56
|
+
raise exceptions.ParameterError('post_aid and post_index cannot be None at the same time')
|
|
57
57
|
|
|
58
58
|
cmd_list.append(command.enter * 2)
|
|
59
59
|
cmd_list.append('r')
|
|
@@ -372,14 +372,15 @@ def goto_board(api, board: str, refresh: bool = False, end: bool = False) -> Non
|
|
|
372
372
|
cmd_list.append('qs')
|
|
373
373
|
cmd_list.append(board)
|
|
374
374
|
cmd_list.append(command.enter)
|
|
375
|
-
cmd_list.append(command.
|
|
375
|
+
cmd_list.append(command.ctrl_c * 5)
|
|
376
376
|
|
|
377
377
|
cmd = ''.join(cmd_list)
|
|
378
378
|
|
|
379
379
|
target_list = [
|
|
380
|
-
connect_core.TargetUnit('任意鍵', log_level=log.DEBUG, response=
|
|
381
|
-
connect_core.TargetUnit('互動式動畫播放中', log_level=log.DEBUG, response=command.ctrl_c),
|
|
380
|
+
connect_core.TargetUnit('任意鍵', log_level=log.DEBUG, response=command.space * 5),
|
|
381
|
+
connect_core.TargetUnit('互動式動畫播放中', log_level=log.DEBUG, response=command.ctrl_c * 5),
|
|
382
382
|
connect_core.TargetUnit(screens.Target.InBoard, log_level=log.DEBUG, break_detect=True),
|
|
383
|
+
connect_core.TargetUnit(screens.Target.InBoardWithCursor, log_level=log.DEBUG, break_detect=True),
|
|
383
384
|
]
|
|
384
385
|
|
|
385
386
|
if refresh:
|
|
@@ -389,8 +390,12 @@ def goto_board(api, board: str, refresh: bool = False, end: bool = False) -> Non
|
|
|
389
390
|
current_refresh = True
|
|
390
391
|
else:
|
|
391
392
|
current_refresh = False
|
|
392
|
-
api._goto_board_list.
|
|
393
|
-
|
|
393
|
+
api._goto_board_list.add(board.lower())
|
|
394
|
+
|
|
395
|
+
# 這裡可能因為發現第一次進入看板會有進版畫面,一般來說都可以在 target_list 找到對應的標的
|
|
396
|
+
# 但某些看板會卡在進版動畫中,但沒有顯示任意鍵繼續或互動是動畫,所以當 index == -1 (表示找不到標的 timeout 了)
|
|
397
|
+
# 可以嘗試修改 cmd_list
|
|
398
|
+
index = api.connect_core.send(cmd, target_list, refresh=current_refresh)
|
|
394
399
|
|
|
395
400
|
if end:
|
|
396
401
|
cmd_list = []
|
|
@@ -416,7 +421,7 @@ def one_thread(api):
|
|
|
416
421
|
def check_board(api, board: str, check_moderator: bool = False) -> Dict:
|
|
417
422
|
if board.lower() not in api._exist_board_list:
|
|
418
423
|
board_info = _api_get_board_info.get_board_info(api, board, get_post_kind=False, call_by_others=False)
|
|
419
|
-
api._exist_board_list.
|
|
424
|
+
api._exist_board_list.add(board.lower())
|
|
420
425
|
api._board_info_list[board.lower()] = board_info
|
|
421
426
|
|
|
422
427
|
moderators = board_info[data_type.BoardField.moderators]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from . import exceptions
|
|
1
2
|
from . import i18n
|
|
2
3
|
from . import log
|
|
3
4
|
|
|
@@ -21,19 +22,19 @@ def check_range(value, min_value, max_value, name) -> None:
|
|
|
21
22
|
|
|
22
23
|
if min_value <= value <= max_value:
|
|
23
24
|
return
|
|
24
|
-
raise
|
|
25
|
+
raise exceptions.ParameterError(f'{name} {value} {i18n.must_between} {min_value} ~ {max_value}')
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def check_index(name, index, max_value=None) -> None:
|
|
28
29
|
check_type(index, int, name)
|
|
29
30
|
if index < 1:
|
|
30
|
-
raise
|
|
31
|
+
raise exceptions.ParameterError(f'{name} {i18n.must_bigger_than} 0')
|
|
31
32
|
|
|
32
33
|
if max_value is not None:
|
|
33
34
|
if index > max_value:
|
|
34
35
|
log.logger.info('index', index)
|
|
35
36
|
log.logger.info('max_value', max_value)
|
|
36
|
-
raise
|
|
37
|
+
raise exceptions.ParameterError(f'{name} {index} {i18n.must_between} 0 ~ {max_value}')
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
def check_index_range(start_name, start_index, end_name, end_index, max_value=None) -> None:
|
|
@@ -41,20 +42,20 @@ def check_index_range(start_name, start_index, end_name, end_index, max_value=No
|
|
|
41
42
|
check_type(end_index, int, end_name)
|
|
42
43
|
|
|
43
44
|
if start_index < 1:
|
|
44
|
-
raise
|
|
45
|
+
raise exceptions.ParameterError(f'{start_name} {start_index} {i18n.must_bigger_than} 0')
|
|
45
46
|
|
|
46
47
|
if end_index <= 1:
|
|
47
|
-
raise
|
|
48
|
+
raise exceptions.ParameterError(f'{end_name} {end_index} {i18n.must_bigger_than} 1')
|
|
48
49
|
|
|
49
50
|
if start_index > end_index:
|
|
50
|
-
raise
|
|
51
|
+
raise exceptions.ParameterError(f'{end_name} {end_index} {i18n.must_bigger_than} {start_name} {start_index}')
|
|
51
52
|
|
|
52
53
|
if max_value is not None:
|
|
53
54
|
if start_index > max_value:
|
|
54
|
-
raise
|
|
55
|
+
raise exceptions.ParameterError(f'{start_name} {start_index} {i18n.must_small_than} {max_value}')
|
|
55
56
|
|
|
56
57
|
if end_index > max_value:
|
|
57
|
-
raise
|
|
58
|
+
raise exceptions.ParameterError(f'{end_name} {end_index} {i18n.must_small_than} {max_value}')
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
if __name__ == '__main__':
|
|
@@ -25,10 +25,12 @@ from . import ssl_config
|
|
|
25
25
|
|
|
26
26
|
try:
|
|
27
27
|
import websockets.http
|
|
28
|
+
|
|
28
29
|
websockets.http.USER_AGENT += f' PyPtt/{PyPtt.__version__}'
|
|
29
30
|
use_http11 = False
|
|
30
31
|
except AttributeError:
|
|
31
32
|
import websockets.http11
|
|
33
|
+
|
|
32
34
|
websockets.http11.USER_AGENT += f' PyPtt/{PyPtt.__version__}'
|
|
33
35
|
use_http11 = True
|
|
34
36
|
|
|
@@ -80,7 +82,7 @@ class TargetUnit:
|
|
|
80
82
|
self._max_match = max_match
|
|
81
83
|
self._current_match = 0
|
|
82
84
|
|
|
83
|
-
def is_match(self, screen: str) -> bool:
|
|
85
|
+
def is_match(self, screen: str, cursor) -> bool:
|
|
84
86
|
if self._current_match >= self._max_match > 0:
|
|
85
87
|
return False
|
|
86
88
|
if isinstance(self.detect_target, str):
|
|
@@ -89,8 +91,11 @@ class TargetUnit:
|
|
|
89
91
|
return True
|
|
90
92
|
return False
|
|
91
93
|
elif isinstance(self.detect_target, list):
|
|
92
|
-
for
|
|
93
|
-
if
|
|
94
|
+
for target in self.detect_target:
|
|
95
|
+
if target == cursor:
|
|
96
|
+
if not any(line.startswith(target) for line in screen.split('\n')):
|
|
97
|
+
return False
|
|
98
|
+
elif target not in screen:
|
|
94
99
|
return False
|
|
95
100
|
self._current_match += 1
|
|
96
101
|
return True
|
|
@@ -152,10 +157,11 @@ class ReceiveDataQueue(object):
|
|
|
152
157
|
|
|
153
158
|
|
|
154
159
|
class API(object):
|
|
155
|
-
def __init__(self,
|
|
160
|
+
def __init__(self, api):
|
|
156
161
|
|
|
157
162
|
self.current_encoding = 'big5uao'
|
|
158
|
-
self.
|
|
163
|
+
self.api = api
|
|
164
|
+
self.config = api.config
|
|
159
165
|
self._RDQ = ReceiveDataQueue()
|
|
160
166
|
self._UseTooManyResources = TargetUnit(screens.Target.use_too_many_resources,
|
|
161
167
|
exceptions_=exceptions.UseTooManyResources())
|
|
@@ -209,7 +215,8 @@ class API(object):
|
|
|
209
215
|
loop = asyncio.new_event_loop()
|
|
210
216
|
asyncio.set_event_loop(loop)
|
|
211
217
|
|
|
212
|
-
log.logger.debug('USER_AGENT',
|
|
218
|
+
log.logger.debug('USER_AGENT',
|
|
219
|
+
websockets.http11.USER_AGENT if use_http11 else websockets.http.USER_AGENT)
|
|
213
220
|
self._core = asyncio.get_event_loop().run_until_complete(
|
|
214
221
|
websockets.connect(
|
|
215
222
|
websocket_host,
|
|
@@ -249,8 +256,7 @@ class API(object):
|
|
|
249
256
|
find_target = False
|
|
250
257
|
target_index = -1
|
|
251
258
|
for target in target_list:
|
|
252
|
-
|
|
253
|
-
if condition:
|
|
259
|
+
if target.is_match(screen, self.api.cursor):
|
|
254
260
|
if target._Handler is not None:
|
|
255
261
|
target._Handler(screen)
|
|
256
262
|
if len(screen) > 0:
|
|
@@ -295,7 +301,7 @@ class API(object):
|
|
|
295
301
|
secret: bool = False) -> int:
|
|
296
302
|
|
|
297
303
|
if not all(isinstance(T, TargetUnit) for T in target_list):
|
|
298
|
-
raise
|
|
304
|
+
raise exceptions.ParameterError('Item of TargetList must be TargetUnit')
|
|
299
305
|
|
|
300
306
|
if self._UseTooManyResources not in target_list:
|
|
301
307
|
target_list.append(self._UseTooManyResources)
|
|
@@ -3,6 +3,7 @@ import random
|
|
|
3
3
|
|
|
4
4
|
from . import __version__
|
|
5
5
|
from . import data_type
|
|
6
|
+
from . import exceptions
|
|
6
7
|
|
|
7
8
|
locale_pool = {
|
|
8
9
|
data_type.Language.ENGLISH,
|
|
@@ -26,7 +27,7 @@ def replace(string, *args):
|
|
|
26
27
|
|
|
27
28
|
def init(locale: str, cache: bool = False) -> None:
|
|
28
29
|
if locale not in locale_pool:
|
|
29
|
-
raise
|
|
30
|
+
raise exceptions.ParameterError(f'Unknown locale: {locale}')
|
|
30
31
|
|
|
31
32
|
if locale == data_type.Language.ENGLISH:
|
|
32
33
|
from . import lang_en_US as lang
|
|
@@ -35,7 +36,7 @@ def init(locale: str, cache: bool = False) -> None:
|
|
|
35
36
|
string_data = lang.string_data
|
|
36
37
|
|
|
37
38
|
if string_data is None:
|
|
38
|
-
raise
|
|
39
|
+
raise exceptions.ParameterError(f'Unknown locale: {locale}')
|
|
39
40
|
|
|
40
41
|
for k, v in string_data.items():
|
|
41
42
|
|
|
@@ -44,7 +45,7 @@ def init(locale: str, cache: bool = False) -> None:
|
|
|
44
45
|
elif isinstance(v, str):
|
|
45
46
|
pass
|
|
46
47
|
else:
|
|
47
|
-
raise
|
|
48
|
+
raise exceptions.ParameterError(f'Unknown string data type: {v}')
|
|
48
49
|
|
|
49
50
|
if locale == data_type.Language.ENGLISH:
|
|
50
51
|
v = v[0].upper() + v[1:]
|
|
@@ -54,6 +55,4 @@ def init(locale: str, cache: bool = False) -> None:
|
|
|
54
55
|
|
|
55
56
|
globals()[k] = v
|
|
56
57
|
if cache:
|
|
57
|
-
global _lang_data
|
|
58
58
|
_lang_data[k] = v
|
|
59
|
-
|
|
@@ -12,6 +12,7 @@ import requests
|
|
|
12
12
|
from . import __version__
|
|
13
13
|
from . import check_value
|
|
14
14
|
from . import data_type
|
|
15
|
+
from . import exceptions
|
|
15
16
|
from . import i18n
|
|
16
17
|
from . import log
|
|
17
18
|
|
|
@@ -50,7 +51,7 @@ def get_aid_from_url(url: str) -> Tuple[str, str]:
|
|
|
50
51
|
pattern = re.compile(r'https://www.ptt.cc/bbs/[-.\w]+/M.[\d]+.A[.\w]*.html')
|
|
51
52
|
r = pattern.search(url)
|
|
52
53
|
if r is None:
|
|
53
|
-
raise
|
|
54
|
+
raise exceptions.ParameterError('wrong parameter url must be www.ptt.cc post url')
|
|
54
55
|
|
|
55
56
|
board = url[23:]
|
|
56
57
|
board = board[:board.find('/')]
|
|
@@ -87,7 +88,6 @@ sync_version_result: str = ''
|
|
|
87
88
|
|
|
88
89
|
def sync_version() -> Tuple[data_type.Compare, str]:
|
|
89
90
|
global sync_version_compare
|
|
90
|
-
global sync_version_result
|
|
91
91
|
|
|
92
92
|
if sync_version_compare is not data_type.Compare.UNKNOWN:
|
|
93
93
|
return sync_version_compare, sync_version_result
|
|
@@ -148,7 +148,7 @@ def uniform_new_line(text: str) -> str:
|
|
|
148
148
|
@functools.lru_cache(maxsize=64)
|
|
149
149
|
def check_aid(aid: str) -> str:
|
|
150
150
|
if aid is None:
|
|
151
|
-
raise
|
|
151
|
+
raise exceptions.ParameterError('aid is None')
|
|
152
152
|
|
|
153
153
|
if not isinstance(aid, str):
|
|
154
154
|
raise TypeError('aid is not str')
|
|
@@ -157,12 +157,12 @@ def check_aid(aid: str) -> str:
|
|
|
157
157
|
aid = aid[1:]
|
|
158
158
|
|
|
159
159
|
if len(aid) != 8:
|
|
160
|
-
raise
|
|
160
|
+
raise exceptions.ParameterError('aid is not valid')
|
|
161
161
|
|
|
162
162
|
# check the char of aid is in aid_table or not
|
|
163
163
|
for char in aid:
|
|
164
164
|
if char not in aid_table:
|
|
165
|
-
raise
|
|
165
|
+
raise exceptions.ParameterError('aid is not valid')
|
|
166
166
|
|
|
167
167
|
return f'#{aid}'
|
|
168
168
|
|
|
@@ -7,6 +7,7 @@ from typing import Optional
|
|
|
7
7
|
|
|
8
8
|
from . import PTT
|
|
9
9
|
from . import check_value
|
|
10
|
+
from . import exceptions
|
|
10
11
|
from . import log
|
|
11
12
|
|
|
12
13
|
|
|
@@ -136,7 +137,7 @@ class Service:
|
|
|
136
137
|
check_value.check_type(args, dict, 'args')
|
|
137
138
|
|
|
138
139
|
if api not in dir(self._api):
|
|
139
|
-
raise
|
|
140
|
+
raise exceptions.ParameterError(f'api {api} not found')
|
|
140
141
|
|
|
141
142
|
call = {
|
|
142
143
|
'api': api,
|
|
@@ -164,4 +165,4 @@ class Service:
|
|
|
164
165
|
self._close = True
|
|
165
166
|
self._thread.join()
|
|
166
167
|
|
|
167
|
-
self.logger.info('done')
|
|
168
|
+
self.logger.info('done')
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
key = """-----BEGIN EC PARAMETERS-----
|
|
2
|
+
BggqhkjOPQMBBw==
|
|
3
|
+
-----END EC PARAMETERS-----
|
|
4
|
+
-----BEGIN EC PRIVATE KEY-----
|
|
5
|
+
MHcCAQEEIJbgsRW2TKkVA9Xes0BF+mZmt9xt55PQVkS/5fW7MSGRoAoGCCqGSM49
|
|
6
|
+
AwEHoUQDQgAEovYlaBBO4yItJL4E64G3vnJ/35Y086ZHhasVSBcOHaWZ3gvPfEQ6
|
|
7
|
+
bGzjAHcP0nvIHujHWZ0n+WiKHx5YrNBqdQ==
|
|
8
|
+
-----END EC PRIVATE KEY-----
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
cert = """-----BEGIN CERTIFICATE-----
|
|
12
|
+
MIIB6TCCAY8CFBH32z9ttIrOFfV1B3S83UMpXJ8rMAoGCCqGSM49BAMCMHcxCzAJ
|
|
13
|
+
BgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UE
|
|
14
|
+
CgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRQw
|
|
15
|
+
EgYDVQQDDAtDb21tb24gTmFtZTAeFw0yNTA2MTEwMTI1NTZaFw0zNTA2MDkwMTI1
|
|
16
|
+
NTZaMHcxCzAJBgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0
|
|
17
|
+
eTEVMBMGA1UECgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25h
|
|
18
|
+
bCBVbml0MRQwEgYDVQQDDAtDb21tb24gTmFtZTBZMBMGByqGSM49AgEGCCqGSM49
|
|
19
|
+
AwEHA0IABKL2JWgQTuMiLSS+BOuBt75yf9+WNPOmR4WrFUgXDh2lmd4Lz3xEOmxs
|
|
20
|
+
4wB3D9J7yB7ox1mdJ/loih8eWKzQanUwCgYIKoZIzj0EAwIDSAAwRQIhAOptXAaR
|
|
21
|
+
UNGwem1Rrhu3u1xjXjquzYugsE4wEqe4ZtINAiAn8kWuKxsy+BLc3R4VOzzQ33zW
|
|
22
|
+
RVgyl1y5FA4471yinw==
|
|
23
|
+
-----END CERTIFICATE-----
|
|
24
|
+
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPtt
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.18
|
|
4
4
|
Summary: PyPtt
|
|
5
5
|
Home-page: https://pyptt.cc/
|
|
6
6
|
Author: CodingMan
|
|
@@ -37,12 +37,12 @@ Dynamic: description
|
|
|
37
37
|
Dynamic: description-content-type
|
|
38
38
|
Dynamic: home-page
|
|
39
39
|
Dynamic: keywords
|
|
40
|
+
Dynamic: license-file
|
|
40
41
|
Dynamic: requires-dist
|
|
41
42
|
Dynamic: requires-python
|
|
42
43
|
Dynamic: summary
|
|
43
44
|
|
|
44
45
|

|
|
45
|
-
# PyPtt
|
|
46
46
|
[](https://pypi.python.org/pypi/PyPtt)
|
|
47
47
|

|
|
48
48
|
[](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml)
|
|
@@ -51,18 +51,27 @@ Dynamic: summary
|
|
|
51
51
|
[](https://t.me/PyPtt)
|
|
52
52
|
[](http://paypal.me/CodingMan)
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
## PyPtt (PTT Library)
|
|
55
|
+
|
|
56
|
+
PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了完整功能讓您就像真人操作一樣地使用 PTT。
|
|
57
|
+
|
|
58
|
+
### 主要功能
|
|
59
|
+
- 帳號管理: 登入登出、修改密碼
|
|
60
|
+
- 文章相關: 發文、推文、回覆、刪文
|
|
61
|
+
- 看板相關: 搜尋文章、取得看板資訊
|
|
62
|
+
- 信件系統: 寄信、收信、刪信
|
|
63
|
+
- 管理功能: 水桶、修改看板標題
|
|
64
|
+
- 其他功能: 即時訊息(水球)、P幣轉帳
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
### 系統需求
|
|
67
|
+
- Python 3.6+
|
|
68
|
+
- 非雲端環境 (因 PTT 封鎖雲端 IP)
|
|
69
|
+
|
|
70
|
+
### 快速開始
|
|
63
71
|
```bash
|
|
64
72
|
pip install PyPtt
|
|
65
73
|
```
|
|
74
|
+
<img src="https://raw.githubusercontent.com/PyPtt/PyPtt/master/docs/_static/login_1.0.gif" width="560">
|
|
66
75
|
|
|
67
76
|
## 回報問題
|
|
68
77
|
#### 請參考 [常見問題](https://pyptt.cc/faq.html) 章節
|
|
@@ -75,6 +84,10 @@ pip install PyPtt
|
|
|
75
84
|
####
|
|
76
85
|
#### [](http://paypal.me/CodingMan)
|
|
77
86
|
|
|
87
|
+
## Star History
|
|
88
|
+
|
|
89
|
+
[](https://www.star-history.com/#PyPtt/PyPtt&Date)
|
|
90
|
+
|
|
78
91
|
## 贊助清單
|
|
79
92
|
|
|
80
93
|
#### leftc
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|

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

|
|
5
4
|
[](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml)
|
|
@@ -8,18 +7,27 @@
|
|
|
8
7
|
[](https://t.me/PyPtt)
|
|
9
8
|
[](http://paypal.me/CodingMan)
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
## PyPtt (PTT Library)
|
|
11
|
+
|
|
12
|
+
PyPtt 是一套以 Pure Python 實作的 PTT (批踢踢) SDK,提供了完整功能讓您就像真人操作一樣地使用 PTT。
|
|
13
|
+
|
|
14
|
+
### 主要功能
|
|
15
|
+
- 帳號管理: 登入登出、修改密碼
|
|
16
|
+
- 文章相關: 發文、推文、回覆、刪文
|
|
17
|
+
- 看板相關: 搜尋文章、取得看板資訊
|
|
18
|
+
- 信件系統: 寄信、收信、刪信
|
|
19
|
+
- 管理功能: 水桶、修改看板標題
|
|
20
|
+
- 其他功能: 即時訊息(水球)、P幣轉帳
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
### 系統需求
|
|
23
|
+
- Python 3.6+
|
|
24
|
+
- 非雲端環境 (因 PTT 封鎖雲端 IP)
|
|
25
|
+
|
|
26
|
+
### 快速開始
|
|
20
27
|
```bash
|
|
21
28
|
pip install PyPtt
|
|
22
29
|
```
|
|
30
|
+
<img src="https://raw.githubusercontent.com/PyPtt/PyPtt/master/docs/_static/login_1.0.gif" width="560">
|
|
23
31
|
|
|
24
32
|
## 回報問題
|
|
25
33
|
#### 請參考 [常見問題](https://pyptt.cc/faq.html) 章節
|
|
@@ -32,6 +40,10 @@ pip install PyPtt
|
|
|
32
40
|
####
|
|
33
41
|
#### [](http://paypal.me/CodingMan)
|
|
34
42
|
|
|
43
|
+
## Star History
|
|
44
|
+
|
|
45
|
+
[](https://www.star-history.com/#PyPtt/PyPtt&Date)
|
|
46
|
+
|
|
35
47
|
## 贊助清單
|
|
36
48
|
|
|
37
49
|
#### leftc
|
pyptt-1.2.16/PyPtt/ssl_config.py
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
key = """-----BEGIN EC PARAMETERS-----
|
|
2
|
-
BggqhkjOPQMBBw==
|
|
3
|
-
-----END EC PARAMETERS-----
|
|
4
|
-
-----BEGIN EC PRIVATE KEY-----
|
|
5
|
-
MHcCAQEEIFaMupPGfs4HeNqeO79cQ/T8oFhOW9XpJAOy7oc247DnoAoGCCqGSM49
|
|
6
|
-
AwEHoUQDQgAESBT3HZWk/z7G0RWKULAbhYW3QEu6LWz8Js3N9vWefgEJSrSlSHw+
|
|
7
|
-
uM06lbX+d6xXWFS0hkAYNP2IpsUyHEEFkg==
|
|
8
|
-
-----END EC PRIVATE KEY-----
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
cert = """-----BEGIN CERTIFICATE-----
|
|
12
|
-
MIIB6jCCAY8CFHEG4hrPag+DbRaGd+PG27opXSxKMAoGCCqGSM49BAMCMHcxCzAJ
|
|
13
|
-
BgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UE
|
|
14
|
-
CgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRQw
|
|
15
|
-
EgYDVQQDDAtDb21tb24gTmFtZTAeFw0yNTAyMDMwODIyMzRaFw0zNTAyMDEwODIy
|
|
16
|
-
MzRaMHcxCzAJBgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0
|
|
17
|
-
eTEVMBMGA1UECgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25h
|
|
18
|
-
bCBVbml0MRQwEgYDVQQDDAtDb21tb24gTmFtZTBZMBMGByqGSM49AgEGCCqGSM49
|
|
19
|
-
AwEHA0IABEgU9x2VpP8+xtEVilCwG4WFt0BLui1s/CbNzfb1nn4BCUq0pUh8PrjN
|
|
20
|
-
OpW1/nesV1hUtIZAGDT9iKbFMhxBBZIwCgYIKoZIzj0EAwIDSQAwRgIhAINzsXQz
|
|
21
|
-
RAF1+F8ECSP3fyPH4FUw32kzoKRA5V0iR6uXAiEAwevGdpqmUIKRLGgAT/N/iZdi
|
|
22
|
-
RkBaBNzGzP0yguxa5rg=
|
|
23
|
-
-----END CERTIFICATE-----
|
|
24
|
-
"""
|
|
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
|