PyPtt 2.3.5__tar.gz → 2.3.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.
- {pyptt-2.3.5 → pyptt-2.3.7}/PKG-INFO +1 -1
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/PTT.py +3 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/__init__.py +1 -1
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_call_status.py +24 -23
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_time.py +2 -1
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_loginout.py +2 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/exceptions.py +10 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/lang_en_US.py +8 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/lang_zh_TW.py +6 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/screens.py +6 -2
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt.egg-info/PKG-INFO +1 -1
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt.egg-info/SOURCES.txt +1 -0
- pyptt-2.3.7/tests/test_status_line.py +41 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/LICENSE +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_bucket.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_change_pw.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_comment.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_del_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_board_info.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_board_list.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_bottom_post_list.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_favourite_board.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_newest_index.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_post_index.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_post_list.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_user.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_get_waterball.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_give_money.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_has_new_mail.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_mail.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_mark_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_reply_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_search_user.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_set_board_title.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_set_signature_file.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_api_util.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_uao/LICENSE +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_uao/__init__.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_uao/b2u.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/_uao/u2b.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/api_server.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/check_value.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/command.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/config.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/connect_core.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/data_type.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/i18n.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/lib_util.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/log.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/service.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt/ssl_config.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt.egg-info/dependency_links.txt +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt.egg-info/requires.txt +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/PyPtt.egg-info/top_level.txt +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/README.md +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/pyproject.toml +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/setup.cfg +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/setup.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_backup_effective.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_backup_param.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_bucket.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_change_pw.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_comment.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_connect_core_timeout.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_del_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_del_post_unit.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_exceptions.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_fast_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_board_info.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_board_list.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_bottom_post_list.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_content_formula.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_favourite_boards.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_mail.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_newest_index.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_post_list.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_post_list_parser.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_post_parser.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_time.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_user.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_get_waterball.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_give_money.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_half_dead_connection.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_i18n.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_incremental_parity.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_init.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_logger.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_login_logout.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_mail_send_and_del.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_mark_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_parse_query_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_post_anonymous.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_pyte_parity.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_reply_post.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_search_user.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_service.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_set_board_title.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_set_signature_file.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_uao_codec.py +0 -0
- {pyptt-2.3.5 → pyptt-2.3.7}/tests/test_vt100_parser.py +0 -0
|
@@ -230,6 +230,7 @@ class API:
|
|
|
230
230
|
|
|
231
231
|
Raises:
|
|
232
232
|
LoginError: 登入失敗。
|
|
233
|
+
TwoFactorAuthRequired: 偵測到兩階段驗證畫面,PyPtt 無法自動完成驗證。
|
|
233
234
|
WrongIDorPassword: 帳號或密碼錯誤。
|
|
234
235
|
OnlySecureConnection: 只能使用安全連線。
|
|
235
236
|
ResetYourContactEmail: 請先至信箱設定連絡信箱。
|
|
@@ -242,6 +243,8 @@ class API:
|
|
|
242
243
|
try:
|
|
243
244
|
ptt_bot.login(
|
|
244
245
|
ptt_id='ptt_id', ptt_pw='ptt_pw', kick_other_session=True)
|
|
246
|
+
except PyPtt.TwoFactorAuthRequired:
|
|
247
|
+
print('偵測到兩階段驗證,請先手動登入完成驗證')
|
|
245
248
|
except PyPtt.LoginError:
|
|
246
249
|
print('登入失敗')
|
|
247
250
|
except PyPtt.WrongIDorPassword:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
1
3
|
from . import command
|
|
2
4
|
from . import connect_core
|
|
3
5
|
from . import data_type
|
|
@@ -6,6 +8,18 @@ from . import log
|
|
|
6
8
|
from . import screens
|
|
7
9
|
|
|
8
10
|
|
|
11
|
+
# 狀態列有 '[呼叫器]打開' 與 '呼叫器關閉' 兩種寫法, 中括號不保證存在。
|
|
12
|
+
_call_status_pattern = re.compile(r'呼叫器[\]\s]*(打開|拔掉|防水|好友|關閉)')
|
|
13
|
+
|
|
14
|
+
_call_status_map = {
|
|
15
|
+
'打開': data_type.CallStatus.ON,
|
|
16
|
+
'拔掉': data_type.CallStatus.UNPLUG,
|
|
17
|
+
'防水': data_type.CallStatus.WATERPROOF,
|
|
18
|
+
'好友': data_type.CallStatus.FRIEND,
|
|
19
|
+
'關閉': data_type.CallStatus.OFF,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
9
23
|
def get_call_status(api) -> data_type.CallStatus:
|
|
10
24
|
|
|
11
25
|
cmd_list = []
|
|
@@ -17,34 +31,21 @@ def get_call_status(api) -> data_type.CallStatus:
|
|
|
17
31
|
cmd = ''.join(cmd_list)
|
|
18
32
|
|
|
19
33
|
target_list = [
|
|
20
|
-
connect_core.TargetUnit(
|
|
21
|
-
connect_core.TargetUnit('[呼叫器]拔掉', log_level=log.DEBUG, break_detect=True),
|
|
22
|
-
connect_core.TargetUnit('[呼叫器]防水', log_level=log.DEBUG, break_detect=True),
|
|
23
|
-
connect_core.TargetUnit('[呼叫器]好友', log_level=log.DEBUG, break_detect=True),
|
|
24
|
-
connect_core.TargetUnit('[呼叫器]關閉', log_level=log.DEBUG, break_detect=True),
|
|
34
|
+
connect_core.TargetUnit(screens.Target.MainMenu, log_level=log.DEBUG, break_detect=True),
|
|
25
35
|
connect_core.TargetUnit('★', log_level=log.DEBUG, response=cmd),
|
|
26
36
|
]
|
|
27
37
|
|
|
28
|
-
for
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
raise exceptions.UnknownError('UnknownError')
|
|
34
|
-
|
|
35
|
-
if index == 0:
|
|
36
|
-
return data_type.CallStatus.ON
|
|
37
|
-
if index == 1:
|
|
38
|
-
return data_type.CallStatus.UNPLUG
|
|
39
|
-
if index == 2:
|
|
40
|
-
return data_type.CallStatus.WATERPROOF
|
|
41
|
-
if index == 3:
|
|
42
|
-
return data_type.CallStatus.FRIEND
|
|
43
|
-
if index == 4:
|
|
44
|
-
return data_type.CallStatus.OFF
|
|
38
|
+
for _ in range(2):
|
|
39
|
+
if api.connect_core.send(cmd, target_list) == 0:
|
|
40
|
+
break
|
|
41
|
+
else:
|
|
42
|
+
raise exceptions.UnknownError('UnknownError')
|
|
45
43
|
|
|
46
44
|
ori_screen = api.connect_core.get_screen_queue()[-1]
|
|
47
|
-
|
|
45
|
+
result = _call_status_pattern.search(ori_screen)
|
|
46
|
+
if result is None:
|
|
47
|
+
raise exceptions.UnknownError(ori_screen)
|
|
48
|
+
return _call_status_map[result.group(1)]
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
def set_call_status(api, call_status) -> None:
|
|
@@ -38,7 +38,8 @@ def get_time(api) -> str:
|
|
|
38
38
|
# 0:00
|
|
39
39
|
|
|
40
40
|
for line in line_list:
|
|
41
|
-
|
|
41
|
+
# 短格式的狀態列寫 '週三' 不是 '星期六', 所以不能比對 '星期'。
|
|
42
|
+
if '線上' in line and '我是' in line:
|
|
42
43
|
result = pattern.search(line)
|
|
43
44
|
if result is not None:
|
|
44
45
|
return result.group(0)
|
|
@@ -97,6 +97,8 @@ def login(api, ptt_id: str, ptt_pw: str, kick_other_session: bool) -> None:
|
|
|
97
97
|
connect_core.TargetUnit('密碼正確'),
|
|
98
98
|
connect_core.TargetUnit('您想刪除其他重複登入的連線嗎', response=kick_other_login_response),
|
|
99
99
|
connect_core.TargetUnit('◆ 您的註冊申請單尚在處理中', response=command.enter, handler=register_processing),
|
|
100
|
+
# 需排在「任意鍵」等泛用 catch-all 之前, 否則會被搶先命中而吃不到這條。
|
|
101
|
+
connect_core.TargetUnit('兩階段', break_detect=True, exceptions_=exceptions.TwoFactorAuthRequired()),
|
|
100
102
|
connect_core.TargetUnit('任意鍵', response=' '),
|
|
101
103
|
connect_core.TargetUnit('正在更新與同步線上使用者及好友名單'),
|
|
102
104
|
connect_core.TargetUnit('【分類看板】', response=command.go_main_menu),
|
|
@@ -38,6 +38,16 @@ class LoginError(Error):
|
|
|
38
38
|
return self.message
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
# 繼承 LoginError 以維持既有 `except LoginError` 呼叫端的相容性。
|
|
42
|
+
# PyPtt 不實作 TOTP 自動輸入, 這裡只負責偵測畫面並指路。
|
|
43
|
+
class TwoFactorAuthRequired(LoginError):
|
|
44
|
+
def __init__(self):
|
|
45
|
+
self.message = i18n.two_factor_auth_required
|
|
46
|
+
|
|
47
|
+
def __str__(self):
|
|
48
|
+
return self.message
|
|
49
|
+
|
|
50
|
+
|
|
41
51
|
class NoFastComment(Error):
|
|
42
52
|
def __init__(self):
|
|
43
53
|
self.message = i18n.no_fast_comment
|
|
@@ -110,6 +110,14 @@ string_data = {
|
|
|
110
110
|
"success": "Success",
|
|
111
111
|
"title": "Title",
|
|
112
112
|
"transaction_cancelled": "The transaction is cancelled!",
|
|
113
|
+
"two_factor_auth_required": (
|
|
114
|
+
"This account has two-factor authentication (2FA) enabled, and PyPtt cannot "
|
|
115
|
+
"complete the verification automatically. PTT's 2FA can be configured to only "
|
|
116
|
+
"require verification from new IPs, so logging in again from the same IP will "
|
|
117
|
+
"skip it. Please first manually log in to PTT from this machine using a regular "
|
|
118
|
+
"BBS client and complete one 2FA verification; afterwards this machine's IP will "
|
|
119
|
+
"be considered verified, and you can then log in with PyPtt."
|
|
120
|
+
),
|
|
113
121
|
"unregistered_user_cant_use_all_api": "Unregistered UserField Can't Use All API",
|
|
114
122
|
"unregistered_user_cant_use_this_api": "Unregistered UserField Can't Use This API",
|
|
115
123
|
"update_remote_version": "Fetching latest version",
|
|
@@ -110,6 +110,12 @@ string_data = {
|
|
|
110
110
|
"success": "成功",
|
|
111
111
|
"title": "標題",
|
|
112
112
|
"transaction_cancelled": "交易取消!",
|
|
113
|
+
"two_factor_auth_required": (
|
|
114
|
+
"此帳號已啟用兩階段驗證(2FA), PyPtt 無法自動完成驗證。"
|
|
115
|
+
"PTT 的兩階段驗證可設定為「僅新 IP 需要驗證」, 同一 IP 再次登入時會跳過驗證。"
|
|
116
|
+
"請先在這台機器上使用一般 BBS 客戶端手動登入 PTT 並完成一次兩階段驗證, "
|
|
117
|
+
"之後這台機器的 IP 即可被視為已驗證, 再用 PyPtt 登入。"
|
|
118
|
+
),
|
|
113
119
|
"unregistered_user_cant_use_all_api": "未註冊使用者,將無法使用全部功能",
|
|
114
120
|
"unregistered_user_cant_use_this_api": "未註冊使用者,無法使用此功能",
|
|
115
121
|
"update_remote_version": "確認最新版本",
|
|
@@ -63,10 +63,14 @@ def _str_pos_at_cells(line: str, cells: int) -> int:
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
class Target:
|
|
66
|
+
# 正式站狀態列有兩種寫法, 短的那種會把逗號後的空格與中括號吃掉:
|
|
67
|
+
# [5/23 星期六 16:40] [ 射手時 ] 線上27866人, 我是CodingMan [呼叫器]打開
|
|
68
|
+
# 8/19週三22:35 [ 七夕 ] 線上30721人,我是DeepLearning 呼叫器關閉 (h)說明
|
|
69
|
+
# 只比對兩種格式共同的最短關鍵詞。
|
|
66
70
|
MainMenu = [
|
|
67
71
|
'離開,再見',
|
|
68
|
-
'
|
|
69
|
-
'
|
|
72
|
+
'我是',
|
|
73
|
+
'呼叫器',
|
|
70
74
|
]
|
|
71
75
|
|
|
72
76
|
MainMenu_Exiting = [
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""PTT 主選單狀態列有長短兩種寫法, 兩種都必須認得。
|
|
2
|
+
|
|
3
|
+
長: [5/23 星期六 16:40] [ 射手時 ] 線上27866人, 我是CodingMan [呼叫器]打開
|
|
4
|
+
短: 8/19週三22:35 [ 七夕 ] 線上30721人,我是DeepLearning 呼叫器關閉 (h)說明
|
|
5
|
+
|
|
6
|
+
短的那種把逗號後的空格與呼叫器的中括號都吃掉了, 日期也從 '星期六' 縮成 '週三'。
|
|
7
|
+
"""
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
from PyPtt import data_type
|
|
11
|
+
from PyPtt._api_call_status import _call_status_map, _call_status_pattern
|
|
12
|
+
from PyPtt._api_get_time import pattern as time_pattern
|
|
13
|
+
from PyPtt.screens import Target
|
|
14
|
+
|
|
15
|
+
LONG = '[5/23 星期六 16:40] [ 射手時 ] 線上27866人, 我是CodingMan [呼叫器]打開 '
|
|
16
|
+
SHORT = '8/19週三22:35 [ 七夕 ] 線上30721人,我是DeepLearning 呼叫器關閉 (h)說明'
|
|
17
|
+
|
|
18
|
+
MENU_BODY = ' (G)oodbye 離開,再見…'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_main_menu_markers_match_both_status_lines():
|
|
22
|
+
for status in (LONG, SHORT):
|
|
23
|
+
screen = MENU_BODY + '\n' + status
|
|
24
|
+
assert all(t in screen for t in Target.MainMenu), status
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_get_time_line_filter_and_pattern():
|
|
28
|
+
for status, expected in ((LONG, '16:40'), (SHORT, '22:35')):
|
|
29
|
+
assert '線上' in status and '我是' in status
|
|
30
|
+
assert time_pattern.search(status).group(0) == expected
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_call_status_pattern():
|
|
34
|
+
assert _call_status_map[_call_status_pattern.search(LONG).group(1)] == data_type.CallStatus.ON
|
|
35
|
+
assert _call_status_map[_call_status_pattern.search(SHORT).group(1)] == data_type.CallStatus.OFF
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_call_status_pattern_covers_every_state():
|
|
39
|
+
for word, expected in _call_status_map.items():
|
|
40
|
+
for form in (f'[呼叫器]{word}', f'呼叫器{word}'):
|
|
41
|
+
assert _call_status_map[_call_status_pattern.search(form).group(1)] == expected
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|