PyPtt 2.3.15__tar.gz → 2.3.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. {pyptt-2.3.15 → pyptt-2.3.16}/PKG-INFO +1 -1
  2. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/__init__.py +1 -1
  3. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/connect_core.py +16 -16
  4. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt.egg-info/PKG-INFO +1 -1
  5. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt.egg-info/SOURCES.txt +2 -1
  6. pyptt-2.3.16/tests/test_websocket_endpoint.py +39 -0
  7. {pyptt-2.3.15 → pyptt-2.3.16}/LICENSE +0 -0
  8. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/PTT.py +0 -0
  9. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_bucket.py +0 -0
  10. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_call_status.py +0 -0
  11. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_change_pw.py +0 -0
  12. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_comment.py +0 -0
  13. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_del_post.py +0 -0
  14. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_board_info.py +0 -0
  15. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_board_list.py +0 -0
  16. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_bottom_post_list.py +0 -0
  17. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_favourite_board.py +0 -0
  18. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_newest_index.py +0 -0
  19. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_post.py +0 -0
  20. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_post_index.py +0 -0
  21. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_post_list.py +0 -0
  22. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_time.py +0 -0
  23. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_user.py +0 -0
  24. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_get_waterball.py +0 -0
  25. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_give_money.py +0 -0
  26. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_has_new_mail.py +0 -0
  27. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_loginout.py +0 -0
  28. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_mail.py +0 -0
  29. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_mark_post.py +0 -0
  30. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_post.py +0 -0
  31. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_reply_post.py +0 -0
  32. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_search_user.py +0 -0
  33. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_set_board_title.py +0 -0
  34. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_set_signature_file.py +0 -0
  35. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_api_util.py +0 -0
  36. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_uao/LICENSE +0 -0
  37. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_uao/__init__.py +0 -0
  38. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_uao/b2u.py +0 -0
  39. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/_uao/u2b.py +0 -0
  40. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/api_server.py +0 -0
  41. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/check_value.py +0 -0
  42. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/command.py +0 -0
  43. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/config.py +0 -0
  44. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/data_type.py +0 -0
  45. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/exceptions.py +0 -0
  46. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/i18n.py +0 -0
  47. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/lang_en_US.py +0 -0
  48. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/lang_zh_TW.py +0 -0
  49. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/lib_util.py +0 -0
  50. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/log.py +0 -0
  51. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/screens.py +0 -0
  52. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/service.py +0 -0
  53. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt/ssl_config.py +0 -0
  54. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt.egg-info/dependency_links.txt +0 -0
  55. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt.egg-info/requires.txt +0 -0
  56. {pyptt-2.3.15 → pyptt-2.3.16}/PyPtt.egg-info/top_level.txt +0 -0
  57. {pyptt-2.3.15 → pyptt-2.3.16}/README.md +0 -0
  58. {pyptt-2.3.15 → pyptt-2.3.16}/pyproject.toml +0 -0
  59. {pyptt-2.3.15 → pyptt-2.3.16}/setup.cfg +0 -0
  60. {pyptt-2.3.15 → pyptt-2.3.16}/setup.py +0 -0
  61. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_backup_effective.py +0 -0
  62. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_backup_param.py +0 -0
  63. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_board_setting_flow.py +0 -0
  64. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_bucket.py +0 -0
  65. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_change_pw.py +0 -0
  66. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_comment.py +0 -0
  67. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_connect_core_timeout.py +0 -0
  68. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_del_post.py +0 -0
  69. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_del_post_unit.py +0 -0
  70. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_exceptions.py +0 -0
  71. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_fast_post.py +0 -0
  72. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_board_info.py +0 -0
  73. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_board_info_parser.py +0 -0
  74. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_board_list.py +0 -0
  75. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_bottom_post_list.py +0 -0
  76. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_content_formula.py +0 -0
  77. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_favourite_boards.py +0 -0
  78. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_mail.py +0 -0
  79. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_newest_index.py +0 -0
  80. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_post.py +0 -0
  81. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_post_list.py +0 -0
  82. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_post_list_parser.py +0 -0
  83. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_post_parser.py +0 -0
  84. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_time.py +0 -0
  85. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_user.py +0 -0
  86. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_get_waterball.py +0 -0
  87. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_give_money.py +0 -0
  88. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_half_dead_connection.py +0 -0
  89. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_i18n.py +0 -0
  90. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_incremental_parity.py +0 -0
  91. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_init.py +0 -0
  92. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_logger.py +0 -0
  93. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_login_logout.py +0 -0
  94. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_mail_send_and_del.py +0 -0
  95. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_main_menu_status_bar.py +0 -0
  96. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_mark_post.py +0 -0
  97. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_new_screen_fixes.py +0 -0
  98. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_parse_query_post.py +0 -0
  99. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_post.py +0 -0
  100. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_post_anonymous.py +0 -0
  101. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_pyte_parity.py +0 -0
  102. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_reply_post.py +0 -0
  103. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_search_user.py +0 -0
  104. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_service.py +0 -0
  105. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_set_board_title.py +0 -0
  106. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_set_signature_file.py +0 -0
  107. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_status_line.py +0 -0
  108. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_uao_codec.py +0 -0
  109. {pyptt-2.3.15 → pyptt-2.3.16}/tests/test_vt100_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPtt
3
- Version: 2.3.15
3
+ Version: 2.3.16
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,4 @@
1
- __version__ = '2.3.15'
1
+ __version__ = '2.3.16'
2
2
  __author__ = 'CodingMan'
3
3
  __email__ = 'pttcodingman@gmail.com'
4
4
 
@@ -179,6 +179,21 @@ class API(object):
179
179
  asyncio.set_event_loop(self._loop)
180
180
  return self._loop
181
181
 
182
+ def _websocket_endpoint(self) -> tuple[str, str]:
183
+ # PTT1 started answering wss://ws.ptt.cc/bbs/ (trailing slash) with HTTP 404 on
184
+ # 2026-11-06; term.ptt.cc connects to /bbs, so use the slash-less path everywhere
185
+ if self.config.host == data_type.HOST.PTT1:
186
+ return 'wss://ws.ptt.cc/bbs', 'https://term.ptt.cc'
187
+ if self.config.host == data_type.HOST.PTT2:
188
+ return 'wss://ws.ptt2.cc/bbs', 'https://term.ptt2.cc'
189
+ if self.config.host == data_type.HOST.LOCALHOST:
190
+ # ponytail: local pttbbs docker image (bbsdocker/imageptt) serves plain ws
191
+ # (no TLS) on 48763; config.port defaults to 23 (telnet default) so treat
192
+ # that as "not set" and fall back to imageptt's default port
193
+ port = self.config.port if self.config.port != 23 else 48763
194
+ return f'ws://localhost:{port}/bbs', 'http://localhost'
195
+ return f'wss://{self.config.host}', 'https://term.ptt.cc'
196
+
182
197
  def connect(self) -> None:
183
198
  def _wait():
184
199
  for i in range(self.config.retry_wait_time):
@@ -198,22 +213,7 @@ class API(object):
198
213
 
199
214
  self.current_encoding = 'big5uao'
200
215
 
201
- if self.config.host == data_type.HOST.PTT1:
202
- websocket_host = 'wss://ws.ptt.cc/bbs/'
203
- websocket_origin = 'https://term.ptt.cc'
204
- elif self.config.host == data_type.HOST.PTT2:
205
- websocket_host = 'wss://ws.ptt2.cc/bbs/'
206
- websocket_origin = 'https://term.ptt2.cc'
207
- elif self.config.host == data_type.HOST.LOCALHOST:
208
- # ponytail: local pttbbs docker image (bbsdocker/imageptt) serves plain ws
209
- # (no TLS) on 48763; config.port defaults to 23 (telnet default) so treat
210
- # that as "not set" and fall back to imageptt's default port
211
- port = self.config.port if self.config.port != 23 else 48763
212
- websocket_host = f'ws://localhost:{port}/bbs'
213
- websocket_origin = 'http://localhost'
214
- else:
215
- websocket_host = f'wss://{self.config.host}'
216
- websocket_origin = 'https://term.ptt.cc'
216
+ websocket_host, websocket_origin = self._websocket_endpoint()
217
217
 
218
218
  self._pending = []
219
219
  connect_success = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPtt
3
- Version: 2.3.15
3
+ Version: 2.3.16
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)
@@ -103,4 +103,5 @@ tests/test_set_board_title.py
103
103
  tests/test_set_signature_file.py
104
104
  tests/test_status_line.py
105
105
  tests/test_uao_codec.py
106
- tests/test_vt100_parser.py
106
+ tests/test_vt100_parser.py
107
+ tests/test_websocket_endpoint.py
@@ -0,0 +1,39 @@
1
+ """No-network regression test for the WebSocket endpoint connect_core dials.
2
+
3
+ On 2026-11-06 PTT1's gateway started answering wss://ws.ptt.cc/bbs/ (with a
4
+ trailing slash) with HTTP 404, so login died with ConnectError before reaching
5
+ any screen. term.ptt.cc connects to wss://ws.ptt.cc/bbs, which still works.
6
+ """
7
+ import pytest
8
+
9
+ from PyPtt import config, connect_core, data_type
10
+
11
+
12
+ class FakeApi:
13
+ def __init__(self, host, port=23):
14
+ self.config = config.Config()
15
+ self.config.host = host
16
+ self.config.port = port
17
+
18
+
19
+ def _endpoint(host, port=23):
20
+ return connect_core.API(FakeApi(host, port))._websocket_endpoint()
21
+
22
+
23
+ @pytest.mark.parametrize('host, url, origin', [
24
+ (data_type.HOST.PTT1, 'wss://ws.ptt.cc/bbs', 'https://term.ptt.cc'),
25
+ (data_type.HOST.PTT2, 'wss://ws.ptt2.cc/bbs', 'https://term.ptt2.cc'),
26
+ (data_type.HOST.LOCALHOST, 'ws://localhost:48763/bbs', 'http://localhost'),
27
+ ])
28
+ def test_known_hosts(host, url, origin):
29
+ assert _endpoint(host) == (url, origin)
30
+
31
+
32
+ @pytest.mark.parametrize('host', [data_type.HOST.PTT1, data_type.HOST.PTT2, data_type.HOST.LOCALHOST])
33
+ def test_no_trailing_slash(host):
34
+ url, _ = _endpoint(host)
35
+ assert not url.endswith('/')
36
+
37
+
38
+ def test_localhost_custom_port():
39
+ assert _endpoint(data_type.HOST.LOCALHOST, port=12345)[0] == 'ws://localhost:12345/bbs'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes