starrailassistant 2.17.0__py3-none-any.whl

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 (59) hide show
  1. SRACore/__init__.py +1 -0
  2. SRACore/__main__.py +115 -0
  3. SRACore/cli2.py +344 -0
  4. SRACore/localization/__init__.py +3 -0
  5. SRACore/localization/resource.py +471 -0
  6. SRACore/localization/resource.toml +224 -0
  7. SRACore/localization/resource_en-us.json +58 -0
  8. SRACore/localization/resource_zh-cn.json +58 -0
  9. SRACore/models/__init__.py +0 -0
  10. SRACore/models/app_settings.py +413 -0
  11. SRACore/models/tasks_config.py +300 -0
  12. SRACore/notification/__init__.py +25 -0
  13. SRACore/notification/channels/__init__.py +33 -0
  14. SRACore/notification/channels/bark.py +43 -0
  15. SRACore/notification/channels/base.py +15 -0
  16. SRACore/notification/channels/common.py +44 -0
  17. SRACore/notification/channels/dingtalk.py +50 -0
  18. SRACore/notification/channels/discord.py +56 -0
  19. SRACore/notification/channels/email.py +65 -0
  20. SRACore/notification/channels/feishu.py +51 -0
  21. SRACore/notification/channels/onebot.py +45 -0
  22. SRACore/notification/channels/serverchan.py +30 -0
  23. SRACore/notification/channels/system.py +26 -0
  24. SRACore/notification/channels/telegram.py +50 -0
  25. SRACore/notification/channels/webhook.py +20 -0
  26. SRACore/notification/channels/wecom.py +63 -0
  27. SRACore/notification/channels/xxtui.py +28 -0
  28. SRACore/notification/dispatcher.py +40 -0
  29. SRACore/notification/http_client.py +112 -0
  30. SRACore/notification/models.py +29 -0
  31. SRACore/notification/service.py +241 -0
  32. SRACore/operators/__init__.py +0 -0
  33. SRACore/operators/browser_operator.py +425 -0
  34. SRACore/operators/ioperator.py +689 -0
  35. SRACore/operators/model.py +56 -0
  36. SRACore/operators/operator.py +313 -0
  37. SRACore/runtime/__init__.py +4 -0
  38. SRACore/runtime/event_listener.py +116 -0
  39. SRACore/runtime/trigger_manager.py +83 -0
  40. SRACore/task/__init__.py +124 -0
  41. SRACore/thread/__init__.py +2 -0
  42. SRACore/thread/task_process.py +312 -0
  43. SRACore/triggers/AutoPlotTrigger.py +42 -0
  44. SRACore/triggers/BaseTrigger.py +24 -0
  45. SRACore/triggers/__init__.py +3 -0
  46. SRACore/util/__init__.py +0 -0
  47. SRACore/util/const.py +32 -0
  48. SRACore/util/data_persister.py +69 -0
  49. SRACore/util/encryption.py +27 -0
  50. SRACore/util/errors.py +238 -0
  51. SRACore/util/image_util.py +59 -0
  52. SRACore/util/logger.py +28 -0
  53. SRACore/util/sys_util.py +100 -0
  54. starrailassistant-2.17.0.dist-info/METADATA +175 -0
  55. starrailassistant-2.17.0.dist-info/RECORD +59 -0
  56. starrailassistant-2.17.0.dist-info/WHEEL +5 -0
  57. starrailassistant-2.17.0.dist-info/entry_points.txt +2 -0
  58. starrailassistant-2.17.0.dist-info/licenses/LICENSE +674 -0
  59. starrailassistant-2.17.0.dist-info/top_level.txt +1 -0
SRACore/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """StarRailAssistant Core - 崩坏:星穹铁道自动化助手"""
SRACore/__main__.py ADDED
@@ -0,0 +1,115 @@
1
+ import argparse
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ sys.path.append(os.getcwd()) # 将当前工作目录添加到 sys.path,以便导入 tasks
6
+
7
+ from SRACore.localization import Resource
8
+ from SRACore.util.const import VERSION
9
+ from SRACore.util.data_persister import load_app_settings
10
+
11
+
12
+ def main():
13
+ settings = load_app_settings()
14
+ language: int = settings.Display.language
15
+ Resource.set_language(language)
16
+ parser = argparse.ArgumentParser(
17
+ description=Resource.argparse_description,
18
+ epilog=Resource.argparse_epilog,
19
+ formatter_class=argparse.RawTextHelpFormatter
20
+ )
21
+ setup_argumentparser(parser)
22
+ # 解析参数
23
+ args = parser.parse_known_args()[0]
24
+ if not is_admin():
25
+ if args.no_admin:
26
+ sys.argv.remove('--no-admin') # 移除参数,不向下传递
27
+ print(Resource.cli_noAdminWarning)
28
+ else:
29
+ restart_as_admin()
30
+ from SRACore.util.logger import logger, setup_logger
31
+ # 设置日志记录器
32
+ setup_logger(level=args.log_level)
33
+ logger.info(f"Current version: {VERSION}")
34
+ logger.debug(f"cwd: {os.getcwd()}")
35
+
36
+ if args.command:
37
+ for cmd in args.command:
38
+ sys.argv.remove(cmd) # 移除命令参数, 避免重复执行
39
+ cmd_str = " ".join(args.command).replace('&', '+')
40
+ commands = cmd_str.split('+')
41
+ for cmd in commands:
42
+ sys.argv.append(cmd)
43
+ print(sys.argv)
44
+ inline = args.inline
45
+ if inline:
46
+ sys.argv.remove('--inline')
47
+ # 延迟导入 SRACli
48
+ from SRACore.cli2 import SRACli
49
+ cli_instance = SRACli(settings=settings)
50
+ # 配置交互式模式(隐藏提示符)
51
+ if inline:
52
+ cli_instance.intro = ''
53
+ cli_instance.prompt = ''
54
+ cli_instance.cmdloop()
55
+
56
+
57
+ def setup_argumentparser(parser: argparse.ArgumentParser) -> None:
58
+ parser.add_argument(
59
+ '--inline',
60
+ action='store_true',
61
+ help=Resource.argparse_inline_help
62
+ )
63
+ parser.add_argument(
64
+ '--command', '-c', '--execute', '-e',
65
+ nargs='*',
66
+ type=str,
67
+ help='The command to execute AFTER launch',
68
+ )
69
+ parser.add_argument(
70
+ '-v', '--version',
71
+ action='version',
72
+ version=f'{VERSION}',
73
+ help=Resource.argparse_version_help
74
+ )
75
+ parser.add_argument(
76
+ '--log-level',
77
+ type=str,
78
+ choices=['TRACE', 'DEBUG', 'INFO', 'SUCCESS', 'WARNING', 'ERROR', 'CRITICAL'],
79
+ default='TRACE',
80
+ help=Resource.argparse_log_level_help
81
+ )
82
+ parser.add_argument(
83
+ '--no-admin',
84
+ action='store_true',
85
+ help="Do not require admin privileges"
86
+ )
87
+
88
+
89
+ def restart_as_admin():
90
+ """以管理员权限重启当前进程"""
91
+ if sys.platform == 'win32' and not is_admin():
92
+ import ctypes
93
+ cmdline = subprocess.list2cmdline(sys.argv)
94
+ executable = sys.executable.removesuffix('.exe')
95
+ executable += '.exe'
96
+ result = ctypes.windll.shell32.ShellExecuteW(None, 'runas', 'wt.exe', f'"{executable}" {cmdline}', None, 1)
97
+ if result > 32:
98
+ sys.exit(0)
99
+ else:
100
+ result = ctypes.windll.shell32.ShellExecuteW(None, 'runas', executable, cmdline, None, 1)
101
+ sys.exit(result)
102
+
103
+
104
+ def is_admin() -> bool:
105
+ """检查当前用户是否具有管理员权限(仅限 Windows)"""
106
+ try:
107
+ import ctypes
108
+ return ctypes.windll.shell32.IsUserAnAdmin() != 0 # NOQA
109
+ except Exception as e:
110
+ print(f"Error checking administrator privileges: {e}")
111
+ return False
112
+
113
+
114
+ if __name__ == '__main__':
115
+ main()
SRACore/cli2.py ADDED
@@ -0,0 +1,344 @@
1
+ import argparse
2
+
3
+ import cmd2
4
+ from loguru import logger
5
+ from rich.text import Text
6
+
7
+ from SRACore.localization import Resource
8
+ from SRACore.models.app_settings import AppSettings
9
+ from SRACore.runtime.event_listener import KeyboardListener
10
+ from SRACore.runtime.trigger_manager import TriggerManager
11
+ from SRACore.thread.task_process import TaskManager
12
+ from SRACore.util.const import VERSION, CORE
13
+
14
+
15
+ class SRACli(cmd2.Cmd):
16
+ def __init__(self, settings: AppSettings):
17
+ super().__init__(startup_script=".srarc")
18
+ self.intro = f"Welcome to SRA-cli (version {VERSION}, core {CORE}). \nType 'help' to list commands."
19
+ self.prompt = 'sra> '
20
+ self.default_error = Resource.cli_defaultError
21
+ self.settings = settings
22
+
23
+ # 移除不需要的 settable 选项
24
+ # for attr in ["debug", "timing", "quiet", "feedback_to_output",
25
+ # "max_completion_items", "allow_style", "always_show_hint",
26
+ # "scripts_add_to_history", "echo"]:
27
+ # self.remove_settable(attr)
28
+
29
+ # 移除不需要的内置命令
30
+ for cmd_name in ["run_pyscript"]:
31
+ if hasattr(cmd2.Cmd, f"do_{cmd_name}"):
32
+ delattr(cmd2.Cmd, f"do_{cmd_name}")
33
+ # 初始化任务管理器
34
+ self.task_manager = TaskManager(settings)
35
+ # 初始化触发器管理器
36
+ self.trigger_manager = TriggerManager()
37
+
38
+ # 初始化键盘监听器
39
+ stop_hotkey = settings.General.hotkeyStop.lower() or 'f9'
40
+ self.event_listener = KeyboardListener()
41
+ self.event_listener.register_key_event(stop_hotkey, self._task_stop)
42
+ self.event_listener.start()
43
+
44
+ # region 任务管理
45
+ @staticmethod
46
+ def _build_task_parser() -> argparse.ArgumentParser:
47
+ task_description = Text.assemble(Resource.task_description)
48
+ task_parser = cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=task_description)
49
+ task_parser.add_subparsers(metavar="SUBCOMMAND", required=True)
50
+ return task_parser
51
+
52
+ @cmd2.with_argparser(_build_task_parser(), preserve_quotes=True)
53
+ def do_task(self, args: argparse.Namespace) -> None:
54
+ handler = args.cmd2_handler.get()
55
+ handler(args)
56
+
57
+ @staticmethod
58
+ def _build_task_run_parser() -> cmd2.Cmd2ArgumentParser:
59
+ task_run_description = Text.assemble(Resource.run_description)
60
+ task_run_parser = cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=task_run_description)
61
+ task_run_parser.add_argument('config', nargs='*', help=Resource.run_configHelp)
62
+ return task_run_parser
63
+
64
+ @cmd2.as_subcommand_to("task", "run", _build_task_run_parser(), help=Resource.run_configHelp)
65
+ def _task_run(self, args: argparse.Namespace) -> None:
66
+ if self.task_manager.is_thread_running():
67
+ self.poutput(Resource.cli_task_taskAlreadyRunning)
68
+ return
69
+ self.task_manager.run_in_thread(*args.config)
70
+
71
+ @staticmethod
72
+ def _build_task_single_parser() -> cmd2.Cmd2ArgumentParser:
73
+ task_single_description = Text.assemble(Resource.single_description)
74
+ task_single_parser = cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=task_single_description)
75
+ task_single_parser.add_argument('task', help=Resource.single_taskHelp)
76
+ task_single_parser.add_argument('--config', help=Resource.single_configHelp)
77
+ return task_single_parser
78
+
79
+ @cmd2.as_subcommand_to("task", "single", _build_task_single_parser(), help=Resource.single_description)
80
+ def _task_single(self, args: argparse.Namespace) -> None:
81
+ if self.task_manager.is_thread_running():
82
+ self.poutput(Resource.cli_task_taskAlreadyRunning)
83
+ return
84
+ if self.task_manager.run_task_in_thread(args.task, args.config):
85
+ self.poutput(Resource.cli_run_started)
86
+
87
+ @staticmethod
88
+ def _build_task_stop_parser() -> cmd2.Cmd2ArgumentParser:
89
+ task_stop_description = Text.assemble(Resource.stop_description)
90
+ return cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=task_stop_description)
91
+
92
+ @cmd2.as_subcommand_to("task", "stop", _build_task_stop_parser(), help=Resource.stop_description)
93
+ def _task_stop(self, _) -> None:
94
+ if self.task_manager.is_thread_running():
95
+ self.task_manager.stop_thread()
96
+ self.poutput(Resource.cli_task_stopped)
97
+ else:
98
+ self.poutput(Resource.cli_task_notRunning)
99
+
100
+ @staticmethod
101
+ def _build_run_parser() -> cmd2.Cmd2ArgumentParser:
102
+ run_description = Text.assemble(Resource.run_description)
103
+ run_parser = cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=run_description)
104
+ run_parser.add_argument('config', nargs='*', help=Resource.run_configHelp)
105
+ return run_parser
106
+
107
+ @cmd2.with_argparser(_build_run_parser())
108
+ def do_run(self, args: argparse.Namespace) -> None:
109
+ """Run specified tasks, will block current command line until tasks complete"""
110
+ self.poutput(Resource.cli_run_started)
111
+ try:
112
+ self.task_manager.run(*args.config)
113
+ except KeyboardInterrupt:
114
+ self.task_manager.request_stop()
115
+
116
+ @staticmethod
117
+ def _build_single_parser() -> cmd2.Cmd2ArgumentParser:
118
+ single_description = Text.assemble(Resource.single_description)
119
+ single_parser = cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=single_description)
120
+ single_parser.add_argument('task', help=Resource.single_taskHelp)
121
+ single_parser.add_argument('--config', help=Resource.single_configHelp)
122
+ return single_parser
123
+
124
+ @cmd2.with_argparser(_build_single_parser())
125
+ def do_single(self, args: argparse.Namespace) -> None:
126
+ """Run a single specified task, will block current command line until task complete"""
127
+ self.poutput(Resource.cli_run_started)
128
+ try:
129
+ self.task_manager.run_task(args.task, args.config)
130
+ except KeyboardInterrupt:
131
+ self.task_manager.request_stop()
132
+
133
+ # endregion
134
+
135
+ # region 触发器管理
136
+
137
+ @staticmethod
138
+ def _build_trigger_parser() -> argparse.ArgumentParser:
139
+ trigger_description = Text.assemble(Resource.trigger_description)
140
+ trigger_parser = cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=trigger_description)
141
+ trigger_parser.add_subparsers(metavar="SUBCOMMAND", required=True)
142
+ return trigger_parser
143
+
144
+ @cmd2.with_argparser(_build_trigger_parser(), preserve_quotes=True)
145
+ def do_trigger(self, args: argparse.Namespace) -> None:
146
+ handler = args.cmd2_handler.get()
147
+ handler(args)
148
+
149
+ @staticmethod
150
+ def _build_trigger_run_parser() -> cmd2.Cmd2ArgumentParser:
151
+ trigger_run_description = Text.assemble(Resource.trigger_run_description)
152
+ return cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=trigger_run_description)
153
+
154
+ @cmd2.as_subcommand_to("trigger", "run", _build_trigger_run_parser(), help=Resource.trigger_run_description)
155
+ def _trigger_run(self, _) -> None:
156
+ if self.trigger_manager.is_thread_running():
157
+ self.poutput(Resource.cli_trigger_alreadyRunning)
158
+ return
159
+ if not self.trigger_manager.has_enabled_triggers():
160
+ self.poutput(Resource.cli_trigger_noEnabledTriggers)
161
+ return
162
+ self.trigger_manager.start_thread()
163
+ self.poutput(Resource.cli_trigger_started)
164
+
165
+ @staticmethod
166
+ def _build_trigger_stop_parser() -> cmd2.Cmd2ArgumentParser:
167
+ trigger_stop_description = Text.assemble(Resource.trigger_stop_description)
168
+ return cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=trigger_stop_description)
169
+
170
+ @cmd2.as_subcommand_to("trigger", "stop", _build_trigger_stop_parser(), help=Resource.trigger_stop_description)
171
+ def _trigger_stop(self, _) -> None:
172
+ if self.trigger_manager.is_thread_running():
173
+ self.trigger_manager.stop_thread()
174
+ self.poutput(Resource.cli_trigger_stopped)
175
+ else:
176
+ self.poutput(Resource.cli_trigger_notRunning)
177
+
178
+ @staticmethod
179
+ def _build_trigger_enable_parser() -> cmd2.Cmd2ArgumentParser:
180
+ trigger_enable_description = Text.assemble(Resource.trigger_enable_description)
181
+ trigger_enable_parser = cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=trigger_enable_description)
182
+ trigger_enable_parser.add_argument('name', help=Resource.trigger_enable_nameHelp)
183
+ return trigger_enable_parser
184
+
185
+ @cmd2.as_subcommand_to("trigger", "enable", _build_trigger_enable_parser(), help=Resource.trigger_enable_description)
186
+ def _trigger_enable(self, args: argparse.Namespace) -> None:
187
+ for trigger in self.trigger_manager.triggers:
188
+ if trigger.__class__.__name__.lower() == args.name.lower():
189
+ trigger.set_enable(True)
190
+ logger.info(Resource.cli_trigger_enabled(args.name))
191
+ self.trigger_manager.ensure_running()
192
+ return
193
+ self.poutput(Resource.cli_trigger_notFound(args.name))
194
+
195
+ @staticmethod
196
+ def _build_trigger_disable_parser() -> cmd2.Cmd2ArgumentParser:
197
+ trigger_disable_description = Text.assemble(Resource.trigger_disable_description)
198
+ trigger_disable_parser = cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=trigger_disable_description)
199
+ trigger_disable_parser.add_argument('name', help=Resource.trigger_disable_nameHelp)
200
+ return trigger_disable_parser
201
+
202
+ @cmd2.as_subcommand_to("trigger", "disable", _build_trigger_disable_parser(), help=Resource.trigger_disable_description)
203
+ def _trigger_disable(self, args: argparse.Namespace) -> None:
204
+ for trigger in self.trigger_manager.triggers:
205
+ if trigger.__class__.__name__.lower() == args.name.lower():
206
+ trigger.set_enable(False)
207
+ logger.info(Resource.cli_trigger_disabled(args.name))
208
+ self.trigger_manager.stop_if_idle()
209
+ return
210
+ self.poutput(Resource.cli_trigger_notFound(args.name))
211
+
212
+ @staticmethod
213
+ def _build_trigger_set_parser() -> cmd2.Cmd2ArgumentParser:
214
+ trigger_set_description = Text.assemble(Resource.trigger_set_description)
215
+ trigger_set_parser = cmd2.argparse_custom.DEFAULT_ARGUMENT_PARSER(description=trigger_set_description)
216
+ trigger_set_parser.add_argument('name', help=Resource.trigger_set_nameHelp)
217
+ trigger_set_parser.add_argument('attr', help=Resource.trigger_set_attrHelp)
218
+ trigger_set_parser.add_argument('value', help=Resource.trigger_set_valueHelp)
219
+ trigger_set_parser.add_argument('--type', choices=['int', 'float', 'str', 'bool'],
220
+ default='str', help=Resource.trigger_set_typeHelp)
221
+ return trigger_set_parser
222
+
223
+ @cmd2.as_subcommand_to("trigger", "set", _build_trigger_set_parser(), help=Resource.trigger_set_description)
224
+ def _trigger_set(self, args: argparse.Namespace) -> None:
225
+ for trigger in self.trigger_manager.triggers:
226
+ if trigger.__class__.__name__.lower() == args.name.lower():
227
+ if not hasattr(trigger, args.attr):
228
+ self.poutput(Resource.cli_trigger_attrNotFound(args.attr, args.name))
229
+ return
230
+ if args.type == 'int':
231
+ setattr(trigger, args.attr, int(args.value))
232
+ elif args.type == 'float':
233
+ setattr(trigger, args.attr, float(args.value))
234
+ elif args.type == 'str':
235
+ setattr(trigger, args.attr, args.value)
236
+ elif args.type == 'bool':
237
+ setattr(trigger, args.attr, args.value.lower() in ['true', '1', 'yes'])
238
+ else:
239
+ self.poutput(Resource.cli_trigger_unknownType(args.type))
240
+ return
241
+ logger.info(Resource.cli_trigger_attrSet(args.name, args.attr, args.value))
242
+ return
243
+ self.poutput(Resource.cli_trigger_notFound(args.name))
244
+
245
+ # endregion
246
+
247
+ # region 其他命令
248
+ def do_init(self, _: str):
249
+ """Initialize the application: download resources and create default settings/config."""
250
+ import io
251
+ import json
252
+ import os
253
+ import zipfile
254
+ from urllib.error import URLError, HTTPError
255
+ from urllib.request import Request, urlopen
256
+
257
+ from SRACore.models.tasks_config import TasksConfig
258
+ from SRACore.util.const import AppDataDir, ConfigsDir
259
+
260
+ # url = f"https://github.com/Shasnow/StarRailAssistant/releases/download/v{VERSION}/StarRailAssistant_Resources_v{VERSION}.zip"
261
+ url = f"https://download.auto-mas.top/d/StarRailAssistant/StarRailAssistant_Resource_v{VERSION}.zip"
262
+ self.poutput(f"Downloading resources from {url} ...")
263
+ try:
264
+ req = Request(url, headers={"User-Agent": "SRA-cli"})
265
+ with urlopen(req) as resp:
266
+ data = resp.read()
267
+ except (URLError, HTTPError) as e:
268
+ self.poutput(f"Failed to download resources: {e}")
269
+ return
270
+
271
+ self.poutput("Extracting resources ...")
272
+ cwd = os.getcwd()
273
+ with zipfile.ZipFile(io.BytesIO(data)) as zf:
274
+ zf.extractall(cwd)
275
+ self.poutput(f"Resources extracted to {cwd}")
276
+
277
+ # 创建设置文件
278
+ AppDataDir.mkdir(parents=True, exist_ok=True)
279
+ settings_path = AppDataDir / "settings.json"
280
+ if not settings_path.exists():
281
+ settings = AppSettings.from_dict({})
282
+ with open(settings_path, "w", encoding="utf-8") as f:
283
+ json.dump(settings.to_dict(), f, indent=2, ensure_ascii=False)
284
+ self.poutput(f"Created settings file: {settings_path}")
285
+ else:
286
+ self.poutput(f"Settings file already exists: {settings_path}")
287
+
288
+ # 创建默认配置文件
289
+ ConfigsDir.mkdir(parents=True, exist_ok=True)
290
+ config_path = ConfigsDir / "Default.json"
291
+ if not config_path.exists():
292
+ config = TasksConfig.from_dict({"name": "Default"})
293
+ with open(config_path, "w", encoding="utf-8") as f:
294
+ json.dump(config.to_dict(), f, indent=2, ensure_ascii=False)
295
+ self.poutput(f"Created default config: {config_path}")
296
+ else:
297
+ self.poutput(f"Default config already exists: {config_path}")
298
+
299
+ self.poutput("Initialization completed.")
300
+ return True
301
+
302
+ def do_version(self, _: str):
303
+ """Show version information"""
304
+ self.poutput(f"{VERSION}")
305
+
306
+ def do_quit(self, _: argparse.Namespace) -> bool | None:
307
+ """Exit this application."""
308
+ self._cleanup()
309
+ # Return True to stop the command loop
310
+ self.last_result = True
311
+ return True
312
+ do_exit = do_quit
313
+
314
+ def do_notify(self, arg: str):
315
+ """Notification command - support test email/webhook/telegram/serverchan/onebot notification"""
316
+ args = arg.split()
317
+ if not args:
318
+ self.poutput(Resource.cli_invalidArguments('notify'))
319
+ return
320
+
321
+ command = args[0]
322
+ if command == 'test' and len(args) >= 2:
323
+ channel = args[1]
324
+ from SRACore.notification import send_channel_test_notification
325
+
326
+ label, result = send_channel_test_notification(channel)
327
+ if label:
328
+ self.poutput(label + "测试通知发送" + ("成功" if result else "失败"))
329
+ else:
330
+ self.poutput(Resource.cli_invalidArguments("notify"))
331
+ else:
332
+ self.poutput(Resource.cli_invalidArguments('notify'))
333
+
334
+ # endregion
335
+
336
+ # region 生命周期管理
337
+
338
+ def _cleanup(self):
339
+ """清理资源"""
340
+ self.task_manager.stop_thread(timeout=5.0)
341
+ self.trigger_manager.stop_thread(timeout=5.0)
342
+ self.event_listener.stop()
343
+
344
+ # endregion
@@ -0,0 +1,3 @@
1
+ from SRACore.localization.resource import Resource
2
+
3
+ __all__ = ['Resource']