PyPtt 2.3.14__tar.gz → 2.3.16__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-2.3.14 → pyptt-2.3.16}/PKG-INFO +1 -1
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/__init__.py +1 -1
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_board_info.py +11 -12
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/connect_core.py +16 -16
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/screens.py +2 -1
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt.egg-info/PKG-INFO +1 -1
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt.egg-info/SOURCES.txt +2 -1
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_board_setting_flow.py +30 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_board_info_parser.py +57 -0
- pyptt-2.3.16/tests/test_websocket_endpoint.py +39 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/LICENSE +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/PTT.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_bucket.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_call_status.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_change_pw.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_comment.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_del_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_board_list.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_bottom_post_list.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_favourite_board.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_newest_index.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_post_index.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_post_list.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_time.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_user.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_get_waterball.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_give_money.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_has_new_mail.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_loginout.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_mail.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_mark_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_reply_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_search_user.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_set_board_title.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_set_signature_file.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_api_util.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_uao/LICENSE +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_uao/__init__.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_uao/b2u.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/_uao/u2b.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/api_server.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/check_value.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/command.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/config.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/data_type.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/exceptions.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/i18n.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/lang_en_US.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/lang_zh_TW.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/lib_util.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/log.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/service.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt/ssl_config.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt.egg-info/dependency_links.txt +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt.egg-info/requires.txt +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/PyPtt.egg-info/top_level.txt +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/README.md +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/pyproject.toml +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/setup.cfg +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/setup.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_backup_effective.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_backup_param.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_bucket.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_change_pw.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_comment.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_connect_core_timeout.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_del_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_del_post_unit.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_exceptions.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_fast_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_board_info.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_board_list.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_bottom_post_list.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_content_formula.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_favourite_boards.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_mail.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_newest_index.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_post_list.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_post_list_parser.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_post_parser.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_time.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_user.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_get_waterball.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_give_money.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_half_dead_connection.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_i18n.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_incremental_parity.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_init.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_logger.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_login_logout.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_mail_send_and_del.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_main_menu_status_bar.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_mark_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_new_screen_fixes.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_parse_query_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_post_anonymous.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_pyte_parity.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_reply_post.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_search_user.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_service.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_set_board_title.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_set_signature_file.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_status_line.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_uao_codec.py +0 -0
- {pyptt-2.3.14 → pyptt-2.3.16}/tests/test_vt100_parser.py +0 -0
|
@@ -95,8 +95,10 @@ def get_board_info(api, board: str, get_post_kind: bool, call_by_others: bool) -
|
|
|
95
95
|
raise exceptions.ConnectionClosed()
|
|
96
96
|
|
|
97
97
|
boardname = None
|
|
98
|
-
#
|
|
99
|
-
|
|
98
|
+
# 2026-11-01 起: '名稱: Test 板主: ...';2026-10: '看板名稱: Test 板主名單: ...';
|
|
99
|
+
# 舊版: '《Test》看板設定'
|
|
100
|
+
r = re.search(r'^(?:看板)?名稱: (\S+)|《(.+)》看板設定', ori_screen, re.M)
|
|
101
|
+
new_layout = r is not None and r.group(1) is not None
|
|
100
102
|
if r is not None:
|
|
101
103
|
boardname = (r.group(1) or r.group(2)).strip()
|
|
102
104
|
|
|
@@ -119,20 +121,17 @@ def get_board_info(api, board: str, get_post_kind: bool, call_by_others: bool) -
|
|
|
119
121
|
logger.debug('中文敘述', chinese_des)
|
|
120
122
|
|
|
121
123
|
moderators = []
|
|
122
|
-
|
|
124
|
+
# 2026-11-01 起帳號板主以 '/' 串接,非帳號的板主稱號接在後面、以空白分隔:
|
|
125
|
+
# '板主: ubcs/redDest 初音未來'
|
|
126
|
+
p = re.compile(r'板主(?:名單)?: (.+)')
|
|
123
127
|
r = p.search(ori_screen)
|
|
124
128
|
if r is not None:
|
|
125
|
-
moderator_line = r.group(
|
|
126
|
-
if '(無)' in moderator_line:
|
|
127
|
-
moderators = []
|
|
128
|
-
else:
|
|
129
|
-
moderators = moderator_line.split('/')
|
|
130
|
-
for moderator in moderators.copy():
|
|
131
|
-
if moderator in ('徵求中', ''):
|
|
132
|
-
moderators.remove(moderator)
|
|
129
|
+
moderator_line = r.group(1).strip()
|
|
130
|
+
if '(無)' not in moderator_line:
|
|
131
|
+
moderators = [m for m in re.split(r'[/\s]+', moderator_line) if m not in ('徵求中', '')]
|
|
133
132
|
logger.debug('板主名單', moderators)
|
|
134
133
|
|
|
135
|
-
if
|
|
134
|
+
if new_layout:
|
|
136
135
|
# 新版畫面每項一列: ' 9. 快速連推文章 限制 (間隔 5 秒)'。
|
|
137
136
|
# 2026-10-19 起欄寬變寬;編輯模式另有熱鍵標示 ' 1. (b)中文敘述' 與
|
|
138
137
|
# 權限註記 '開放 (限站長)',兩者都去掉再比對。
|
|
@@ -179,6 +179,21 @@ class API(object):
|
|
|
179
179
|
asyncio.set_event_loop(self._loop)
|
|
180
180
|
return self._loop
|
|
181
181
|
|
|
182
|
+
def _websocket_endpoint(self) -> tuple[str, str]:
|
|
183
|
+
# PTT1 started answering wss://ws.ptt.cc/bbs/ (trailing slash) with HTTP 404 on
|
|
184
|
+
# 2026-11-06; term.ptt.cc connects to /bbs, so use the slash-less path everywhere
|
|
185
|
+
if self.config.host == data_type.HOST.PTT1:
|
|
186
|
+
return 'wss://ws.ptt.cc/bbs', 'https://term.ptt.cc'
|
|
187
|
+
if self.config.host == data_type.HOST.PTT2:
|
|
188
|
+
return 'wss://ws.ptt2.cc/bbs', 'https://term.ptt2.cc'
|
|
189
|
+
if self.config.host == data_type.HOST.LOCALHOST:
|
|
190
|
+
# ponytail: local pttbbs docker image (bbsdocker/imageptt) serves plain ws
|
|
191
|
+
# (no TLS) on 48763; config.port defaults to 23 (telnet default) so treat
|
|
192
|
+
# that as "not set" and fall back to imageptt's default port
|
|
193
|
+
port = self.config.port if self.config.port != 23 else 48763
|
|
194
|
+
return f'ws://localhost:{port}/bbs', 'http://localhost'
|
|
195
|
+
return f'wss://{self.config.host}', 'https://term.ptt.cc'
|
|
196
|
+
|
|
182
197
|
def connect(self) -> None:
|
|
183
198
|
def _wait():
|
|
184
199
|
for i in range(self.config.retry_wait_time):
|
|
@@ -198,22 +213,7 @@ class API(object):
|
|
|
198
213
|
|
|
199
214
|
self.current_encoding = 'big5uao'
|
|
200
215
|
|
|
201
|
-
|
|
202
|
-
websocket_host = 'wss://ws.ptt.cc/bbs/'
|
|
203
|
-
websocket_origin = 'https://term.ptt.cc'
|
|
204
|
-
elif self.config.host == data_type.HOST.PTT2:
|
|
205
|
-
websocket_host = 'wss://ws.ptt2.cc/bbs/'
|
|
206
|
-
websocket_origin = 'https://term.ptt2.cc'
|
|
207
|
-
elif self.config.host == data_type.HOST.LOCALHOST:
|
|
208
|
-
# ponytail: local pttbbs docker image (bbsdocker/imageptt) serves plain ws
|
|
209
|
-
# (no TLS) on 48763; config.port defaults to 23 (telnet default) so treat
|
|
210
|
-
# that as "not set" and fall back to imageptt's default port
|
|
211
|
-
port = self.config.port if self.config.port != 23 else 48763
|
|
212
|
-
websocket_host = f'ws://localhost:{port}/bbs'
|
|
213
|
-
websocket_origin = 'http://localhost'
|
|
214
|
-
else:
|
|
215
|
-
websocket_host = f'wss://{self.config.host}'
|
|
216
|
-
websocket_origin = 'https://term.ptt.cc'
|
|
216
|
+
websocket_host, websocket_origin = self._websocket_endpoint()
|
|
217
217
|
|
|
218
218
|
self._pending = []
|
|
219
219
|
connect_success = False
|
|
@@ -250,8 +250,9 @@ class Target:
|
|
|
250
250
|
'看板設定與管理 [唯讀檢視]',
|
|
251
251
|
'(←)離開',
|
|
252
252
|
]
|
|
253
|
+
# 2026-11-01 起編輯模式頁首改為「[管理權限: 板主]」
|
|
253
254
|
BoardSettingEdit = [
|
|
254
|
-
'編輯模式 (按 Ctrl-P 唯讀)',
|
|
255
|
+
['編輯模式 (按 Ctrl-P 唯讀)', '看板設定與管理 [管理權限:'],
|
|
255
256
|
'看板設定(編輯)',
|
|
256
257
|
]
|
|
257
258
|
# 舊版 (本機 pttbbs): i → 設定頁 + 任意鍵提示,^P → 單行輸入「要改變的設定」
|
|
@@ -232,6 +232,36 @@ class TestSetBoardTitleNewFlow:
|
|
|
232
232
|
assert api.connect_core.keys == ['I', LEFT]
|
|
233
233
|
|
|
234
234
|
|
|
235
|
+
# 2026-11-01 PTT1 改版: 編輯模式頁首改成 '[管理權限: 板主]',頁尾 (w) 改叫禁言名單
|
|
236
|
+
EDIT_20261101 = EDIT.replace('[編輯模式 (按 Ctrl-P 唯讀)]', '[管理權限: 板主]').replace(
|
|
237
|
+
'(w)水桶名單', '(w)禁言名單').replace('看板名稱: give 板主名單: SomeBM', '名稱: give 板主: SomeBM')
|
|
238
|
+
EDIT_20261101_CURSOR16 = EDIT_CURSOR16.replace('[編輯模式 (按 Ctrl-P 唯讀)]', '[管理權限: 板主]')
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class TestEditHeader20261101:
|
|
242
|
+
def test_set_board_title(self, make_api):
|
|
243
|
+
assert '[管理權限: 板主]' in EDIT_20261101 and '編輯模式' not in EDIT_20261101
|
|
244
|
+
text = '[贈送] 新標題 test'
|
|
245
|
+
api = make_api([READONLY, EDIT_20261101, TITLE_PROMPT, _typed(text), EDIT_20261101, SAVED, ARTICLE_LIST])
|
|
246
|
+
_api_set_board_title.set_board_title(api, 'give', '新標題 test')
|
|
247
|
+
keys = api.connect_core.keys
|
|
248
|
+
assert keys[:3] == ['I', CTRL_P, ENTER]
|
|
249
|
+
assert keys[3].lstrip(command.backspace) == text
|
|
250
|
+
assert keys[4:] == [ENTER, LEFT, LEFT]
|
|
251
|
+
|
|
252
|
+
def test_bucket(self, make_api):
|
|
253
|
+
api = make_api([READONLY, EDIT_20261101, BAKUMAN, BAKUMAN_ID, BAKUMAN_PERIOD, BAKUMAN_REASON,
|
|
254
|
+
BAKUMAN_CONFIRM, BAKUMAN_DONE, BAKUMAN, EDIT_20261101_CURSOR16, UNCHANGED, ARTICLE_LIST])
|
|
255
|
+
_api_bucket.bucket(api, 'give', 1, 'PyPtt test', 'CodingMan')
|
|
256
|
+
assert api.connect_core.keys == [
|
|
257
|
+
'i', CTRL_P, 'w', 'a' + ENTER, 'CodingMan' + ENTER, '1' + ENTER, 'PyPtt test' + ENTER, 'y' + ENTER,
|
|
258
|
+
LEFT, ENTER, LEFT, LEFT]
|
|
259
|
+
|
|
260
|
+
def test_exit_key_and_in_board_setting(self):
|
|
261
|
+
assert screens.in_board_setting(EDIT_20261101)
|
|
262
|
+
assert screens.board_setting_exit_key(EDIT_20261101) == LEFT
|
|
263
|
+
|
|
264
|
+
|
|
235
265
|
class TestBucketNewFlow:
|
|
236
266
|
def test_happy_path(self, make_api):
|
|
237
267
|
api = make_api([READONLY, EDIT, BAKUMAN, BAKUMAN_ID, BAKUMAN_PERIOD, BAKUMAN_REASON, BAKUMAN_CONFIRM,
|
|
@@ -312,3 +312,60 @@ def test_no_such_board_after_i_still_leaves_menu():
|
|
|
312
312
|
with pytest.raises(exceptions.NoSuchBoard):
|
|
313
313
|
_api_get_board_info.get_board_info(api, 'Test', get_post_kind=False, call_by_others=True)
|
|
314
314
|
assert api.connect_core.keys[-1] == command.left
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
# 2026-11-01 PTT1 改版: 標頭 '看板名稱: X 板主名單: ...' 改成 '名稱: X 板主: ...';
|
|
318
|
+
# 帳號板主以 '/' 串接,非帳號的板主稱號 (綠字) 以空白分隔接在後面
|
|
319
|
+
_ROWS_20261101 = '\n'.join([
|
|
320
|
+
'> 1. 中文敘述 [八卦] X教授 R.I.P',
|
|
321
|
+
' 2. 公開狀態 (是否隱形) 公開',
|
|
322
|
+
' 3. 隱板時進入十大排行榜 可以',
|
|
323
|
+
' 4. 非看板會員發文 開放',
|
|
324
|
+
' 5. 回應文章 開放',
|
|
325
|
+
' 6. 自刪文章 開放',
|
|
326
|
+
' 7. 推薦文章 (推文) 開放',
|
|
327
|
+
' 8. 噓文 開放',
|
|
328
|
+
' 9. 快速連推文章 限制 (間隔 5 秒)',
|
|
329
|
+
' 10. 推文時記錄來源 IP 自動記錄',
|
|
330
|
+
' 11. 推文開頭自動對齊 不用對齊',
|
|
331
|
+
' 12. 板主刪除部份違規文字 可',
|
|
332
|
+
' 13. 轉錄文章自動記錄 會 (需發文權限)',
|
|
333
|
+
' 14. 冷靜模式 未設定',
|
|
334
|
+
' 15. 未滿十八歲進入 禁止',
|
|
335
|
+
' 16. [發文條件] 登入次數 無限制',
|
|
336
|
+
' 17. [發文條件] 退文篇數 無限制',
|
|
337
|
+
' 18. 個人發文推文權限 具有發文與推文權限',
|
|
338
|
+
' 19. 預設連署限制 (登入/退文) 無特別限制',
|
|
339
|
+
' 20. [名單] 設定禁言名單(水桶) [唯讀檢視]',
|
|
340
|
+
' 21. [名單] 可見會員名單 [唯讀檢視]',
|
|
341
|
+
' 看板資訊 (PgUp)上頁 (PgDn)下頁 (1)跳號 (←)離開 (h)說明',
|
|
342
|
+
])
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _info_20261101(header):
|
|
346
|
+
return ' 看板設定與管理 [唯讀檢視]\n' + header + '\n' + _ROWS_20261101
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@pytest.mark.parametrize('header, board, moderators', [
|
|
350
|
+
('名稱: Gossiping 板主: ubcs/redDest 初音未來', 'Gossiping', ['ubcs', 'redDest', '初音未來']),
|
|
351
|
+
('名稱: C_Chat 板主: daniel0527/arrenwu/motw1999', 'C_Chat', ['daniel0527', 'arrenwu', 'motw1999']),
|
|
352
|
+
('名稱: SYSOP 板主: 站長', 'SYSOP', ['站長']),
|
|
353
|
+
('名稱: Python 板主: (無)', 'Python', []),
|
|
354
|
+
])
|
|
355
|
+
def test_layout_20261101(header, board, moderators):
|
|
356
|
+
info = _info(board, _info_20261101(header))
|
|
357
|
+
assert info[BoardField.board] == board
|
|
358
|
+
assert info[BoardField.moderators] == moderators
|
|
359
|
+
assert info[BoardField.mandarin_des] == '[八卦] X教授 R.I.P'
|
|
360
|
+
assert info[BoardField.into_top_ten_when_hide] is True
|
|
361
|
+
assert info[BoardField.can_fast_push] is False
|
|
362
|
+
assert info[BoardField.min_interval_between_comments] == 5
|
|
363
|
+
assert info[BoardField.is_comment_record_ip] is True
|
|
364
|
+
assert info[BoardField.can_moderators_del_illegal_content] is True
|
|
365
|
+
assert info[BoardField.is_require18] is True
|
|
366
|
+
assert info[BoardField.require_login_time] == 0
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def test_layout_20261101_board_name_mismatch_raises_no_such_board():
|
|
370
|
+
with pytest.raises(exceptions.NoSuchBoard):
|
|
371
|
+
_info('Test', _info_20261101('名稱: Python 板主: (無)'))
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""No-network regression test for the WebSocket endpoint connect_core dials.
|
|
2
|
+
|
|
3
|
+
On 2026-11-06 PTT1's gateway started answering wss://ws.ptt.cc/bbs/ (with a
|
|
4
|
+
trailing slash) with HTTP 404, so login died with ConnectError before reaching
|
|
5
|
+
any screen. term.ptt.cc connects to wss://ws.ptt.cc/bbs, which still works.
|
|
6
|
+
"""
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from PyPtt import config, connect_core, data_type
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FakeApi:
|
|
13
|
+
def __init__(self, host, port=23):
|
|
14
|
+
self.config = config.Config()
|
|
15
|
+
self.config.host = host
|
|
16
|
+
self.config.port = port
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _endpoint(host, port=23):
|
|
20
|
+
return connect_core.API(FakeApi(host, port))._websocket_endpoint()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.mark.parametrize('host, url, origin', [
|
|
24
|
+
(data_type.HOST.PTT1, 'wss://ws.ptt.cc/bbs', 'https://term.ptt.cc'),
|
|
25
|
+
(data_type.HOST.PTT2, 'wss://ws.ptt2.cc/bbs', 'https://term.ptt2.cc'),
|
|
26
|
+
(data_type.HOST.LOCALHOST, 'ws://localhost:48763/bbs', 'http://localhost'),
|
|
27
|
+
])
|
|
28
|
+
def test_known_hosts(host, url, origin):
|
|
29
|
+
assert _endpoint(host) == (url, origin)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.parametrize('host', [data_type.HOST.PTT1, data_type.HOST.PTT2, data_type.HOST.LOCALHOST])
|
|
33
|
+
def test_no_trailing_slash(host):
|
|
34
|
+
url, _ = _endpoint(host)
|
|
35
|
+
assert not url.endswith('/')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_localhost_custom_port():
|
|
39
|
+
assert _endpoint(data_type.HOST.LOCALHOST, port=12345)[0] == 'ws://localhost:12345/bbs'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|