PyPtt 2.3.8__tar.gz → 2.3.10__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.8 → pyptt-2.3.10}/PKG-INFO +1 -1
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/__init__.py +1 -1
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_time.py +6 -2
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/screens.py +8 -5
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt.egg-info/PKG-INFO +1 -1
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt.egg-info/SOURCES.txt +1 -0
- pyptt-2.3.10/tests/test_main_menu_status_bar.py +24 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_vt100_parser.py +30 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/LICENSE +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/PTT.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_bucket.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_call_status.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_change_pw.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_comment.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_del_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_board_info.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_board_list.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_bottom_post_list.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_favourite_board.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_newest_index.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_post_index.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_post_list.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_user.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_get_waterball.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_give_money.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_has_new_mail.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_loginout.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_mail.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_mark_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_reply_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_search_user.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_set_board_title.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_set_signature_file.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_api_util.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_uao/LICENSE +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_uao/__init__.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_uao/b2u.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/_uao/u2b.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/api_server.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/check_value.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/command.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/config.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/connect_core.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/data_type.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/exceptions.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/i18n.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/lang_en_US.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/lang_zh_TW.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/lib_util.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/log.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/service.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt/ssl_config.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt.egg-info/dependency_links.txt +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt.egg-info/requires.txt +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/PyPtt.egg-info/top_level.txt +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/README.md +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/pyproject.toml +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/setup.cfg +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/setup.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_backup_effective.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_backup_param.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_bucket.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_change_pw.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_comment.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_connect_core_timeout.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_del_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_del_post_unit.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_exceptions.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_fast_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_board_info.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_board_list.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_bottom_post_list.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_content_formula.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_favourite_boards.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_mail.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_newest_index.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_post_list.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_post_list_parser.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_post_parser.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_time.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_user.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_get_waterball.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_give_money.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_half_dead_connection.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_i18n.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_incremental_parity.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_init.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_logger.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_login_logout.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_mail_send_and_del.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_mark_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_parse_query_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_post_anonymous.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_pyte_parity.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_reply_post.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_search_user.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_service.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_set_board_title.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_set_signature_file.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_status_line.py +0 -0
- {pyptt-2.3.8 → pyptt-2.3.10}/tests/test_uao_codec.py +0 -0
|
@@ -37,9 +37,13 @@ def get_time(api) -> str:
|
|
|
37
37
|
|
|
38
38
|
# 0:00
|
|
39
39
|
|
|
40
|
+
return _find_time(line_list)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _find_time(line_list):
|
|
40
44
|
for line in line_list:
|
|
41
|
-
#
|
|
42
|
-
if '線上' in line
|
|
45
|
+
# 狀態列有多種寫法 (週三/星期六, 新版沒有「我是」), 只認「線上」+ 時間。
|
|
46
|
+
if '線上' in line:
|
|
43
47
|
result = pattern.search(line)
|
|
44
48
|
if result is not None:
|
|
45
49
|
return result.group(0)
|
|
@@ -66,11 +66,11 @@ class Target:
|
|
|
66
66
|
# 正式站狀態列有兩種寫法, 短的那種會把逗號後的空格與中括號吃掉:
|
|
67
67
|
# [5/23 星期六 16:40] [ 射手時 ] 線上27866人, 我是CodingMan [呼叫器]打開
|
|
68
68
|
# 8/19週三22:35 [ 七夕 ] 線上30721人,我是DeepLearning 呼叫器關閉 (h)說明
|
|
69
|
-
#
|
|
69
|
+
# 新版 (2026-10) 已拿掉「我是」「呼叫器」:
|
|
70
|
+
# 主選單 世界郵政日 10/9 週五 15:04 | janice001 | 線上24305人 (h)說明
|
|
71
|
+
# 只比對三種格式共同的最短關鍵詞。
|
|
70
72
|
MainMenu = [
|
|
71
73
|
'離開,再見',
|
|
72
|
-
'我是',
|
|
73
|
-
'呼叫器',
|
|
74
74
|
]
|
|
75
75
|
|
|
76
76
|
MainMenu_Exiting = [
|
|
@@ -267,8 +267,9 @@ def show(config, screen_queue, function_name=None):
|
|
|
267
267
|
|
|
268
268
|
xy_pattern_h = re.compile(r'^=ESC=\[[\d]+;[\d]+H')
|
|
269
269
|
xy_pattern_s = re.compile(r'^=ESC=\[[\d]+;[\d]+s')
|
|
270
|
-
#
|
|
271
|
-
|
|
270
|
+
# 依 ECMA-48 剝掉引擎不處理的 CSI (顏色、ESC[?2026h、ESC[6n…, PttCurrent M.1789835163.A.633),
|
|
271
|
+
# 以及被下一個 ESC 打斷的孤立 ESC (主選單動畫尾端的 '\x1b\x1b[?2026h'); 留著會讓引擎凍住畫面
|
|
272
|
+
_color_sgr = re.compile(r'\x1B(?=\x1B)|\x1B\[(?!(?:\d+;\d+)?[Hs]|K|2J)[0-?]*[ -/]*[@-~]')
|
|
272
273
|
|
|
273
274
|
|
|
274
275
|
def _preprocess(data: str) -> str:
|
|
@@ -494,6 +495,8 @@ class IncrementalScreen(_VT100Engine):
|
|
|
494
495
|
cut -= 1
|
|
495
496
|
esc = s.rfind('\x1b')
|
|
496
497
|
if esc != -1 and not _escape_complete(s[esc:]):
|
|
498
|
+
while esc > 0 and s[esc - 1] == '\x1b':
|
|
499
|
+
esc -= 1 # 孤立 ESC 要跟後面的 ESC 一起送, _preprocess 才認得
|
|
497
500
|
cut = min(cut, esc)
|
|
498
501
|
return cut
|
|
499
502
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from PyPtt import screens
|
|
4
|
+
from PyPtt._api_get_time import _find_time
|
|
5
|
+
|
|
6
|
+
NEW = '主選單 世界郵政日 10/9 週五 15:04 | janice001 | 線上24305人 (h)說明'
|
|
7
|
+
OLD = '[5/23 星期六 16:40] [ 射手時 ] 線上27866人, 我是CodingMan [呼叫器]打開'
|
|
8
|
+
SHORT = '8/19週三22:35 [ 七夕 ] 線上30721人,我是DeepLearning 呼叫器關閉 (h)說明'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.parametrize('bar, expected', [(NEW, '15:04'), (OLD, '16:40'), (SHORT, '22:35')])
|
|
12
|
+
def test_status_bar(bar, expected):
|
|
13
|
+
screen = f' (G)離開,再見\n\n{bar}'
|
|
14
|
+
assert all(t in screen for t in screens.Target.MainMenu)
|
|
15
|
+
assert _find_time(screen.split('\n')[-3:]) == expected
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_find_time_none():
|
|
19
|
+
assert _find_time(['沒有狀態列']) is None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_cursor_to_goodbye_matches_main_menu():
|
|
23
|
+
# login 會在 CursorToGoodbye 尾端追加游標項目, 只比對 MainMenu 的前綴
|
|
24
|
+
assert screens.Target.CursorToGoodbye[:len(screens.Target.MainMenu)] == screens.Target.MainMenu
|
|
@@ -451,3 +451,33 @@ def test_dec_private_mode_escape_does_not_drop_screen():
|
|
|
451
451
|
# PTT 送 ESC[?2026h/l (同步輸出); 沒被吃掉時引擎會在此中止, 整個畫面變空白 (登入 LoginError)
|
|
452
452
|
lines = parse(b'\x1b[2J\x1b[?2026h\x1b[3;1Hhello\x1b[?2026l\x1b[4;1Hworld')
|
|
453
453
|
assert 'hello' in lines[2] and 'world' in lines[3]
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def test_dsr_query_is_ignored():
|
|
457
|
+
# PTT 歡迎畫面送 ESC[6n (DSR 游標位置查詢) 且不等回應; 沒被吃掉時畫面凍結在登入提示之前 (LoginError)
|
|
458
|
+
lines = parse(b'\x1b[2J\x1b[3;1Hhello\x1b[6n\x1b[4;1Hworld')
|
|
459
|
+
assert 'hello' in lines[2] and 'world' in lines[3]
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def test_stray_esc_and_unknown_csi_are_ignored():
|
|
463
|
+
# PTT 主選單動畫尾端送 '\x1b\x1b[?2026h' (孤立 ESC); 未知 CSI 依 ECMA-48 一律略過 (PttCurrent M.1789835163.A.633)
|
|
464
|
+
lines = parse(b'\x1b[30m \x1b\x1b[?2026h\x1b[H\x1b[2J\x1b[3;1Hhello\x1b[0K\x1b[>4;1m\x1b[4;1Hworld')
|
|
465
|
+
assert 'hello' in lines[2] and 'world' in lines[3]
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def test_stray_esc_split_across_chunks():
|
|
469
|
+
from PyPtt.screens import IncrementalScreen
|
|
470
|
+
data = b'\x1b[2J\x1b[3;1Hhello\x1b\x1b[?2026h\x1b[4;1Hworld'
|
|
471
|
+
for cut in range(1, len(data)):
|
|
472
|
+
s = IncrementalScreen('utf-8')
|
|
473
|
+
s.feed(data[:cut])
|
|
474
|
+
s.feed(data[cut:])
|
|
475
|
+
lines = s.screen.split('\n')
|
|
476
|
+
assert 'hello' in lines[2] and 'world' in lines[3], cut
|
|
477
|
+
# ESC 後接非 ESC (串流會把尾端空白留到下一輪): 串流與批次須一致
|
|
478
|
+
data = b'a\x1b b'
|
|
479
|
+
for cut in range(1, len(data)):
|
|
480
|
+
s = IncrementalScreen('utf-8')
|
|
481
|
+
s.feed(data[:cut])
|
|
482
|
+
s.feed(data[cut:])
|
|
483
|
+
assert s.screen == VT100Parser(data, 'utf-8').screen, cut
|
|
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
|