Initial commit to new repository
This commit is contained in:
186
src/AxCopilot/Handlers/RoutineHandler.cs
Normal file
186
src/AxCopilot/Handlers/RoutineHandler.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Themes;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 루틴 자동화 핸들러. "routine" 프리픽스로 사용합니다.
|
||||
/// 등록된 루틴을 실행하면 앱·폴더·URL을 순서대로 일괄 실행합니다.
|
||||
/// 예: routine → 등록된 루틴 목록
|
||||
/// routine morning → "morning" 루틴 실행
|
||||
/// routine add morning → 루틴 추가 안내
|
||||
/// 루틴 정의: %APPDATA%\AxCopilot\routines.json
|
||||
/// </summary>
|
||||
public class RoutineHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "routine";
|
||||
|
||||
public PluginMetadata Metadata => new(
|
||||
"Routine",
|
||||
"루틴 자동화 — routine",
|
||||
"1.0",
|
||||
"AX");
|
||||
|
||||
private static readonly string RoutineFile = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"AxCopilot", "routines.json");
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true, PropertyNameCaseInsensitive = true };
|
||||
|
||||
// 내장 기본 루틴
|
||||
private static readonly RoutineDefinition[] BuiltInRoutines =
|
||||
[
|
||||
new("morning", "출근 루틴", [
|
||||
new("app", "explorer.exe", "파일 탐색기"),
|
||||
new("info", "info", "시스템 정보 표시"),
|
||||
]),
|
||||
new("endofday", "퇴근 루틴", [
|
||||
new("cmd", "journal", "오늘 업무 일지 생성"),
|
||||
]),
|
||||
];
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
var q = query.Trim();
|
||||
var all = LoadRoutines();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
var items = new List<LauncherItem>
|
||||
{
|
||||
new("루틴 자동화",
|
||||
$"총 {all.Count}개 루틴 · 이름 입력 시 실행 · routines.json에서 편집",
|
||||
null, null, Symbol: Symbols.Info)
|
||||
};
|
||||
|
||||
foreach (var r in all)
|
||||
{
|
||||
var steps = string.Join(" → ", r.Steps.Select(s => s.Label));
|
||||
items.Add(new LauncherItem(
|
||||
$"[{r.Name}] {r.Description}",
|
||||
$"{r.Steps.Length}단계: {steps} · Enter로 실행",
|
||||
null, r,
|
||||
Symbol: Symbols.Lightbulb));
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
||||
}
|
||||
|
||||
// 루틴 검색/실행
|
||||
var match = all.FirstOrDefault(r =>
|
||||
r.Name.Equals(q, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (match != null)
|
||||
{
|
||||
var steps = string.Join(" → ", match.Steps.Select(s => s.Label));
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
$"[{match.Name}] 루틴 실행",
|
||||
$"{match.Description} · {steps}",
|
||||
null, match,
|
||||
Symbol: Symbols.Lightbulb)
|
||||
]);
|
||||
}
|
||||
|
||||
// 부분 매칭
|
||||
var filtered = all.Where(r =>
|
||||
r.Name.Contains(q, StringComparison.OrdinalIgnoreCase) ||
|
||||
r.Description.Contains(q, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var result = filtered.Select(r => new LauncherItem(
|
||||
$"[{r.Name}] {r.Description}",
|
||||
$"{r.Steps.Length}단계 · Enter로 실행",
|
||||
null, r,
|
||||
Symbol: Symbols.Lightbulb)).ToList<LauncherItem>();
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
result.Add(new LauncherItem(
|
||||
$"'{q}' 루틴 없음",
|
||||
$"routines.json에서 직접 추가하거나 routine으로 목록 확인",
|
||||
null, null, Symbol: Symbols.Warning));
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(result);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is not RoutineDefinition routine) return;
|
||||
|
||||
int executed = 0;
|
||||
foreach (var step in routine.Steps)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (step.Type.ToLowerInvariant())
|
||||
{
|
||||
case "app":
|
||||
case "url":
|
||||
case "folder":
|
||||
Process.Start(new ProcessStartInfo(step.Target) { UseShellExecute = true });
|
||||
break;
|
||||
case "cmd":
|
||||
// PowerShell 명령 실행
|
||||
Process.Start(new ProcessStartInfo("powershell.exe", $"-Command \"{step.Target}\"")
|
||||
{ UseShellExecute = false, CreateNoWindow = true });
|
||||
break;
|
||||
case "info":
|
||||
// 알림으로 대체
|
||||
NotificationService.Notify("루틴", step.Label);
|
||||
break;
|
||||
}
|
||||
executed++;
|
||||
await Task.Delay(300, ct); // 앱 간 간격
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn($"루틴 단계 실행 실패: {step.Label} — {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
NotificationService.Notify("루틴 완료", $"[{routine.Name}] {executed}/{routine.Steps.Length}단계 실행 완료");
|
||||
}
|
||||
|
||||
private List<RoutineDefinition> LoadRoutines()
|
||||
{
|
||||
var list = new List<RoutineDefinition>(BuiltInRoutines);
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(RoutineFile))
|
||||
{
|
||||
var json = File.ReadAllText(RoutineFile);
|
||||
var user = JsonSerializer.Deserialize<List<RoutineDefinition>>(json, JsonOpts);
|
||||
if (user != null)
|
||||
{
|
||||
// 사용자 루틴이 내장 루틴을 오버라이드
|
||||
foreach (var r in user)
|
||||
{
|
||||
list.RemoveAll(x => x.Name.Equals(r.Name, StringComparison.OrdinalIgnoreCase));
|
||||
list.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { LogService.Warn($"루틴 로드 실패: {ex.Message}"); }
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
internal record RoutineDefinition(
|
||||
[property: JsonPropertyName("name")] string Name,
|
||||
[property: JsonPropertyName("description")] string Description,
|
||||
[property: JsonPropertyName("steps")] RoutineStep[] Steps);
|
||||
|
||||
internal record RoutineStep(
|
||||
[property: JsonPropertyName("type")] string Type,
|
||||
[property: JsonPropertyName("target")] string Target,
|
||||
[property: JsonPropertyName("label")] string Label);
|
||||
}
|
||||
Reference in New Issue
Block a user