PyPtt 2.0.5__tar.gz → 2.0.7__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 (80) hide show
  1. {pyptt-2.0.5 → pyptt-2.0.7}/PKG-INFO +1 -1
  2. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/__init__.py +3 -1
  3. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_user.py +33 -25
  4. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_waterball.py +10 -2
  5. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/screens.py +2 -0
  6. pyptt-2.0.7/PyPtt/ssl_config.py +24 -0
  7. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt.egg-info/PKG-INFO +1 -1
  8. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_user.py +6 -13
  9. pyptt-2.0.5/PyPtt/ssl_config.py +0 -24
  10. {pyptt-2.0.5 → pyptt-2.0.7}/LICENSE +0 -0
  11. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/PTT.py +0 -0
  12. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_bucket.py +0 -0
  13. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_call_status.py +0 -0
  14. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_change_pw.py +0 -0
  15. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_comment.py +0 -0
  16. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_del_post.py +0 -0
  17. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_board_info.py +0 -0
  18. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_board_list.py +0 -0
  19. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_bottom_post_list.py +0 -0
  20. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_favourite_board.py +0 -0
  21. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_newest_index.py +0 -0
  22. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_post.py +0 -0
  23. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_post_index.py +0 -0
  24. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_post_list.py +0 -0
  25. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_get_time.py +0 -0
  26. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_give_money.py +0 -0
  27. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_has_new_mail.py +0 -0
  28. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_loginout.py +0 -0
  29. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_mail.py +0 -0
  30. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_mark_post.py +0 -0
  31. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_post.py +0 -0
  32. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_reply_post.py +0 -0
  33. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_search_user.py +0 -0
  34. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_set_board_title.py +0 -0
  35. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/_api_util.py +0 -0
  36. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/api_server.py +0 -0
  37. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/check_value.py +0 -0
  38. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/command.py +0 -0
  39. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/config.py +0 -0
  40. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/connect_core.py +0 -0
  41. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/data_type.py +0 -0
  42. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/exceptions.py +0 -0
  43. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/i18n.py +0 -0
  44. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/lang_en_US.py +0 -0
  45. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/lang_zh_TW.py +0 -0
  46. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/lib_util.py +0 -0
  47. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/log.py +0 -0
  48. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt/service.py +0 -0
  49. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt.egg-info/SOURCES.txt +0 -0
  50. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt.egg-info/dependency_links.txt +0 -0
  51. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt.egg-info/requires.txt +0 -0
  52. {pyptt-2.0.5 → pyptt-2.0.7}/PyPtt.egg-info/top_level.txt +0 -0
  53. {pyptt-2.0.5 → pyptt-2.0.7}/README.md +0 -0
  54. {pyptt-2.0.5 → pyptt-2.0.7}/pyproject.toml +0 -0
  55. {pyptt-2.0.5 → pyptt-2.0.7}/setup.cfg +0 -0
  56. {pyptt-2.0.5 → pyptt-2.0.7}/setup.py +0 -0
  57. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_change_pw.py +0 -0
  58. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_comment.py +0 -0
  59. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_del_post.py +0 -0
  60. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_exceptions.py +0 -0
  61. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_board_info.py +0 -0
  62. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_board_list.py +0 -0
  63. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_bottom_post_list.py +0 -0
  64. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_favourite_boards.py +0 -0
  65. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_mail.py +0 -0
  66. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_newest_index.py +0 -0
  67. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_post.py +0 -0
  68. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_post_list.py +0 -0
  69. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_post_parser.py +0 -0
  70. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_time.py +0 -0
  71. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_get_waterball.py +0 -0
  72. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_give_money.py +0 -0
  73. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_i18n.py +0 -0
  74. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_init.py +0 -0
  75. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_logger.py +0 -0
  76. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_parse_query_post.py +0 -0
  77. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_post.py +0 -0
  78. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_reply_post.py +0 -0
  79. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_search_user.py +0 -0
  80. {pyptt-2.0.5 → pyptt-2.0.7}/tests/test_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPtt
3
- Version: 2.0.5
3
+ Version: 2.0.7
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)
@@ -1,4 +1,6 @@
1
- __version__ = '2.0.5'
1
+ __version__ = '2.0.7'
2
+ __author__ = 'CodingMan'
3
+ __email__ = 'pttcodingman@gmail.com'
2
4
 
3
5
  from .PTT import API
4
6
  from .data_type import *
@@ -37,30 +37,21 @@ def get_user(api, ptt_id: str) -> Dict:
37
37
 
38
38
  cmd = ''.join(cmd_list)
39
39
 
40
- target_list = [
41
- connect_core.TargetUnit(screens.Target.InTalk, break_detect=True),
42
- ]
43
-
44
- api.connect_core.send(cmd, target_list)
45
-
46
- cmd_list = []
47
- cmd_list.append('Q')
48
- cmd_list.append(command.enter)
49
- cmd_list.append(ptt_id)
50
- cmd_list.append(command.enter)
51
-
52
- cmd = ''.join(cmd_list)
40
+ # Use InTalk as a non-break target that auto-responds with the
41
+ # query command, and max_match=1 to prevent re-matching on
42
+ # intermediate screens (race condition fix).
43
+ query_cmd = 'Q' + command.enter + ptt_id + command.enter
53
44
 
54
45
  target_list = [
55
46
  connect_core.TargetUnit(screens.Target.AnyKey, break_detect=True),
56
- connect_core.TargetUnit(screens.Target.InTalk, break_detect=True),
47
+ connect_core.TargetUnit(screens.Target.InTalk,
48
+ response=query_cmd,
49
+ max_match=1),
57
50
  ]
58
51
 
59
- index = api.connect_core.send(
60
- cmd,
61
- target_list)
52
+ index = api.connect_core.send(cmd, target_list)
62
53
  ori_screen = api.connect_core.get_screen_queue()[-1]
63
- if index == 1:
54
+ if index != 0:
64
55
  raise exceptions.NoSuchUser(ptt_id)
65
56
  # PTT1
66
57
  # 《ID暱稱》CodingMan (專業程式 BUG 製造機)《經濟狀況》小康 ($73866)
@@ -85,7 +76,13 @@ def get_user(api, ptt_id: str) -> Dict:
85
76
 
86
77
  # 《個人名片》CodingMan 目前沒有名片
87
78
 
88
- lines = ori_screen.split('\n')[1:]
79
+ lines = ori_screen.split('\n')
80
+
81
+ def find_line(marker):
82
+ for i, line in enumerate(lines):
83
+ if marker in line:
84
+ return i, line
85
+ return -1, ''
89
86
 
90
87
  def parse_user_info_from_line(line: str) -> (str, str):
91
88
  part_0 = line[line.find('》') + 1:]
@@ -95,9 +92,20 @@ def get_user(api, ptt_id: str) -> Dict:
95
92
 
96
93
  return part_0, part_1
97
94
 
98
- ptt_id, buff_1 = parse_user_info_from_line(lines[0])
95
+ id_idx, id_line = find_line('《ID暱稱》')
96
+ login_idx, login_line = find_line('《登入次數》')
97
+ activity_idx, activity_line = find_line('《目前動態》')
98
+ last_login_idx, last_login_line = find_line('《上次上站》')
99
+ chess_idx, chess_line = find_line('《 五子棋 》')
100
+
101
+ if any(idx == -1 for idx in [id_idx, login_idx, activity_idx, last_login_idx, chess_idx]):
102
+ log.logger.debug('get_user_parse_error', ori_screen)
103
+ raise exceptions.UnknownError(f'get_user parse error: {ori_screen}')
104
+
105
+ ptt_id, buff_1 = parse_user_info_from_line(id_line)
99
106
  money = int(int_list[0]) if len(int_list := re.findall(r'\d+', buff_1)) > 0 else buff_1
100
- buff_0, buff_1 = parse_user_info_from_line(lines[1])
107
+
108
+ buff_0, buff_1 = parse_user_info_from_line(login_line)
101
109
 
102
110
  login_count = int(re.findall(r'\d+', buff_0)[0])
103
111
  account_verified = ('同天內只計一次' in buff_0)
@@ -109,11 +117,11 @@ def get_user(api, ptt_id: str) -> Dict:
109
117
  else:
110
118
  illegal_post = None
111
119
 
112
- activity, mail = parse_user_info_from_line(lines[2])
113
- last_login_date, last_login_ip = parse_user_info_from_line(lines[3])
114
- five_chess, chess = parse_user_info_from_line(lines[4])
120
+ activity, mail = parse_user_info_from_line(activity_line)
121
+ last_login_date, last_login_ip = parse_user_info_from_line(last_login_line)
122
+ five_chess, chess = parse_user_info_from_line(chess_line)
115
123
 
116
- signature_file = '\n'.join(lines[5:-1]).strip('\n')
124
+ signature_file = '\n'.join(lines[chess_idx + 1:-1]).strip('\n')
117
125
 
118
126
  log.logger.debug('ptt_id', ptt_id)
119
127
  log.logger.debug('money', money)
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
+ from datetime import datetime
4
5
  from typing import List, Dict
5
6
 
6
7
  from . import _api_util
@@ -19,6 +20,11 @@ _to_target_pattern = re.compile(r'To (\S+):')
19
20
  _from_target_pattern = re.compile(r'★(\S+) ')
20
21
 
21
22
 
23
+ def _parse_date(date_str: str) -> datetime:
24
+ """Parse waterball date string into datetime object."""
25
+ return datetime.strptime(date_str, '%m/%d/%Y %H:%M:%S')
26
+
27
+
22
28
  def _merge_continuation_lines(lines: list) -> list:
23
29
  merged = []
24
30
  i = 0
@@ -52,7 +58,7 @@ def _parse_waterball_line(line: str) -> Dict | None:
52
58
  WaterballField.type: WaterballType.SEND,
53
59
  WaterballField.target: target,
54
60
  WaterballField.content: content,
55
- WaterballField.date: date,
61
+ WaterballField.date: _parse_date(date),
56
62
  }
57
63
  elif line.startswith('★'):
58
64
  target_match = _from_target_pattern.search(line)
@@ -66,7 +72,7 @@ def _parse_waterball_line(line: str) -> Dict | None:
66
72
  WaterballField.type: WaterballType.CATCH,
67
73
  WaterballField.target: target,
68
74
  WaterballField.content: content,
69
- WaterballField.date: date,
75
+ WaterballField.date: _parse_date(date),
70
76
  }
71
77
  return None
72
78
 
@@ -128,6 +134,8 @@ def get_waterball(api, post_action: WaterballPostAction = WaterballPostAction.KE
128
134
  target_list_exit = [
129
135
  connect_core.TargetUnit(screens.Target.WaterBallPostAction, log_level=log.DEBUG,
130
136
  response=action_cmd + command.enter),
137
+ connect_core.TargetUnit(screens.Target.WaterballCleanup, log_level=log.DEBUG,
138
+ response='y' + command.enter),
131
139
  connect_core.TargetUnit(screens.Target.MainMenu, log_level=log.DEBUG, break_detect=True),
132
140
  ]
133
141
  api.connect_core.send('q', target_list_exit)
@@ -104,6 +104,8 @@ class Target:
104
104
 
105
105
  AnyKey = '任意鍵'
106
106
 
107
+ WaterballCleanup = '確定清除?'
108
+
107
109
  InTalk = [
108
110
  '【聊天說話】',
109
111
  '線上使用者列表',
@@ -0,0 +1,24 @@
1
+ key = """-----BEGIN EC PARAMETERS-----
2
+ BggqhkjOPQMBBw==
3
+ -----END EC PARAMETERS-----
4
+ -----BEGIN EC PRIVATE KEY-----
5
+ MHcCAQEEIFvkfPbkl4Ke99iXWTXG1LJnhFeMFREJWW9d4I76PZmGoAoGCCqGSM49
6
+ AwEHoUQDQgAErWNfoKcmEmi+0tNm2CCne7MhF0+4iKZYJvtrn5kxRG73XWhoR79c
7
+ 7tfex7gLuFn/VXiP+rgDOe4MLT1LGrtD4A==
8
+ -----END EC PRIVATE KEY-----
9
+ """
10
+
11
+ cert = """-----BEGIN CERTIFICATE-----
12
+ MIIB6TCCAY8CFFzZNObjJZqIs69GFDYbSXJqNpBIMAoGCCqGSM49BAMCMHcxCzAJ
13
+ BgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UE
14
+ CgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRQw
15
+ EgYDVQQDDAtDb21tb24gTmFtZTAeFw0yNjA0MDMxNDM1MzRaFw0zNjAzMzExNDM1
16
+ MzRaMHcxCzAJBgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0
17
+ eTEVMBMGA1UECgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25h
18
+ bCBVbml0MRQwEgYDVQQDDAtDb21tb24gTmFtZTBZMBMGByqGSM49AgEGCCqGSM49
19
+ AwEHA0IABK1jX6CnJhJovtLTZtggp3uzIRdPuIimWCb7a5+ZMURu911oaEe/XO7X
20
+ 3se4C7hZ/1V4j/q4AznuDC09Sxq7Q+AwCgYIKoZIzj0EAwIDSAAwRQIhAOAM8IPM
21
+ 5D4BoaWYf3SaSVzrm8v6FtSIhWrSYJatYjwoAiAW3NggrC+qYY3wYRDq8UmaiHLv
22
+ pkJCEsbjl+remcwPbg==
23
+ -----END CERTIFICATE-----
24
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPtt
3
- Version: 2.0.5
3
+ Version: 2.0.7
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)
@@ -1,3 +1,6 @@
1
+ import time
2
+
3
+
1
4
  def _check_user_info(user_info, test_user):
2
5
  assert user_info is not None, f'get_user returned None for user {test_user}'
3
6
  assert 'ptt_id' in user_info
@@ -16,20 +19,10 @@ def test_get_user(ptt_bots):
16
19
  'CodingMan',
17
20
  ]
18
21
 
19
- for ptt_bot in ptt_bots:
22
+ for _ in range(100):
20
23
  for test_user in test_users:
21
- user_info = ptt_bot.get_user(test_user)
24
+ user_info = ptt_bots[0].get_user(test_user)
22
25
  _check_user_info(user_info, test_user)
23
26
 
27
+ time.sleep(1)
24
28
 
25
- def test_get_user_ptt1_only(ptt_bots):
26
- """Tests getting user information that only exists on PTT1."""
27
- ptt1_bot = ptt_bots[0]
28
-
29
- test_users = [
30
- 'janice001',
31
- ]
32
-
33
- for test_user in test_users:
34
- user_info = ptt1_bot.get_user(test_user)
35
- _check_user_info(user_info, test_user)
@@ -1,24 +0,0 @@
1
- key = """-----BEGIN EC PARAMETERS-----
2
- BggqhkjOPQMBBw==
3
- -----END EC PARAMETERS-----
4
- -----BEGIN EC PRIVATE KEY-----
5
- MHcCAQEEIKoC26Q5VfzkSPN5TShnQWBjUczlLrchp6TUvZz3yUvGoAoGCCqGSM49
6
- AwEHoUQDQgAE9FSTa74Y+hPSchUFAl0c0oX631NUsDjDZ2BR5/5uZ0CHcPNPiObg
7
- D3uXFU0vPdGIqDWIDHDf3VmshiDUD9IUgQ==
8
- -----END EC PRIVATE KEY-----
9
- """
10
-
11
- cert = """-----BEGIN CERTIFICATE-----
12
- MIIB6DCCAY8CFGo6SqurrckTpxffizxr9VUuLqI9MAoGCCqGSM49BAMCMHcxCzAJ
13
- BgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UE
14
- CgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRQw
15
- EgYDVQQDDAtDb21tb24gTmFtZTAeFw0yNjA0MDIxNTExNTdaFw0zNjAzMzAxNTEx
16
- NTdaMHcxCzAJBgNVBAYTAlRXMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0
17
- eTEVMBMGA1UECgwMT3JnYW5pemF0aW9uMRwwGgYDVQQLDBNPcmdhbml6YXRpb25h
18
- bCBVbml0MRQwEgYDVQQDDAtDb21tb24gTmFtZTBZMBMGByqGSM49AgEGCCqGSM49
19
- AwEHA0IABPRUk2u+GPoT0nIVBQJdHNKF+t9TVLA4w2dgUef+bmdAh3DzT4jm4A97
20
- lxVNLz3RiKg1iAxw391ZrIYg1A/SFIEwCgYIKoZIzj0EAwIDRwAwRAIgSrQ9dTQ9
21
- qajw32Ws/Ua9s3WvPaPa9QGwmv2Mm1Wk02sCICh8iO5eApwSnSD+PdoTVgrINx72
22
- aDsfqjLNJ4BPjSDw
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
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