PyPtt 2.1.1__tar.gz → 2.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. {pyptt-2.1.1 → pyptt-2.1.2}/PKG-INFO +1 -1
  2. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/PTT.py +5 -0
  3. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/__init__.py +1 -1
  4. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_get_board_list.py +4 -0
  5. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_get_favourite_board.py +1 -0
  6. pyptt-2.1.2/PyPtt/_api_get_post_list.py +111 -0
  7. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_get_waterball.py +45 -44
  8. pyptt-2.1.2/PyPtt/_api_has_new_mail.py +77 -0
  9. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_util.py +167 -136
  10. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/config.py +5 -0
  11. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/connect_core.py +23 -2
  12. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/screens.py +2 -2
  13. pyptt-2.1.2/PyPtt/ssl_config.py +24 -0
  14. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt.egg-info/PKG-INFO +1 -1
  15. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt.egg-info/SOURCES.txt +1 -0
  16. pyptt-2.1.2/tests/test_get_content_formula.py +431 -0
  17. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_post_list_parser.py +54 -0
  18. pyptt-2.1.1/PyPtt/_api_get_post_list.py +0 -104
  19. pyptt-2.1.1/PyPtt/_api_has_new_mail.py +0 -76
  20. pyptt-2.1.1/PyPtt/ssl_config.py +0 -24
  21. {pyptt-2.1.1 → pyptt-2.1.2}/LICENSE +0 -0
  22. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_bucket.py +0 -0
  23. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_call_status.py +0 -0
  24. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_change_pw.py +0 -0
  25. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_comment.py +0 -0
  26. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_del_post.py +0 -0
  27. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_get_board_info.py +0 -0
  28. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_get_bottom_post_list.py +0 -0
  29. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_get_newest_index.py +0 -0
  30. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_get_post.py +0 -0
  31. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_get_post_index.py +0 -0
  32. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_get_time.py +0 -0
  33. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_get_user.py +0 -0
  34. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_give_money.py +0 -0
  35. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_loginout.py +0 -0
  36. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_mail.py +0 -0
  37. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_mark_post.py +0 -0
  38. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_post.py +0 -0
  39. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_reply_post.py +0 -0
  40. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_search_user.py +0 -0
  41. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/_api_set_board_title.py +0 -0
  42. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/api_server.py +0 -0
  43. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/check_value.py +0 -0
  44. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/command.py +0 -0
  45. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/data_type.py +0 -0
  46. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/exceptions.py +0 -0
  47. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/i18n.py +0 -0
  48. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/lang_en_US.py +0 -0
  49. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/lang_zh_TW.py +0 -0
  50. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/lib_util.py +0 -0
  51. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/log.py +0 -0
  52. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt/service.py +0 -0
  53. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt.egg-info/dependency_links.txt +0 -0
  54. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt.egg-info/requires.txt +0 -0
  55. {pyptt-2.1.1 → pyptt-2.1.2}/PyPtt.egg-info/top_level.txt +0 -0
  56. {pyptt-2.1.1 → pyptt-2.1.2}/README.md +0 -0
  57. {pyptt-2.1.1 → pyptt-2.1.2}/pyproject.toml +0 -0
  58. {pyptt-2.1.1 → pyptt-2.1.2}/setup.cfg +0 -0
  59. {pyptt-2.1.1 → pyptt-2.1.2}/setup.py +0 -0
  60. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_bucket.py +0 -0
  61. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_change_pw.py +0 -0
  62. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_comment.py +0 -0
  63. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_del_post.py +0 -0
  64. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_exceptions.py +0 -0
  65. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_fast_post.py +0 -0
  66. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_board_info.py +0 -0
  67. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_board_list.py +0 -0
  68. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_bottom_post_list.py +0 -0
  69. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_favourite_boards.py +0 -0
  70. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_mail.py +0 -0
  71. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_newest_index.py +0 -0
  72. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_post.py +0 -0
  73. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_post_list.py +0 -0
  74. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_post_parser.py +0 -0
  75. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_time.py +0 -0
  76. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_user.py +0 -0
  77. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_get_waterball.py +0 -0
  78. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_give_money.py +0 -0
  79. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_i18n.py +0 -0
  80. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_init.py +0 -0
  81. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_logger.py +0 -0
  82. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_mail_send_and_del.py +0 -0
  83. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_mark_post.py +0 -0
  84. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_parse_query_post.py +0 -0
  85. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_post.py +0 -0
  86. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_pyte_parity.py +0 -0
  87. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_reply_post.py +0 -0
  88. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_search_user.py +0 -0
  89. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_service.py +0 -0
  90. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_set_board_title.py +0 -0
  91. {pyptt-2.1.1 → pyptt-2.1.2}/tests/test_vt100_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPtt
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: PyPtt github: https://github.com/PyPtt/PyPtt
5
5
  Author-email: CodingMan <pttcodingman@gmail.com>
6
6
  License: GNU Lesser General Public License v3 (LGPLv3)
@@ -150,6 +150,11 @@ class API:
150
150
  check_value.check_type(verify_ssl, bool, 'verify_ssl')
151
151
  self.config.verify_ssl = verify_ssl
152
152
 
153
+ screen_height = kwargs.get('screen_height', 24)
154
+ check_value.check_type(screen_height, int, 'screen_height')
155
+ check_value.check_range(screen_height, 24, 254, 'screen_height')
156
+ self.config.screen_height = screen_height
157
+
153
158
  self.connect_core = connect_core.API(self)
154
159
  self._exist_board_list = set()
155
160
  self._exist_user_list = set()
@@ -1,4 +1,4 @@
1
- __version__ = '2.1.1'
1
+ __version__ = '2.1.2'
2
2
  __author__ = 'CodingMan'
3
3
  __email__ = 'pttcodingman@gmail.com'
4
4
 
@@ -65,6 +65,10 @@ def get_board_list(api) -> list:
65
65
  cmd = ''.join(cmd_list)
66
66
 
67
67
  board_list = []
68
+ # NOTE: expanded_screen is intentionally NOT used here.
69
+ # get_board_list traverses all boards sequentially; a mid-session NAWS resize
70
+ # causes PTT to render the first page at the old size before the resize takes
71
+ # effect, producing inconsistent page boundaries and skipping boards.
68
72
  while True:
69
73
 
70
74
  api.connect_core.send(
@@ -24,6 +24,7 @@ def get_favourite_board(api) -> list:
24
24
 
25
25
  board_list = []
26
26
  favourite_board_list = []
27
+ # NOTE: expanded_screen intentionally not used — same risk as get_board_list.
27
28
  while True:
28
29
 
29
30
  api.connect_core.send(
@@ -0,0 +1,111 @@
1
+ from typing import Dict, List
2
+
3
+ from . import _api_util
4
+ from . import check_value
5
+ from . import command
6
+ from . import connect_core
7
+ from . import data_type
8
+ from . import exceptions
9
+ from . import i18n
10
+ from . import log
11
+ from . import screens
12
+
13
+
14
+ def get_post_list(api, board: str, limit: int = 20, offset: int = 0) -> List[Dict]:
15
+ _api_util.one_thread(api)
16
+
17
+ if not api._is_login:
18
+ raise exceptions.RequireLogin(i18n.require_login)
19
+
20
+ check_value.check_type(board, str, 'board')
21
+ if limit is not None:
22
+ check_value.check_type(limit, int, 'limit')
23
+ if offset is not None:
24
+ check_value.check_type(offset, int, 'offset')
25
+
26
+ max_index = api.get_newest_index(data_type.NewIndex.BOARD, board=board)
27
+
28
+ check_value.check_range(limit, min_value=1, max_value=max_index, name='limit')
29
+ check_value.check_range(offset, min_value=0, max_value=max_index - 1, name='offset')
30
+
31
+ start_index = max_index - limit - offset + 1
32
+ end_index = start_index + limit - 1
33
+
34
+ # the first page shows 10 posts, each next page shows 20 posts
35
+ if limit <= 10:
36
+ max_round = 1
37
+ else:
38
+ # 1 is for the first page, +1 is for rounding up
39
+ # so +2
40
+ max_round = (limit - 10) // 20 + 2
41
+
42
+ cmd_list = []
43
+
44
+ cmd_list.append(str(max(1, start_index - 100)))
45
+ cmd_list.append(command.enter)
46
+ cmd_list.append(str(start_index))
47
+ cmd_list.append(command.enter)
48
+
49
+ cmd = ''.join(cmd_list)
50
+
51
+ target_list = [
52
+ connect_core.TargetUnit(screens.Target.InBoard, log_level=log.DEBUG, break_detect=True)
53
+ ]
54
+
55
+ post_list = []
56
+
57
+ with _api_util.expanded_screen(api):
58
+ for _ in range(max_round):
59
+
60
+ index = api.connect_core.send(cmd, target_list)
61
+ cmd = command.page_down
62
+ last_screen = api.connect_core.get_screen_queue()[-1]
63
+
64
+ is_parse_area = False
65
+
66
+ for line in last_screen.splitlines()[3:-1]:
67
+
68
+ if not line.startswith(api.cursor) and not is_parse_area:
69
+ continue
70
+ is_parse_area = True
71
+
72
+ if line.strip().startswith(api.cursor):
73
+ # replace the first cursor in the line
74
+ line = line.replace(api.cursor, ' ' * len(api.cursor.encode('big5uao')), 1)
75
+
76
+ if api.cursor == data_type.Cursor.NEW:
77
+ # Pad one leading space so the fixed-column offsets below
78
+ # apply equally to cursor and non-cursor rows. Before the
79
+ # VT100Parser cell-tracking fix, the parser inserted this
80
+ # padding implicitly via its off-by-one column quirk; the
81
+ # offsets here were tuned to that padded layout.
82
+ line = f' {line}'
83
+
84
+ try:
85
+ cur_index = int(line[:8].strip())
86
+ except ValueError:
87
+ # Pinned/announcement posts use '★' instead of a numeric
88
+ # index — they have no sequential index and cannot be
89
+ # fetched by index, so skip them.
90
+ continue
91
+ status = line[8:10].strip()
92
+ comment = line[10:12].strip()
93
+ list_date = line[12:17].strip()
94
+ author = line[18:31].strip()
95
+ title = line[31:]
96
+
97
+ cur_post = {
98
+ data_type.PostField.board: board,
99
+ data_type.PostField.index: cur_index,
100
+ data_type.PostField.list_date: list_date,
101
+ data_type.PostField.author: author,
102
+ data_type.PostField.title: title,
103
+ data_type.PostField.push_number: comment
104
+ }
105
+
106
+ post_list.append(cur_post)
107
+
108
+ if cur_index >= end_index:
109
+ return post_list
110
+
111
+ raise exceptions.UnknownError(i18n.unknown_error)
@@ -101,51 +101,52 @@ def get_waterball(api, post_action: WaterballPostAction = WaterballPostAction.KE
101
101
  seen_lines = set()
102
102
  all_content_parts = []
103
103
 
104
- while True:
105
- target_list = [
106
- connect_core.TargetUnit('◆ 暫無訊息記錄', log_level=log.DEBUG, break_detect=True),
107
- connect_core.TargetUnit(screens.Target.WaterBallListEnd, log_level=log.DEBUG, break_detect=True),
108
- connect_core.TargetUnit(screens.Target.InWaterBallList, log_level=log.DEBUG, break_detect=True),
109
- ]
110
-
111
- index = api.connect_core.send(cmd, target_list)
112
-
113
- if index == 0:
114
- # no waterball records
115
- api.connect_core.send('q' + command.go_main_menu, [
116
- connect_core.TargetUnit(screens.Target.MainMenu, log_level=log.DEBUG, break_detect=True),
117
- ])
118
- log.logger.info(i18n.get_waterball, '...', i18n.success)
119
- return []
120
-
121
- if index < 0:
122
- break
123
-
124
- screen = api.connect_core.get_screen_queue()[-1]
125
- for line in screen.split('\n'):
126
- line = line.strip()
127
- if line and line not in seen_lines:
128
- seen_lines.add(line)
129
- all_content_parts.append(line)
130
-
131
- if index == 1:
132
- # WaterBallListEnd — collect done, exit viewer; handle post-action prompt
133
- action_cmd = _post_action_cmd[post_action]
134
- target_list_exit = [
135
- connect_core.TargetUnit(screens.Target.WaterBallPostAction, log_level=log.DEBUG,
136
- response=action_cmd + command.enter),
137
- connect_core.TargetUnit(screens.Target.WaterballCleanup, log_level=log.DEBUG,
138
- response='y' + command.enter),
139
- connect_core.TargetUnit(screens.Target.MainMenu, log_level=log.DEBUG, break_detect=True),
104
+ with _api_util.expanded_screen(api):
105
+ while True:
106
+ target_list = [
107
+ connect_core.TargetUnit('◆ 暫無訊息記錄', log_level=log.DEBUG, break_detect=True),
108
+ connect_core.TargetUnit(screens.Target.WaterBallListEnd, log_level=log.DEBUG, break_detect=True),
109
+ connect_core.TargetUnit(screens.Target.InWaterBallList, log_level=log.DEBUG, break_detect=True),
140
110
  ]
141
- api.connect_core.send('q', target_list_exit)
142
- api.connect_core.send(command.go_main_menu, [
143
- connect_core.TargetUnit(screens.Target.MainMenu, log_level=log.DEBUG, break_detect=True),
144
- ])
145
- break
146
-
147
- # index == 2: InWaterBallList — next page
148
- cmd = command.ctrl_f
111
+
112
+ index = api.connect_core.send(cmd, target_list)
113
+
114
+ if index == 0:
115
+ # no waterball records
116
+ api.connect_core.send('q' + command.go_main_menu, [
117
+ connect_core.TargetUnit(screens.Target.MainMenu, log_level=log.DEBUG, break_detect=True),
118
+ ])
119
+ log.logger.info(i18n.get_waterball, '...', i18n.success)
120
+ return []
121
+
122
+ if index < 0:
123
+ break
124
+
125
+ screen = api.connect_core.get_screen_queue()[-1]
126
+ for line in screen.split('\n'):
127
+ line = line.strip()
128
+ if line and line not in seen_lines:
129
+ seen_lines.add(line)
130
+ all_content_parts.append(line)
131
+
132
+ if index == 1:
133
+ # WaterBallListEnd — collect done, exit viewer; handle post-action prompt
134
+ action_cmd = _post_action_cmd[post_action]
135
+ target_list_exit = [
136
+ connect_core.TargetUnit(screens.Target.WaterBallPostAction, log_level=log.DEBUG,
137
+ response=action_cmd + command.enter),
138
+ connect_core.TargetUnit(screens.Target.WaterballCleanup, log_level=log.DEBUG,
139
+ response='y' + command.enter),
140
+ connect_core.TargetUnit(screens.Target.MainMenu, log_level=log.DEBUG, break_detect=True),
141
+ ]
142
+ api.connect_core.send('q', target_list_exit)
143
+ api.connect_core.send(command.go_main_menu, [
144
+ connect_core.TargetUnit(screens.Target.MainMenu, log_level=log.DEBUG, break_detect=True),
145
+ ])
146
+ break
147
+
148
+ # index == 2: InWaterBallList — next page
149
+ cmd = command.ctrl_f
149
150
 
150
151
  result = []
151
152
  for line in _merge_continuation_lines(all_content_parts):
@@ -0,0 +1,77 @@
1
+ import re
2
+
3
+ from . import _api_util
4
+ from . import command
5
+ from . import connect_core
6
+ from . import log
7
+ from . import screens
8
+
9
+
10
+ def has_new_mail(api) -> int:
11
+ cmd_list = []
12
+ cmd_list.append(command.go_main_menu)
13
+ cmd_list.append(command.ctrl_z)
14
+ cmd_list.append('m')
15
+ # cmd_list.append('1')
16
+ # cmd_list.append(command.enter)
17
+ cmd = ''.join(cmd_list)
18
+ current_capacity = None
19
+ plus_count = 0
20
+ index_pattern = re.compile(r'(\d+)')
21
+ checked_index_list = []
22
+ break_detect = False
23
+
24
+ target_list = [
25
+ connect_core.TargetUnit(screens.Target.InMailBox, log_level=log.DEBUG, break_detect=True)
26
+ ]
27
+
28
+ api.connect_core.send(
29
+ cmd,
30
+ target_list,
31
+ )
32
+ current_capacity, _ = _api_util.get_mailbox_capacity(api)
33
+ if current_capacity > 20:
34
+ cmd_list = []
35
+ cmd_list.append(command.go_main_menu)
36
+ cmd_list.append(command.ctrl_z)
37
+ cmd_list.append('m')
38
+ cmd_list.append('1')
39
+ cmd_list.append(command.enter)
40
+ cmd = ''.join(cmd_list)
41
+
42
+ with _api_util.expanded_screen(api):
43
+ while True:
44
+ if current_capacity > 20:
45
+ api.connect_core.send(
46
+ cmd,
47
+ target_list,
48
+ )
49
+ last_screen = api.connect_core.get_screen_queue()[-1]
50
+
51
+ last_screen_list = last_screen.split('\n')
52
+ last_screen_list = last_screen_list[3:-1]
53
+ last_screen_list = [x[:10] for x in last_screen_list]
54
+
55
+ current_plus_count = 0
56
+ for line in last_screen_list:
57
+ if str(current_capacity) in line:
58
+ break_detect = True
59
+
60
+ index_result = index_pattern.search(line)
61
+ if index_result is None:
62
+ continue
63
+ current_index = index_result.group(0)
64
+ if current_index in checked_index_list:
65
+ continue
66
+ checked_index_list.append(current_index)
67
+ if '+' not in line:
68
+ continue
69
+
70
+ current_plus_count += 1
71
+
72
+ plus_count += current_plus_count
73
+ if break_detect:
74
+ break
75
+ cmd = command.ctrl_f
76
+
77
+ return plus_count
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  import threading
5
+ from contextlib import contextmanager
5
6
  from dataclasses import dataclass
6
7
  from typing import Dict, Optional
7
8
 
@@ -14,6 +15,27 @@ from . import log
14
15
  from . import screens
15
16
 
16
17
 
18
+ _MAX_GET_CONTENT_HEIGHT = 100 # PTT server hard cap
19
+
20
+
21
+ @contextmanager
22
+ def expanded_screen(api):
23
+ """Temporarily resize the PTT terminal to its maximum (100 rows) and restore on exit.
24
+
25
+ Wraps any paging loop that calls connect_core.send() in a loop so that more
26
+ content is visible per page and fewer round-trips are needed. PTT's hard
27
+ cap is 100 rows regardless of what NAWS requests.
28
+ """
29
+ original_height = api.config.screen_height
30
+ if original_height < _MAX_GET_CONTENT_HEIGHT:
31
+ api.connect_core.set_screen_height(_MAX_GET_CONTENT_HEIGHT)
32
+ try:
33
+ yield
34
+ finally:
35
+ if original_height < _MAX_GET_CONTENT_HEIGHT:
36
+ api.connect_core.set_screen_height(original_height)
37
+
38
+
17
39
  @dataclass
18
40
  class PostQueryResult:
19
41
  lock_post: bool
@@ -30,158 +52,167 @@ class PostQueryResult:
30
52
  def get_content(api, post_mode: bool = True):
31
53
  api.Unconfirmed = False
32
54
 
33
- def is_unconfirmed_handler(screen):
34
- api.Unconfirmed = True
35
-
36
- if post_mode:
37
- cmd = command.enter * 2
38
- else:
39
- cmd = command.enter
40
-
41
- target_list = [
42
- # 待證實文章
43
- connect_core.TargetUnit('本篇文章內容經站方授權之板務管理人員判斷有尚待證實之處', response=' ',
44
- handler=is_unconfirmed_handler),
45
- connect_core.TargetUnit(screens.Target.PostEnd, log_level=log.DEBUG, break_detect=True),
46
- connect_core.TargetUnit(screens.Target.InPost, log_level=log.DEBUG, break_detect=True),
47
- connect_core.TargetUnit(screens.Target.PostNoContent, log_level=log.DEBUG, break_detect=True),
48
- # 動畫文章
49
- connect_core.TargetUnit(screens.Target.Animation, response='n'),
50
- ]
51
-
52
- line_from_pattern = re.compile(r'[\d]+~[\d]+')
53
-
54
- has_control_code = False
55
- control_code_mode = False
56
- push_start = False
57
- content_start_exist = False
58
- content_start_jump = False
59
- content_start_jump_set = False
60
-
61
- first_page = True
62
- origin_post = []
63
- stop_dict = dict()
64
- new_content_part = ''
65
- get_line_b = 0
66
-
67
- while True:
68
- index = api.connect_core.send(cmd, target_list)
69
- if index == 3 or index == 4:
70
- return None, False
71
-
72
- last_screen = api.connect_core.get_screen_queue()[-1]
73
- lines = last_screen.split('\n')
74
- last_line = lines[-1]
75
- lines.pop()
76
- last_screen = '\n'.join(lines)
77
-
78
- if screens.Target.content_start in last_screen and not content_start_exist:
79
- content_start_exist = True
80
-
81
- if content_start_exist:
82
- if not content_start_jump_set:
83
- if screens.Target.content_start not in last_screen:
84
- content_start_jump = True
85
- content_start_jump_set = True
86
- else:
87
- content_start_jump = False
55
+ with expanded_screen(api):
56
+ def is_unconfirmed_handler(screen):
57
+ api.Unconfirmed = True
88
58
 
89
- pattern_result = line_from_pattern.search(last_line)
90
- if pattern_result is None:
91
- control_code_mode = True
92
- has_control_code = True
93
- else:
94
- last_read_line_list = pattern_result.group(0).split('~')
95
- last_read_line_a_temp = int(last_read_line_list[0])
96
- last_read_line_b_temp = int(last_read_line_list[1])
97
- if control_code_mode:
98
- last_read_line_a = last_read_line_a_temp - 1
99
- last_read_line_b = last_read_line_b_temp - 1
100
- control_code_mode = False
101
-
102
- if first_page:
103
- first_page = False
104
- origin_post.append(last_screen)
59
+ if post_mode:
60
+ cmd = command.enter * 2
105
61
  else:
106
- # 這裡是根據觀察畫面行數的變化歸納出的神奇公式...
107
- # 輸出的結果是要判斷出畫面的最後 x 行是新的文章內容
108
- #
109
- # 這裡是 PyPtt 最黑暗最墮落的地方,所有你所知的程式碼守則,在這裡都不適用
110
- # 每除完一次錯誤,我會陷入嚴重的創傷後壓力症候群,而我的腦袋會自動選擇遺忘這裡所有的一切
111
- # 以確保下一個週一,我可以正常上班
112
- # but it works!!!
113
-
114
- # print(LastScreen)
115
- # print(f'last_read_line_a_temp [{last_read_line_a_temp}]')
116
- # print(f'last_read_line_b_temp [{last_read_line_b_temp}]')
117
- # print(f'last_read_line_a {last_read_line_a}')
118
- # print(f'last_read_line_b {last_read_line_b}')
119
- # print(f'GetLineB {last_read_line_a_temp - last_read_line_a}')
120
- # print(f'GetLineA {last_read_line_b_temp - last_read_line_b}')
121
- # print(f'show line {last_read_line_b_temp - last_read_line_a_temp + 1}')
122
- if not control_code_mode:
62
+ cmd = command.enter
63
+
64
+ target_list = [
65
+ # 待證實文章
66
+ connect_core.TargetUnit('本篇文章內容經站方授權之板務管理人員判斷有尚待證實之處', response=' ',
67
+ handler=is_unconfirmed_handler),
68
+ connect_core.TargetUnit(screens.Target.PostEnd, log_level=log.DEBUG, break_detect=True),
69
+ connect_core.TargetUnit(screens.Target.InPost, log_level=log.DEBUG, break_detect=True),
70
+ connect_core.TargetUnit(screens.Target.PostNoContent, log_level=log.DEBUG, break_detect=True),
71
+ # 動畫文章
72
+ connect_core.TargetUnit(screens.Target.Animation, response='n'),
73
+ ]
123
74
 
124
- if last_read_line_a_temp in stop_dict:
125
- new_content_part = '\n'.join(
126
- lines[-stop_dict[last_read_line_a_temp]:])
127
- stop_dict = dict()
75
+ line_from_pattern = re.compile(r'[\d]+~[\d]+')
76
+
77
+ has_control_code = False
78
+ control_code_mode = False
79
+ push_start = False
80
+ content_start_exist = False
81
+ content_start_jump = False
82
+ content_start_jump_set = False
83
+
84
+ first_page = True
85
+ origin_post = []
86
+ stop_dict = dict()
87
+ new_content_part = ''
88
+ get_line_b = 0
89
+
90
+ while True:
91
+ index = api.connect_core.send(cmd, target_list)
92
+ if index == 3 or index == 4:
93
+ return None, False
94
+
95
+ last_screen = api.connect_core.get_screen_queue()[-1]
96
+ lines = last_screen.split('\n')
97
+ last_line = lines[-1]
98
+ lines.pop()
99
+ last_screen = '\n'.join(lines)
100
+
101
+ if screens.Target.content_start in last_screen and not content_start_exist:
102
+ content_start_exist = True
103
+
104
+ if content_start_exist:
105
+ if not content_start_jump_set:
106
+ if screens.Target.content_start not in last_screen:
107
+ content_start_jump = True
108
+ content_start_jump_set = True
128
109
  else:
129
- get_line_b = last_read_line_b_temp - last_read_line_b
130
- if get_line_b > 0:
131
- # print('Type 1')
132
- new_content_part = '\n'.join(lines[-get_line_b:])
133
- if index == 1 and len(new_content_part) == get_line_b - 1:
134
- new_content_part = '\n'.join(lines[-(get_line_b * 2):])
135
- elif origin_post:
136
- last_line_temp = origin_post[-1].strip()
137
- try_line = lines[-(get_line_b + 1)].strip()
138
-
139
- if not last_line_temp.endswith(try_line):
140
- new_content_part = try_line + '\n' + new_content_part
110
+ content_start_jump = False
111
+
112
+ pattern_result = line_from_pattern.search(last_line)
113
+ if pattern_result is None:
114
+ control_code_mode = True
115
+ has_control_code = True
116
+ else:
117
+ last_read_line_list = pattern_result.group(0).split('~')
118
+ last_read_line_a_temp = int(last_read_line_list[0])
119
+ last_read_line_b_temp = int(last_read_line_list[1])
120
+ if control_code_mode:
121
+ last_read_line_a = last_read_line_a_temp - 1
122
+ last_read_line_b = last_read_line_b_temp - 1
123
+ control_code_mode = False
124
+
125
+ if first_page:
126
+ first_page = False
127
+ origin_post.append(last_screen)
128
+ else:
129
+ # 這裡是根據觀察畫面行數的變化歸納出的神奇公式...
130
+ # 輸出的結果是要判斷出畫面的最後 x 行是新的文章內容
131
+ #
132
+ # 這裡是 PyPtt 最黑暗最墮落的地方,所有你所知的程式碼守則,在這裡都不適用
133
+ # 每除完一次錯誤,我會陷入嚴重的創傷後壓力症候群,而我的腦袋會自動選擇遺忘這裡所有的一切
134
+ # 以確保下一個週一,我可以正常上班
135
+ # but it works!!!
136
+
137
+ # print(LastScreen)
138
+ # print(f'last_read_line_a_temp [{last_read_line_a_temp}]')
139
+ # print(f'last_read_line_b_temp [{last_read_line_b_temp}]')
140
+ # print(f'last_read_line_a {last_read_line_a}')
141
+ # print(f'last_read_line_b {last_read_line_b}')
142
+ # print(f'GetLineB {last_read_line_a_temp - last_read_line_a}')
143
+ # print(f'GetLineA {last_read_line_b_temp - last_read_line_b}')
144
+ # print(f'show line {last_read_line_b_temp - last_read_line_a_temp + 1}')
145
+ if not control_code_mode:
146
+
147
+ if last_read_line_a_temp in stop_dict:
148
+ new_content_part = '\n'.join(
149
+ lines[-stop_dict[last_read_line_a_temp]:])
141
150
  stop_dict = dict()
142
151
  else:
143
- # 駐足現象,LastReadLineB跟上一次相比並沒有改變
144
- if (last_read_line_b_temp + 1) not in stop_dict:
145
- stop_dict[last_read_line_b_temp + 1] = 1
146
- stop_dict[last_read_line_b_temp + 1] += 1
147
-
148
- get_line_a = last_read_line_a_temp - last_read_line_a
149
-
150
- if get_line_a > 0:
151
- # print(f'Type 2 get_line_a [{get_line_a}]')
152
- new_content_part = '\n'.join(lines[-get_line_a:])
152
+ get_line_b = last_read_line_b_temp - last_read_line_b
153
+ if get_line_b > 0:
154
+ # print('Type 1')
155
+ new_content_part = '\n'.join(lines[-get_line_b:])
156
+ # The doubling and try_line heuristics were tuned for
157
+ # 24-row screens (max get_line_b ≈ 22). With larger
158
+ # screen_height the delta can be 50+, causing both
159
+ # heuristics to look back into the post header rows
160
+ # (作者/標題/時間) and inject them into the body.
161
+ # Only apply them when the delta is within the original
162
+ # 24-row regime.
163
+ if get_line_b <= 23:
164
+ if index == 1 and len(new_content_part) == get_line_b - 1:
165
+ new_content_part = '\n'.join(lines[-(get_line_b * 2):])
166
+ elif origin_post and get_line_b + 1 <= len(lines):
167
+ last_line_temp = origin_post[-1].strip()
168
+ try_line = lines[-(get_line_b + 1)].strip()
169
+
170
+ if not last_line_temp.endswith(try_line):
171
+ new_content_part = try_line + '\n' + new_content_part
172
+ stop_dict = dict()
153
173
  else:
154
- new_content_part = '\n'.join(lines)
155
-
156
- else:
157
- new_content_part = lines[-1]
174
+ # 駐足現象,LastReadLineB跟上一次相比並沒有改變
175
+ if (last_read_line_b_temp + 1) not in stop_dict:
176
+ stop_dict[last_read_line_b_temp + 1] = 1
177
+ stop_dict[last_read_line_b_temp + 1] += 1
158
178
 
159
- origin_post.append(new_content_part)
179
+ get_line_a = last_read_line_a_temp - last_read_line_a
160
180
 
161
- log.logger.debug('NewContentPart', new_content_part)
181
+ if get_line_a > 0:
182
+ # print(f'Type 2 get_line_a [{get_line_a}]')
183
+ new_content_part = '\n'.join(lines[-get_line_a:])
184
+ else:
185
+ new_content_part = '\n'.join(lines)
162
186
 
163
- if index == 1:
164
- if content_start_jump and len(new_content_part) == 0:
165
- get_line_b += 1
166
- new_content_part = '\n'.join(lines[-get_line_b:])
187
+ else:
188
+ new_content_part = lines[-1]
167
189
 
168
- origin_post.pop()
169
190
  origin_post.append(new_content_part)
170
- break
171
191
 
172
- if not control_code_mode:
173
- last_read_line_a = last_read_line_a_temp
174
- last_read_line_b = last_read_line_b_temp
192
+ log.logger.debug('NewContentPart', new_content_part)
175
193
 
176
- for EC in screens.Target.content_end_list:
177
- if EC in last_screen:
178
- push_start = True
194
+ if index == 1:
195
+ if content_start_jump and len(new_content_part) == 0:
196
+ get_line_b += 1
197
+ new_content_part = '\n'.join(lines[-get_line_b:])
198
+
199
+ origin_post.pop()
200
+ origin_post.append(new_content_part)
179
201
  break
180
202
 
181
- if push_start:
182
- cmd = command.right
183
- else:
184
- cmd = command.down
203
+ if not control_code_mode:
204
+ last_read_line_a = last_read_line_a_temp
205
+ last_read_line_b = last_read_line_b_temp
206
+
207
+ for EC in screens.Target.content_end_list:
208
+ if EC in last_screen:
209
+ push_start = True
210
+ break
211
+
212
+ if push_start:
213
+ cmd = command.right
214
+ else:
215
+ cmd = command.down
185
216
 
186
217
  # print(api.Unconfirmed)
187
218
  origin_post = '\n'.join(origin_post)