Skip to content

Commit 2b6e37f

Browse files
committed
update launch task with template variable support
1 parent 98d8c27 commit 2b6e37f

8 files changed

Lines changed: 234 additions & 7 deletions

File tree

src-tauri/src/commands/utils.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,3 +277,118 @@ pub fn build_launch_command(
277277

278278
cmd
279279
}
280+
281+
/// 获取当前 instance 所有已勾选 task 的状态
282+
///
283+
/// - 输出为数组,按照 instance 中 task 的顺序排列。
284+
/// - 数组的每项是一个包含两个字符串的数组:[任务名称(i18n), 任务状态("idle","pending","running","succeeded","failed")]。
285+
pub fn get_checked_task_status_of_instance(
286+
app_handle: Option<&AppHandle>,
287+
instance_id: Option<&str>,
288+
) -> Vec<Vec<String>> {
289+
let app_config_state = app_handle
290+
.and_then(|app| {
291+
Some(
292+
app.try_state::<Arc<crate::commands::AppConfigState>>()
293+
.unwrap(),
294+
)
295+
})
296+
.unwrap();
297+
let translations = app_config_state.translations.lock().ok().unwrap(); // content as (dist => locales/interface/<selected-language>.ts)
298+
let config = app_config_state.config.lock().ok().unwrap(); // content as (dist => configs/mxu-<program-name>.ts)
299+
300+
// i18n
301+
let language = config
302+
.get("settings")
303+
.and_then(|v| v.get("language"))
304+
.and_then(|v| v.as_str())
305+
.unwrap_or("system");
306+
let i18n = translations
307+
.get(match language {
308+
// get i18n task name by (i18n["task.<task-name>.label"])
309+
"zh-TW" => "zh_tw",
310+
"en-US" => "en_us",
311+
"ja-JP" => "ja_jp",
312+
"ko-KR" => "ko_kr",
313+
_ => "zh_cn",
314+
})
315+
.unwrap_or_default();
316+
317+
// instance (config)
318+
let id = instance_id.unwrap_or("");
319+
let instance_config_list = config.get("instances");
320+
let instance_config = instance_config_list
321+
.unwrap()
322+
.as_array()
323+
.unwrap()
324+
.iter()
325+
.find(|inst| inst.get("id").and_then(|v| v.as_str()) == Some(id))
326+
.unwrap();
327+
328+
// instance (runtime)
329+
let maa_state = app_handle
330+
.and_then(|app| Some(app.try_state::<Arc<crate::commands::MaaState>>().unwrap()))
331+
.unwrap();
332+
let instance_runtime_list = maa_state.instances.lock().ok().unwrap();
333+
let instance_runtime = instance_runtime_list
334+
.get(instance_id.unwrap_or_default())
335+
.unwrap();
336+
337+
return instance_runtime
338+
.task_run_state
339+
.pending_task_ids
340+
.iter()
341+
.filter_map(|&maa_task_id| {
342+
if let Some(selected_task_id) =
343+
instance_runtime.task_run_state.mappings.get(&maa_task_id)
344+
{
345+
if let Some(status) = instance_runtime
346+
.task_run_state
347+
.statuses
348+
.get(selected_task_id)
349+
{
350+
let task_config_list = instance_config.get("tasks").unwrap();
351+
let task_config = task_config_list
352+
.as_array()
353+
.unwrap()
354+
.iter()
355+
.find(|task| {
356+
task.get("id").and_then(|v| v.as_str())
357+
== Some(selected_task_id.as_str())
358+
})
359+
.unwrap();
360+
let task_name = task_config
361+
.get("taskName")
362+
.unwrap()
363+
.as_str()
364+
.unwrap()
365+
.to_string();
366+
let custom_name = task_config
367+
.get("customName")
368+
.and_then(|v| v.as_str())
369+
.unwrap_or("")
370+
.to_string();
371+
let task_name_i18n = i18n
372+
.get(format!("task.{}.label", task_name))
373+
.and_then(|v| v.as_str())
374+
.unwrap_or("")
375+
.to_string();
376+
// custom name / task name (i18n) / task name
377+
// => task status ("idle","pending","running","succeeded","failed")
378+
let name: String = if !custom_name.is_empty() {
379+
custom_name
380+
} else if !task_name_i18n.is_empty() {
381+
task_name_i18n
382+
} else {
383+
task_name
384+
};
385+
Some(vec![name, status.to_string()])
386+
} else {
387+
None
388+
}
389+
} else {
390+
None
391+
}
392+
})
393+
.collect();
394+
}

src-tauri/src/mxu_actions.rs

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! 提供 MXU 特有的自定义动作实现,如 MXU_SLEEP 等
44
5+
use base64::{engine::general_purpose, Engine as _};
56
use chrono::TimeZone;
67
use log::{info, warn};
78
use maa_framework::custom::FnAction;
@@ -200,7 +201,62 @@ const MXU_LAUNCH_ACTION: &str = "MXU_LAUNCH_ACTION";
200201
fn mxu_launch_action_fn(
201202
_ctx: &maa_framework::context::Context,
202203
args: &maa_framework::custom::ActionArgs,
204+
app_handle: Option<&AppHandle>,
205+
instance_id: Option<&str>,
203206
) -> bool {
207+
//
208+
// {{STATUS}}
209+
//
210+
// success - 任务1
211+
// failed - 任务2
212+
// pending - 任务3
213+
//
214+
let task_status =
215+
crate::commands::utils::get_checked_task_status_of_instance(app_handle, instance_id);
216+
let status = task_status
217+
.iter()
218+
.map(|row| format!("{} - {}", row[1], row[0]))
219+
.collect::<Vec<String>>()
220+
.join("\n");
221+
//
222+
// {{S_BASE64}}
223+
//
224+
// c3VjY2VzcyAtIOS7u+WKoTEKZmFpbGVkIC0g5Lu75YqhMgpwZW5kaW5nIC0g5Lu75YqhMw==
225+
//
226+
let s_base64 = general_purpose::STANDARD.encode(&status);
227+
//
228+
// {{S_CSV}}
229+
//
230+
// NAME,STATUS
231+
// 任务1,success
232+
// 任务2,failed
233+
// 任务3,pending
234+
//
235+
let s_csv = format!(
236+
"NAME,STATUS\n{}",
237+
task_status
238+
.iter()
239+
.map(|row| format!("{},{}", row[0], row[1]))
240+
.collect::<Vec<String>>()
241+
.join("\n")
242+
);
243+
//
244+
// {{S_JSON}}
245+
//
246+
// [["任务1","success"],["任务2","failed"],["任务3","pending"]]
247+
//
248+
let s_json = match serde_json::to_string(&task_status) {
249+
Ok(v) => v,
250+
Err(_) => String::from("[]"),
251+
};
252+
//
253+
// {{S_JSON_BASE64}}
254+
//
255+
// W1si5Lu75YqhMSIsInN1Y2Nlc3MiXSxbIuS7u+WKoTIiLCJmYWlsZWQiXSxbIuS7u+WKoTMiLCJwZW5kaW5nIl1d
256+
//
257+
let s_json_base64 = general_purpose::STANDARD.encode(&s_json);
258+
info!("[MXU_LAUNCH] Generated task(s) status: {}", &s_json);
259+
204260
let param_str = args.param;
205261
info!("[MXU_LAUNCH] Received param: {}", param_str);
206262

@@ -224,7 +280,12 @@ fn mxu_launch_action_fn(
224280
.get("args")
225281
.and_then(|v| v.as_str())
226282
.unwrap_or("")
227-
.to_string();
283+
.to_string()
284+
.replace("{{STATUS}}", &status)
285+
.replace("{{S_BASE64}}", &s_base64)
286+
.replace("{{S_CSV}}", &s_csv)
287+
.replace("{{S_JSON}}", &s_json)
288+
.replace("{{S_JSON_BASE64}}", &s_json_base64);
228289

229290
let wait_for_exit = json
230291
.get("wait_for_exit")
@@ -878,11 +939,56 @@ pub fn register_all_mxu_actions(
878939

879940
reg_action!(MXU_SLEEP_ACTION, mxu_sleep_action_fn);
880941
reg_action!(MXU_WAITUNTIL_ACTION, mxu_waituntil_action_fn);
881-
reg_action!(MXU_LAUNCH_ACTION, mxu_launch_action_fn);
882942
reg_action!(MXU_WEBHOOK_ACTION, mxu_webhook_action_fn);
883943
reg_action!(MXU_NOTIFY_ACTION, mxu_notify_action_fn);
884944
reg_action!(MXU_POWER_ACTION, mxu_power_action_fn);
885945

946+
// launch
947+
948+
let launch_app_handle = app_handle.clone();
949+
let launch_instance_id = instance_id.to_string();
950+
let launch_wrapper = move |ctx: &maa_framework::context::Context,
951+
args: &maa_framework::custom::ActionArgs|
952+
-> bool {
953+
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
954+
mxu_launch_action_fn(
955+
ctx,
956+
args,
957+
Some(&launch_app_handle),
958+
Some(&launch_instance_id),
959+
)
960+
}))
961+
.unwrap_or_else(|e| {
962+
let msg = if let Some(s) = e.downcast_ref::<&str>() {
963+
s.to_string()
964+
} else if let Some(s) = e.downcast_ref::<String>() {
965+
s.clone()
966+
} else {
967+
"Unknown panic payload".to_string()
968+
};
969+
log::error!(
970+
"[MXU] Custom action {} panicked: {}",
971+
MXU_LAUNCH_ACTION,
972+
msg
973+
);
974+
false
975+
})
976+
};
977+
978+
if let Err(e) =
979+
resource.register_custom_action(MXU_LAUNCH_ACTION, Box::new(FnAction::new(launch_wrapper)))
980+
{
981+
warn!("[MXU] Failed to register {}: {:?}", MXU_LAUNCH_ACTION, e);
982+
failed_count += 1;
983+
} else {
984+
info!(
985+
"[MXU] Custom action {} registered successfully",
986+
MXU_LAUNCH_ACTION
987+
);
988+
}
989+
990+
// kill process
991+
886992
let killproc_app_handle = app_handle.clone();
887993
let killproc_instance_id = instance_id.to_string();
888994
let killproc_wrapper = move |ctx: &maa_framework::context::Context,

src/i18n/locales/en-US.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ export default {
140140
programLabel: 'Program Path',
141141
programPlaceholder: 'Enter program path or click browse...',
142142
argsLabel: 'Additional Arguments',
143-
argsPlaceholder: 'Enter additional arguments (optional)',
143+
argsPlaceholder: 'Enter additional arguments (optional, template variables supported)',
144+
argsDescription: 'Template variables {{STATUS}} {{S_BASE64}} {{S_CSV}} {{S_JSON}} {{S_JSON_BASE64}} represent the real-time status of selected tasks of the current instance. See https://github.com/MistEO/MXU/blob/main/src-tauri/src/mxu_actions.rs#L207 for details.',
144145
waitLabel: 'Wait for Exit',
145146
waitDescription:
146147
'When disabled, continues immediately after launch; when enabled, waits for the process to exit before continuing, suitable for scripts that need to complete synchronously',

src/i18n/locales/ja-JP.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ export default {
279279
program: 'プログラムパス',
280280
programPlaceholder: 'プログラムパスを入力または参照...',
281281
args: '追加引数',
282-
argsPlaceholder: '追加引数を入力(オプション)',
282+
argsPlaceholder: '追加パラメータを入力(オプション、テンプレート変数可)',
283+
argsDescription: 'テンプレート変数 {{STATUS}} {{S_BASE64}} {{S_CSV}} {{S_JSON}} {{S_JSON_BASE64}} は、現在のインスタンスの選択されたタスクのリアルタイム状態を表します。詳細は https://github.com/MistEO/MXU/blob/main/src-tauri/src/mxu_actions.rs#L207 を参照してください。',
283284
browse: '参照',
284285
waitForExit: '終了を待機',
285286
waitForExitHintPre:

src/i18n/locales/ko-KR.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ export default {
137137
programLabel: '프로그램 경로',
138138
programPlaceholder: '프로그램 경로를 입력하거나 오른쪽 찾아보기를 클릭...',
139139
argsLabel: '추가 인수',
140-
argsPlaceholder: '추가 인수 입력 (선택 사항)',
140+
argsPlaceholder: '추가 인수 입력(선택 사항, 템플릿 변수 지원)',
141+
argsDescription: '템플릿 변수 {{STATUS}} {{S_BASE64}} {{S_CSV}} {{S_JSON}} {{S_JSON_BASE64}} 는 현재 인스턴스의 선택된 모든 작업의 실시간 상태를 나타냅니다. 자세한 내용은 https://github.com/MistEO/MXU/blob/main/src-tauri/src/mxu_actions.rs#L207 를 참조하세요.',
141142
waitLabel: '종료 대기',
142143
waitDescription:
143144
'비활성화하면 실행 후 즉시 계속합니다. 활성화하면 프로세스 종료 후 계속합니다. 스크립트 등 동기 완료가 필요한 작업에 적합합니다',

src/i18n/locales/zh-CN.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ export default {
136136
programLabel: '程序路径',
137137
programPlaceholder: '输入程序路径或点击右侧浏览...',
138138
argsLabel: '附加参数',
139-
argsPlaceholder: '输入附加参数(可选)',
139+
argsPlaceholder: '输入附加参数(可选,支持模板变量)',
140+
argsDescription: '模板变量 {{STATUS}} {{S_BASE64}} {{S_CSV}} {{S_JSON}} {{S_JSON_BASE64}} 表示 当前实例-所有选定任务-实时状态,详见 https://github.com/MistEO/MXU/blob/main/src-tauri/src/mxu_actions.rs#L207 。',
140141
waitLabel: '等待退出',
141142
waitDescription: '禁用时启动进程后立即继续;启用时等待进程退出后再继续作',
142143
waitYes: '等待程序退出后继续',

src/i18n/locales/zh-TW.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ export default {
135135
programLabel: '程式路徑',
136136
programPlaceholder: '輸入程式路徑或點擊右側瀏覽...',
137137
argsLabel: '附加參數',
138-
argsPlaceholder: '輸入附加參數(可選)',
138+
argsPlaceholder: '輸入附加參數(可選,支持模板變量)',
139+
argsDescription: '模板變量 {{STATUS}} {{S_BASE64}} {{S_CSV}} {{S_JSON}} {{S_JSON_BASE64}} 表示 當前實例-所有選定任務-即時狀態,詳見 https://github.com/MistEO/MXU/blob/main/src-tauri/src/mxu_actions.rs#L207 。',
139140
waitLabel: '等待退出',
140141
waitDescription:
141142
'禁用時啟動程序後立即繼續;啟用時等待程序退出後再繼續,適用於執行腳本等需要同步完成的操作',

src/types/specialTasks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ const MXU_LAUNCH_INPUT_OPTION_DEF_INTERNAL: InputOption = {
191191
default: '',
192192
pipeline_type: 'string',
193193
placeholder: 'specialTask.launch.argsPlaceholder',
194+
description: 'specialTask.launch.argsDescription',
194195
},
195196
],
196197
pipeline_override: {

0 commit comments

Comments
 (0)