using System.Security.Cryptography; using System.Text; using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// L10-2: UUID/GUID 생성기 핸들러. "uuid" 프리픽스로 사용합니다. /// /// 예: uuid → UUID v4 1개 생성 /// uuid 5 → UUID v4 5개 생성 /// uuid upper → 대문자 UUID 생성 /// uuid v4 → UUID v4 (랜덤) /// uuid seq → 순차 UUID (시간 기반, 정렬 가능) /// uuid short → 짧은 고유 ID (8자리 hex) /// uuid nil → Nil UUID (00000000-…) /// uuid parse → UUID 분석 (버전, 타임스탬프 등) /// Enter → 결과를 클립보드에 복사. /// public class UuidHandler : IActionHandler { public string? Prefix => "uuid"; public PluginMetadata Metadata => new( "UUID", "UUID/GUID 생성기 — v4 · 순차 · 짧은 ID · 분석", "1.0", "AX"); public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var items = new List(); if (string.IsNullOrWhiteSpace(q)) { // 기본: v4 1개 생성 var uuid = Guid.NewGuid().ToString(); items.Add(new LauncherItem( uuid, "UUID v4 (랜덤) · Enter 복사", null, ("copy", uuid), Symbol: "\uF0E2")); items.Add(new LauncherItem("uuid 5", "5개 생성", null, null, Symbol: "\uF0E2")); items.Add(new LauncherItem("uuid upper", "대문자 UUID", null, null, Symbol: "\uF0E2")); items.Add(new LauncherItem("uuid seq", "순차 UUID (정렬가능)", null, null, Symbol: "\uF0E2")); items.Add(new LauncherItem("uuid short", "짧은 ID (8자리)", null, null, Symbol: "\uF0E2")); items.Add(new LauncherItem("uuid parse …", "UUID 분석", null, null, Symbol: "\uF0E2")); return Task.FromResult>(items); } var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries); var sub = parts[0].ToLowerInvariant(); // "uuid parse " if (sub == "parse" && parts.Length >= 2) { items.AddRange(ParseUuid(string.Join(" ", parts.Skip(1)))); return Task.FromResult>(items); } // "uuid nil" if (sub == "nil") { var nil = "00000000-0000-0000-0000-000000000000"; items.Add(new LauncherItem(nil, "Nil UUID", null, ("copy", nil), Symbol: "\uF0E2")); return Task.FromResult>(items); } // "uuid seq" if (sub == "seq") { items.AddRange(GenerateSequential(5)); return Task.FromResult>(items); } // "uuid short" if (sub == "short") { items.AddRange(GenerateShort(5)); return Task.FromResult>(items); } // "uuid upper" if (sub == "upper") { var upper = Guid.NewGuid().ToString().ToUpperInvariant(); items.Add(new LauncherItem(upper, "대문자 UUID v4 · Enter 복사", null, ("copy", upper), Symbol: "\uF0E2")); for (var i = 0; i < 4; i++) { var u = Guid.NewGuid().ToString().ToUpperInvariant(); items.Add(new LauncherItem(u, "대문자 UUID v4", null, ("copy", u), Symbol: "\uF0E2")); } return Task.FromResult>(items); } // "uuid v4" if (sub == "v4") { items.AddRange(GenerateV4(5)); return Task.FromResult>(items); } // "uuid <숫자>" — N개 생성 if (int.TryParse(sub, out var count)) { count = Math.Clamp(count, 1, 20); items.AddRange(GenerateV4(count)); return Task.FromResult>(items); } // UUID 자체를 입력한 경우 → parse if (Guid.TryParse(q, out _)) { items.AddRange(ParseUuid(q)); return Task.FromResult>(items); } items.Add(new LauncherItem("알 수 없는 명령", "예: uuid / uuid 5 / uuid upper / uuid seq / uuid short / uuid parse", null, null, Symbol: "\uE783")); return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is ("copy", string text)) { try { System.Windows.Application.Current.Dispatcher.Invoke( () => Clipboard.SetText(text)); NotificationService.Notify("UUID", "클립보드에 복사했습니다."); } catch { /* 비핵심 */ } } return Task.CompletedTask; } // ── 생성 헬퍼 ───────────────────────────────────────────────────────────── private static IEnumerable GenerateV4(int count) { var all = new List(); for (var i = 0; i < count; i++) { var uuid = Guid.NewGuid().ToString(); all.Add(uuid); } if (count > 1) { yield return new LauncherItem( $"UUID v4 {count}개", "전체 복사: Enter", null, ("copy", string.Join("\n", all)), Symbol: "\uF0E2"); } foreach (var uuid in all) yield return new LauncherItem(uuid, "UUID v4 · Enter 복사", null, ("copy", uuid), Symbol: "\uF0E2"); } /// /// 시간 기반 순차 UUID (UUIDv7 스타일: 밀리초 타임스탬프 + 랜덤 비트). /// 정렬 가능하고 시간 정보 포함. /// private static IEnumerable GenerateSequential(int count) { var all = new List(); for (var i = 0; i < count; i++) { if (i > 0) System.Threading.Thread.Sleep(1); // 밀리초 차이 보장 var uuid = NewSequentialGuid(); all.Add(uuid); } yield return new LauncherItem( $"순차 UUID {count}개", "시간 기반 정렬 가능 · 전체 복사: Enter", null, ("copy", string.Join("\n", all)), Symbol: "\uF0E2"); foreach (var uuid in all) yield return new LauncherItem(uuid, "순차 UUID · Enter 복사", null, ("copy", uuid), Symbol: "\uF0E2"); } private static IEnumerable GenerateShort(int count) { var all = new List(); for (var i = 0; i < count; i++) all.Add(NewShortId()); yield return new LauncherItem( $"짧은 ID {count}개", "8자리 hex · 전체 복사: Enter", null, ("copy", string.Join("\n", all)), Symbol: "\uF0E2"); foreach (var id in all) yield return new LauncherItem(id, "짧은 ID (8자리) · Enter 복사", null, ("copy", id), Symbol: "\uF0E2"); } private static IEnumerable ParseUuid(string raw) { raw = raw.Trim(); if (!Guid.TryParse(raw, out var guid)) { yield return new LauncherItem("UUID 파싱 실패", $"'{raw}'은 유효한 UUID가 아닙니다", null, null, Symbol: "\uE783"); yield break; } var bytes = guid.ToByteArray(); var version = (bytes[7] >> 4) & 0x0F; var variant = (bytes[8] >> 6) & 0x03; yield return new LauncherItem( guid.ToString(), $"버전 {version} · 변형 {variant}", null, ("copy", guid.ToString()), Symbol: "\uF0E2"); yield return new LauncherItem("소문자", guid.ToString(), null, ("copy", guid.ToString()), Symbol: "\uF0E2"); yield return new LauncherItem("대문자", guid.ToString().ToUpper(), null, ("copy", guid.ToString().ToUpper()), Symbol: "\uF0E2"); yield return new LauncherItem("중괄호", $"{{{guid}}}", null, ("copy", $"{{{guid}}}"), Symbol: "\uF0E2"); yield return new LauncherItem("대시 없음", guid.ToString("N"), null, ("copy", guid.ToString("N")), Symbol: "\uF0E2"); yield return new LauncherItem("버전", $"UUID v{version}", null, null, Symbol: "\uF0E2"); yield return new LauncherItem("변형", variant == 2 ? "RFC 4122" : variant == 3 ? "Microsoft" : $"변형 {variant}", null, null, Symbol: "\uF0E2"); // 시간 기반 버전 (v1)이면 타임스탬프 복원 시도 if (version == 1) { var ts = ExtractV1Timestamp(bytes); if (ts.HasValue) yield return new LauncherItem("타임스탬프", ts.Value.ToString("yyyy-MM-dd HH:mm:ss.fff UTC"), null, null, Symbol: "\uF0E2"); } } // ── UUID 생성 구현 ──────────────────────────────────────────────────────── /// UUIDv7 스타일 순차 GUID: 상위 48비트 = Unix ms 타임스탬프, 하위 = 랜덤 private static string NewSequentialGuid() { var ms = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var rand = RandomNumberGenerator.GetBytes(10); var bytes = new byte[16]; // 상위 6바이트 = 타임스탬프 (big-endian ms) bytes[0] = (byte)(ms >> 40); bytes[1] = (byte)(ms >> 32); bytes[2] = (byte)(ms >> 24); bytes[3] = (byte)(ms >> 16); bytes[4] = (byte)(ms >> 8); bytes[5] = (byte)(ms); // 버전 비트 (v7 = 0111) bytes[6] = (byte)((rand[0] & 0x0F) | 0x70); bytes[7] = rand[1]; // 변형 비트 (RFC 4122 = 10xx) bytes[8] = (byte)((rand[2] & 0x3F) | 0x80); bytes[9] = rand[3]; // 나머지 랜덤 Array.Copy(rand, 4, bytes, 10, 6); return new Guid(bytes).ToString(); } private static string NewShortId() { var bytes = RandomNumberGenerator.GetBytes(4); return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant(); } private static DateTime? ExtractV1Timestamp(byte[] bytes) { try { // UUID v1: time_low(4) + time_mid(2) + time_hi_version(2) var timeLow = (long)((uint)((bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0])); var timeMid = (long)((ushort)((bytes[5] << 8) | bytes[4])); var timeHigh = (long)((ushort)((bytes[7] << 8) | bytes[6]) & 0x0FFF); var ticks = (timeHigh << 48) | (timeMid << 32) | timeLow; // UUID epoch = Oct 15, 1582 var uuidEpoch = new DateTime(1582, 10, 15, 0, 0, 0, DateTimeKind.Utc); return uuidEpoch.AddTicks(ticks); } catch { return null; } } }