using System.Text; using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// L20-2: 랜덤 생성기 핸들러. "rand" 프리픽스로 사용합니다. /// /// 예: rand → 사용법 목록 /// rand → 1~100 기본 난수 /// rand 50 → 1~50 난수 /// rand 10 99 → 10~99 난수 /// rand str → 랜덤 문자열 (12자, 영숫자) /// rand str 20 → 20자 랜덤 문자열 /// rand str 16 alpha → 16자 알파벳만 /// rand str 8 num → 8자 숫자만 /// rand str 12 hex → 12자 hex 문자열 /// rand str 16 special → 특수문자 포함 /// rand color → 랜덤 HEX 색상 /// rand pick 항목1 항목2 → 목록에서 무작위 선택 /// rand dice → 주사위 1d6 /// rand dice 2d6 → 2개 주사위 /// rand dice 1d20 → 20면체 주사위 /// rand coin → 동전 던지기 /// rand shuffle a b c d → 목록 셔플 /// rand uuid → UUID v4 /// rand token → 보안 토큰 (32자 hex) /// rand pin → 6자리 PIN /// rand pin 4 → 4자리 PIN /// Enter → 결과 복사. /// public class RandHandler : IActionHandler { public string? Prefix => "rand"; public PluginMetadata Metadata => new( "Rand", "랜덤 생성기 — 숫자·문자열·색상·주사위·UUID·토큰·PIN·셔플", "1.0", "AX"); private static readonly Random _rng = Random.Shared; public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var items = new List(); if (string.IsNullOrWhiteSpace(q)) { // 기본: 1~100 난수 + 사용법 var n = _rng.Next(1, 101); items.Add(new LauncherItem($"{n}", "1~100 랜덤 숫자 · Enter 복사", null, ("copy", n.ToString()), Symbol: "\uE8D0")); items.Add(new LauncherItem("사용법", "rand / rand / rand str / rand color / rand dice / rand pick …", null, null, Symbol: "\uE8D0")); return Task.FromResult>(items); } var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries); var sub = parts[0].ToLowerInvariant(); switch (sub) { // rand str [length] [charset] case "str" or "string" or "문자열": { int len = 12; if (parts.Length >= 2 && int.TryParse(parts[1], out var l)) len = Math.Clamp(l, 1, 256); var charset = parts.Length >= 3 ? parts[2].ToLowerInvariant() : "alnum"; if (parts.Length == 2 && !int.TryParse(parts[1], out _)) charset = parts[1].ToLowerInvariant(); var cs = GetCharset(charset); if (string.IsNullOrEmpty(cs)) { items.Add(ErrorItem("charset: alpha/num/alnum/hex/special")); break; } var result = RandStr(len, cs); items.Add(new LauncherItem(result, $"{len}자 랜덤 문자열 ({charset}) · Enter 복사", null, ("copy", result), Symbol: "\uE8D0")); // 다른 자릿수 미리 생성 foreach (var altLen in new[] { 8, 16, 32 }.Where(x => x != len)) items.Add(new LauncherItem(RandStr(altLen, cs), $"{altLen}자 ({charset})", null, ("copy", RandStr(altLen, cs)), Symbol: "\uE8D0")); break; } // rand color case "color" or "색상" or "colour": { for (int i = 0; i < 5; i++) { var r = _rng.Next(256); var g = _rng.Next(256); var b = _rng.Next(256); var hex = $"#{r:X2}{g:X2}{b:X2}"; var rgb = $"rgb({r}, {g}, {b})"; var hsl = RgbToHsl(r, g, b); items.Add(new LauncherItem(hex, $"{rgb} {hsl} · Enter 복사", null, ("copy", hex), Symbol: "\uE8D0")); } break; } // rand dice [NdS] case "dice" or "주사위": { var spec = parts.Length >= 2 ? parts[1].ToLowerInvariant() : "1d6"; if (!TryParseDice(spec, out var dCount, out var dSides)) { items.Add(ErrorItem("형식: 1d6 / 2d10 / 1d20")); break; } var rolls = Enumerable.Range(0, dCount).Select(_ => _rng.Next(1, dSides + 1)).ToList(); var total = rolls.Sum(); var detail = string.Join(" + ", rolls); items.Add(new LauncherItem($"{dCount}d{dSides} → {total}", $"각 주사위: {detail} · Enter 복사", null, ("copy", total.ToString()), Symbol: "\uE8D0")); if (dCount > 1) { items.Add(CopyItem("합계", total.ToString())); items.Add(CopyItem("상세", detail)); items.Add(CopyItem("최솟값", rolls.Min().ToString())); items.Add(CopyItem("최댓값", rolls.Max().ToString())); } // 다시 굴리기 미리보기 items.Add(new LauncherItem("── 다시 굴리기 (미리보기) ──", "", null, null, Symbol: "\uE8D0")); for (int i = 0; i < 3; i++) { var r2 = Enumerable.Range(0, dCount).Select(_ => _rng.Next(1, dSides + 1)).ToList(); items.Add(new LauncherItem($"{r2.Sum()}", string.Join(" + ", r2), null, ("copy", r2.Sum().ToString()), Symbol: "\uE8D0")); } break; } // rand coin case "coin" or "동전": { var result = _rng.Next(2) == 0 ? "앞면 (Head)" : "뒷면 (Tail)"; items.Add(new LauncherItem(result, "동전 던지기 · Enter 복사", null, ("copy", result), Symbol: "\uE8D0")); // 연속 5회 items.Add(new LauncherItem("── 연속 5회 ──", "", null, null, Symbol: "\uE8D0")); for (int i = 0; i < 5; i++) items.Add(new LauncherItem(_rng.Next(2) == 0 ? "앞면 ⬤" : "뒷면 ○", $"#{i + 1}", null, null, Symbol: "\uE8D0")); break; } // rand pick item1 item2 ... case "pick" or "선택": { if (parts.Length < 2) { items.Add(ErrorItem("예: rand pick 치킨 피자 짜장면")); break; } var pool = parts[1..]; var picked = pool[_rng.Next(pool.Length)]; items.Add(new LauncherItem(picked, $"{pool.Length}개 중 선택 · Enter 복사", null, ("copy", picked), Symbol: "\uE8D0")); items.Add(new LauncherItem("── 다시 선택 ──", "", null, null, Symbol: "\uE8D0")); for (int i = 0; i < Math.Min(3, pool.Length); i++) items.Add(new LauncherItem(pool[_rng.Next(pool.Length)], $"후보 {i + 1}", null, null, Symbol: "\uE8D0")); break; } // rand shuffle item1 item2 ... case "shuffle" or "섞기": { if (parts.Length < 2) { items.Add(ErrorItem("예: rand shuffle a b c d e")); break; } var list = parts[1..].ToList(); for (int i = list.Count - 1; i > 0; i--) { var j = _rng.Next(i + 1); (list[i], list[j]) = (list[j], list[i]); } var joined = string.Join(" ", list); items.Add(new LauncherItem(joined, $"{list.Count}개 셔플 · Enter 복사", null, ("copy", joined), Symbol: "\uE8D0")); items.Add(CopyItem("쉼표 구분", string.Join(", ", list))); for (int r = 0; r < list.Count; r++) items.Add(new LauncherItem($"#{r + 1} {list[r]}", "", null, null, Symbol: "\uE8D0")); break; } // rand uuid case "uuid": { for (int i = 0; i < 5; i++) { var guid = Guid.NewGuid().ToString(); items.Add(new LauncherItem(guid, $"UUID v4 · Enter 복사", null, ("copy", guid), Symbol: "\uE8D0")); } break; } // rand token case "token" or "secret" or "key": { int tLen = 32; if (parts.Length >= 2 && int.TryParse(parts[1], out var tl)) tLen = Math.Clamp(tl, 8, 128); var tokenBytes = new byte[tLen]; System.Security.Cryptography.RandomNumberGenerator.Fill(tokenBytes); var tokenHex = BitConverter.ToString(tokenBytes).Replace("-", "").ToLowerInvariant(); var tokenB64 = Convert.ToBase64String(tokenBytes); items.Add(new LauncherItem(tokenHex, $"{tLen}바이트 보안 토큰 (hex) · Enter 복사", null, ("copy", tokenHex), Symbol: "\uE8D0")); items.Add(CopyItem("Hex", tokenHex)); items.Add(CopyItem("Base64", tokenB64)); break; } // rand pin [length] case "pin": { int pLen = 6; if (parts.Length >= 2 && int.TryParse(parts[1], out var pl)) pLen = Math.Clamp(pl, 4, 12); var pin = string.Concat(Enumerable.Range(0, pLen).Select(_ => _rng.Next(10))); items.Add(new LauncherItem(pin, $"{pLen}자리 PIN · Enter 복사", null, ("copy", pin), Symbol: "\uE8D0")); items.Add(new LauncherItem("── 추가 PIN ──", "", null, null, Symbol: "\uE8D0")); for (int i = 0; i < 4; i++) items.Add(new LauncherItem( string.Concat(Enumerable.Range(0, pLen).Select(_ => _rng.Next(10))), $"{pLen}자리", null, null, Symbol: "\uE8D0")); break; } default: { // rand 또는 rand if (int.TryParse(sub, out var max)) { int min = 1; if (parts.Length >= 2 && int.TryParse(parts[1], out var mx)) { min = max; max = mx; } if (min > max) (min, max) = (max, min); var n = _rng.Next(min, max + 1); items.Add(new LauncherItem($"{n}", $"{min}~{max} 랜덤 숫자 · Enter 복사", null, ("copy", n.ToString()), Symbol: "\uE8D0")); items.Add(new LauncherItem("── 추가 결과 ──", "", null, null, Symbol: "\uE8D0")); for (int i = 0; i < 4; i++) items.Add(new LauncherItem(_rng.Next(min, max + 1).ToString(), $"{min}~{max}", null, null, Symbol: "\uE8D0")); } else { items.Add(new LauncherItem($"알 수 없는 서브커맨드: '{sub}'", "rand / rand / rand str / rand color / rand dice / rand pick / rand uuid / rand token / rand pin", null, null, Symbol: "\uE783")); } break; } } 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("Rand", "클립보드에 복사했습니다."); } catch { } } return Task.CompletedTask; } // ── 헬퍼 ──────────────────────────────────────────────────────────────── private static string GetCharset(string name) => name switch { "alpha" or "알파" => "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "num" or "숫자" => "0123456789", "alnum" or "영숫자" => "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "hex" => "0123456789abcdef", "lower" => "abcdefghijklmnopqrstuvwxyz", "upper" => "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "special" => "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*", _ => "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" }; private static string RandStr(int len, string charset) { var sb = new StringBuilder(len); for (int i = 0; i < len; i++) sb.Append(charset[_rng.Next(charset.Length)]); return sb.ToString(); } private static bool TryParseDice(string s, out int count, out int sides) { count = sides = 0; var d = s.IndexOf('d'); if (d < 0) return false; var left = d == 0 ? "1" : s[..d]; var right = s[(d + 1)..]; return int.TryParse(left, out count) && count >= 1 && count <= 100 && int.TryParse(right, out sides) && sides >= 2 && sides <= 10000; } private static string RgbToHsl(int r, int g, int b) { var rf = r / 255.0; var gf = g / 255.0; var bf = b / 255.0; var max = Math.Max(rf, Math.Max(gf, bf)); var min = Math.Min(rf, Math.Min(gf, bf)); var l = (max + min) / 2; if (max == min) return $"hsl(0, 0%, {l:P0})"; var d = max - min; var s = l > 0.5 ? d / (2 - max - min) : d / (max + min); var h = max == rf ? (gf - bf) / d + (gf < bf ? 6 : 0) : max == gf ? (bf - rf) / d + 2 : (rf - gf) / d + 4; h /= 6; return $"hsl({h * 360:F0}, {s:P0}, {l:P0})"; } private static LauncherItem CopyItem(string label, string value) => new(label, value, null, ("copy", value), Symbol: "\uE8D0"); private static LauncherItem ErrorItem(string msg) => new(msg, "올바른 입력 형식을 확인하세요", null, null, Symbol: "\uE783"); }