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.
Files changed (52) hide show
  1. {pyptt-1.2.16 → pyptt-1.2.18}/PKG-INFO +24 -11
  2. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/PTT.py +6 -4
  3. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/__init__.py +1 -1
  4. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_comment.py +6 -6
  5. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_del_post.py +5 -5
  6. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_board_info.py +7 -12
  7. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_newest_index.py +2 -1
  8. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_post.py +4 -4
  9. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_post_index.py +0 -1
  10. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_user.py +1 -1
  11. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_loginout.py +1 -1
  12. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_mail.py +2 -2
  13. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_mark_post.py +7 -7
  14. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_post.py +1 -1
  15. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_reply_post.py +3 -3
  16. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_util.py +11 -6
  17. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/check_value.py +9 -8
  18. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/connect_core.py +15 -9
  19. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/exceptions.py +8 -0
  20. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/i18n.py +4 -5
  21. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/lib_util.py +5 -5
  22. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/screens.py +5 -0
  23. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/service.py +3 -2
  24. pyptt-1.2.18/PyPtt/ssl_config.py +24 -0
  25. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt.egg-info/PKG-INFO +24 -11
  26. {pyptt-1.2.16 → pyptt-1.2.18}/README.md +21 -9
  27. pyptt-1.2.16/PyPtt/ssl_config.py +0 -24
  28. {pyptt-1.2.16 → pyptt-1.2.18}/LICENSE +0 -0
  29. {pyptt-1.2.16 → pyptt-1.2.18}/MANIFEST.in +0 -0
  30. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_bucket.py +0 -0
  31. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_call_status.py +0 -0
  32. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_change_pw.py +0 -0
  33. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_board_list.py +0 -0
  34. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_bottom_post_list.py +0 -0
  35. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_favourite_board.py +0 -0
  36. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_get_time.py +0 -0
  37. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_give_money.py +0 -0
  38. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_has_new_mail.py +0 -0
  39. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_search_user.py +0 -0
  40. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/_api_set_board_title.py +0 -0
  41. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/command.py +0 -0
  42. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/config.py +0 -0
  43. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/data_type.py +0 -0
  44. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/lang_en_US.py +0 -0
  45. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/lang_zh_TW.py +0 -0
  46. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt/log.py +0 -0
  47. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt.egg-info/SOURCES.txt +0 -0
  48. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt.egg-info/dependency_links.txt +0 -0
  49. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt.egg-info/requires.txt +0 -0
  50. {pyptt-1.2.16 → pyptt-1.2.18}/PyPtt.egg-info/top_level.txt +0 -0
  51. {pyptt-1.2.16 → pyptt-1.2.18}/setup.cfg +0 -0
  52. {pyptt-1.2.16 → pyptt-1.2.18}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: PyPtt
3
- Version: 1.2.16
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
  ![](https://raw.githubusercontent.com/PttCodingMan/PyPtt/master/logo/facebook_cover_photo_2.png)
45
- # PyPtt
46
46
  [![Package Version](https://img.shields.io/pypi/v/PyPtt.svg)](https://pypi.python.org/pypi/PyPtt)
47
47
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/PyPtt)
48
48
  [![test](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml/badge.svg)](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml)
@@ -51,18 +51,27 @@ Dynamic: summary
51
51
  [![chatroom icon](https://patrolavia.github.io/telegram-badge/chat.png)](https://t.me/PyPtt)
52
52
  [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/CodingMan)
53
53
 
54
- #### PyPtt (PTT Library) 是一套 Pure Python PTT API 是目前支援最完整的 PTT API。具備大部分常用功能,無論推文、發文、取得文章、取得信件、寄信、發 P 幣、丟水球,你都可以在這裡找到完整的使用範例
55
- #### 使用帳號登入,支援使用登入之後才可以使用的功能,例如:推文、發文、寄信、發 P 幣等等
56
- #### 本專案意旨在提供 PTT 自動化機器人函式庫,無意違反任何 PTT 站方規範。如有牴觸,請馬上告知。
57
- #### 由於 PTT 封鎖雲端連線,建議在雲端以外的環境使用。
58
- ####
59
- #### Pypi: https://pypi.org/project/PyPtt/
60
- <img src="https://raw.githubusercontent.com/PyPtt/PyPtt/master/docs/_static/login_1.0.gif" width="560">
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
  #### [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/CodingMan)
77
86
 
87
+ ## Star History
88
+
89
+ [![Star History Chart](https://codingman.cc/images/star_history.svg)](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 ValueError('[PyPtt] TELNET is not available on PTT1 and PTT2')
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.config)
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
 
@@ -1,4 +1,4 @@
1
- __version__ = '1.2.16'
1
+ __version__ = '1.2.18'
2
2
 
3
3
  from .PTT import API
4
4
  from .data_type import *
@@ -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 ValueError('post_aid and post_index cannot be None at the same time')
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 ValueError(f'wrong parameter board: {board}')
141
+ raise exceptions.ParameterError(f'wrong parameter board: {board}')
142
142
 
143
143
  if post_index != 0 and isinstance(post_aid, str):
144
- raise ValueError('wrong parameter index and aid can\'t both input')
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 ValueError('wrong parameter index or aid must input')
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 ValueError(f'board error parameter: {board}')
32
+ raise exceptions.ParameterError(f'board error parameter: {board}')
33
33
 
34
34
  if post_index != 0 and isinstance(post_aid, str):
35
- raise ValueError('wrong parameter index and aid can\'t both input')
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 ValueError('wrong parameter index or aid must input')
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 ValueError('post_aid and post_index cannot be None at the same time')
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
- nuser = None
33
+ online_user_line = None
34
34
  for line in ori_screen.split('\n'):
35
- if '編號' not in line:
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
- nuser = line
40
+ online_user_line = line
43
41
  break
44
42
 
45
- if nuser is None:
43
+ if online_user_line is None:
46
44
  raise exceptions.NoSuchBoard(api.config, board)
47
45
 
48
- # print('------------------------')
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 '編號' not in nuser or '人氣' not in nuser:
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(nuser)
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 ValueError('board should not input at NewIndex.MAIL.')
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 ValueError(f'board error parameter: {board}')
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 ValueError('wrong parameter index and aid can\'t both input')
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 ValueError('wrong parameter index or aid must input')
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 ValueError('post_aid and post_index cannot be None at the same time')
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)
@@ -2,7 +2,6 @@ from . import _api_util
2
2
  from . import command
3
3
  from . import connect_core
4
4
  from . import exceptions
5
- from . import i18n
6
5
  from . import log
7
6
  from . import screens
8
7
 
@@ -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 ValueError(f'wrong parameter user_id: {ptt_id}')
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 ValueError(f'wrong parameter sign_file: {sign_file}')
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 ValueError('index must be int')
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 ValueError(f'board error parameter: {board}')
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 ValueError('wrong parameter index and aid can\'t both input')
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 ValueError('wrong parameter index or aid must input')
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 ValueError('wrong parameter index or aid must input')
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 ValueError(f'wrong parameter search_condition: {search_condition}')
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 ValueError('wrong parameter aid and search_condition can\'t both input')
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 ValueError('post_aid and post_index cannot be None at the same time')
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 ValueError(f'wrong parameter sign_file: {sign_file}')
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 ValueError(f'wrong parameter sign_file: {sign_file}')
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 ValueError('wrong parameter aid and index can\'t both input')
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 ValueError('post_aid and post_index cannot be None at the same time')
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.space)
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.append(board.lower())
393
- api.connect_core.send(cmd, target_list, refresh=current_refresh)
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.append(board.lower())
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 ValueError(f'{name} {value} {i18n.must_between} {min_value} ~ {max_value}')
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 ValueError(f'{name} {i18n.must_bigger_than} 0')
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 ValueError(f'{name} {index} {i18n.must_between} 0 ~ {max_value}')
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 ValueError(f'{start_name} {start_index} {i18n.must_bigger_than} 0')
45
+ raise exceptions.ParameterError(f'{start_name} {start_index} {i18n.must_bigger_than} 0')
45
46
 
46
47
  if end_index <= 1:
47
- raise ValueError(f'{end_name} {end_index} {i18n.must_bigger_than} 1')
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 ValueError(f'{end_name} {end_index} {i18n.must_bigger_than} {start_name} {start_index}')
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 ValueError(f'{start_name} {start_index} {i18n.must_small_than} {max_value}')
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 ValueError(f'{end_name} {end_index} {i18n.must_small_than} {max_value}')
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 Target in self.detect_target:
93
- if Target not in screen:
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, config):
160
+ def __init__(self, api):
156
161
 
157
162
  self.current_encoding = 'big5uao'
158
- self.config = config
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', websockets.http11.USER_AGENT if use_http11 else websockets.http.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
- condition = target.is_match(screen)
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 ValueError('Item of TargetList must be TargetUnit')
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)
@@ -275,3 +275,11 @@ class ResetYourContactEmail(Error):
275
275
 
276
276
  def __str__(self):
277
277
  return self.message
278
+
279
+
280
+ class ParameterError(Error):
281
+ def __init__(self, message):
282
+ self.message = message
283
+
284
+ def __str__(self):
285
+ return self.message
@@ -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 ValueError(f'Unknown locale: {locale}')
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 ValueError(f'Unknown locale: {locale}')
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 ValueError(f'Unknown string data type: {v}')
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 ValueError('wrong parameter url must be www.ptt.cc post url')
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 ValueError('aid is None')
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 ValueError('aid is not valid')
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 ValueError('aid is not valid')
165
+ raise exceptions.ParameterError('aid is not valid')
166
166
 
167
167
  return f'#{aid}'
168
168
 
@@ -161,6 +161,11 @@ class Target:
161
161
  '--\n※ 發信站: 新批踢踢(ptt2.twbbs.org.tw)'
162
162
  ]
163
163
 
164
+ OnlineUser = [
165
+ '編號',
166
+ '日 期'
167
+ ]
168
+
164
169
 
165
170
  def show(config, screen_queue, function_name=None):
166
171
  if config.log_level != log.DEBUG:
@@ -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 ValueError(f'api {api} not found')
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.2
1
+ Metadata-Version: 2.4
2
2
  Name: PyPtt
3
- Version: 1.2.16
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
  ![](https://raw.githubusercontent.com/PttCodingMan/PyPtt/master/logo/facebook_cover_photo_2.png)
45
- # PyPtt
46
46
  [![Package Version](https://img.shields.io/pypi/v/PyPtt.svg)](https://pypi.python.org/pypi/PyPtt)
47
47
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/PyPtt)
48
48
  [![test](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml/badge.svg)](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml)
@@ -51,18 +51,27 @@ Dynamic: summary
51
51
  [![chatroom icon](https://patrolavia.github.io/telegram-badge/chat.png)](https://t.me/PyPtt)
52
52
  [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/CodingMan)
53
53
 
54
- #### PyPtt (PTT Library) 是一套 Pure Python PTT API 是目前支援最完整的 PTT API。具備大部分常用功能,無論推文、發文、取得文章、取得信件、寄信、發 P 幣、丟水球,你都可以在這裡找到完整的使用範例
55
- #### 使用帳號登入,支援使用登入之後才可以使用的功能,例如:推文、發文、寄信、發 P 幣等等
56
- #### 本專案意旨在提供 PTT 自動化機器人函式庫,無意違反任何 PTT 站方規範。如有牴觸,請馬上告知。
57
- #### 由於 PTT 封鎖雲端連線,建議在雲端以外的環境使用。
58
- ####
59
- #### Pypi: https://pypi.org/project/PyPtt/
60
- <img src="https://raw.githubusercontent.com/PyPtt/PyPtt/master/docs/_static/login_1.0.gif" width="560">
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
  #### [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/CodingMan)
77
86
 
87
+ ## Star History
88
+
89
+ [![Star History Chart](https://codingman.cc/images/star_history.svg)](https://www.star-history.com/#PyPtt/PyPtt&Date)
90
+
78
91
  ## 贊助清單
79
92
 
80
93
  #### leftc
@@ -1,5 +1,4 @@
1
1
  ![](https://raw.githubusercontent.com/PttCodingMan/PyPtt/master/logo/facebook_cover_photo_2.png)
2
- # PyPtt
3
2
  [![Package Version](https://img.shields.io/pypi/v/PyPtt.svg)](https://pypi.python.org/pypi/PyPtt)
4
3
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/PyPtt)
5
4
  [![test](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml/badge.svg)](https://github.com/PyPtt/PyPtt/actions/workflows/test.yml)
@@ -8,18 +7,27 @@
8
7
  [![chatroom icon](https://patrolavia.github.io/telegram-badge/chat.png)](https://t.me/PyPtt)
9
8
  [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/CodingMan)
10
9
 
11
- #### PyPtt (PTT Library) 是一套 Pure Python PTT API 是目前支援最完整的 PTT API。具備大部分常用功能,無論推文、發文、取得文章、取得信件、寄信、發 P 幣、丟水球,你都可以在這裡找到完整的使用範例
12
- #### 使用帳號登入,支援使用登入之後才可以使用的功能,例如:推文、發文、寄信、發 P 幣等等
13
- #### 本專案意旨在提供 PTT 自動化機器人函式庫,無意違反任何 PTT 站方規範。如有牴觸,請馬上告知。
14
- #### 由於 PTT 封鎖雲端連線,建議在雲端以外的環境使用。
15
- ####
16
- #### Pypi: https://pypi.org/project/PyPtt/
17
- <img src="https://raw.githubusercontent.com/PyPtt/PyPtt/master/docs/_static/login_1.0.gif" width="560">
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
  #### [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/CodingMan)
34
42
 
43
+ ## Star History
44
+
45
+ [![Star History Chart](https://codingman.cc/images/star_history.svg)](https://www.star-history.com/#PyPtt/PyPtt&Date)
46
+
35
47
  ## 贊助清單
36
48
 
37
49
  #### leftc
@@ -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