using System; using System.Collections.Generic; using System.Globalization; using System.Net.Http; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using AxCopilot.SDK; using AxCopilot.Services; namespace AxCopilot.Handlers; internal static class CurrencyConverter { private static readonly Regex _pattern = new Regex("^(\\d+(?:\\.\\d+)?)\\s+([A-Za-z]{3})\\s+(?:to|in)\\s+([A-Za-z]{3})$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Dictionary Rates)> _cache = new Dictionary)>(); private static readonly HttpClient _http = new HttpClient { Timeout = TimeSpan.FromSeconds(5.0) }; private static readonly TimeSpan CacheTtl = TimeSpan.FromHours(1.0); private static readonly Dictionary _names = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["KRW"] = "원", ["USD"] = "달러", ["EUR"] = "유로", ["JPY"] = "엔", ["GBP"] = "파운드", ["CNY"] = "위안", ["HKD"] = "홍콩달러", ["SGD"] = "싱가포르달러", ["CAD"] = "캐나다달러", ["AUD"] = "호주달러", ["CHF"] = "스위스프랑", ["TWD"] = "대만달러", ["MXN"] = "멕시코페소", ["BRL"] = "브라질헤알", ["INR"] = "인도루피", ["RUB"] = "루블", ["THB"] = "바트", ["VND"] = "동", ["IDR"] = "루피아", ["MYR"] = "링깃", ["PHP"] = "페소", ["NZD"] = "뉴질랜드달러", ["SEK"] = "크로나", ["NOK"] = "크로나(노르)", ["DKK"] = "크로나(덴)", ["AED"] = "디르함", ["SAR"] = "리얄" }; public static bool IsCurrencyQuery(string input) { return _pattern.IsMatch(input.Trim()); } public static async Task> ConvertAsync(string input, CancellationToken ct) { Match m = _pattern.Match(input.Trim()); if (!m.Success) { return new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("통화 형식 오류", "예: 100 USD to KRW", null, null, null, "\uea39")); } if (!double.TryParse(m.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var amount)) { return new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("숫자 형식 오류", "", null, null, null, "\uea39")); } string from = m.Groups[2].Value.ToUpperInvariant(); string to = m.Groups[3].Value.ToUpperInvariant(); try { Dictionary rates = await GetRatesAsync(from, ct); if (rates == null) { return new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("환율 조회 실패", "네트워크 연결을 확인하세요", null, null, null, "\ue7ba")); } if (!rates.TryGetValue(to, out var rate)) { return new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("지원하지 않는 통화: " + to, "3자리 ISO 4217 코드를 입력하세요", null, null, null, "\uea39")); } double result = amount * rate; string fn; string fromName = (_names.TryGetValue(from, out fn) ? fn : from); string tn; string toName = (_names.TryGetValue(to, out tn) ? tn : to); string text; switch (to) { default: text = result.ToString("N2", CultureInfo.CurrentCulture); break; case "KRW": case "JPY": case "IDR": case "VND": text = result.ToString("N0", CultureInfo.CurrentCulture); break; } string resultStr = text; string rateStr = ((rate < 0.01) ? rate.ToString("G4", CultureInfo.InvariantCulture) : rate.ToString("N4", CultureInfo.CurrentCulture)); string display = resultStr + " " + to; return new _003C_003Ez__ReadOnlyArray(new LauncherItem[2] { new LauncherItem($"{amount:N2} {from} = {display}", $"1 {from}({fromName}) = {rateStr} {to}({toName}) · Enter로 복사", null, display, null, "\ue8ef"), new LauncherItem("숫자만: " + resultStr, "Enter로 숫자만 복사", null, resultStr, null, "\ue8ef") }); } catch (OperationCanceledException) { return Array.Empty(); } catch (Exception ex2) { LogService.Warn("환율 조회 오류: " + ex2.Message); return new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("환율 조회 실패", ex2.Message, null, null, null, "\ue7ba")); } } private static async Task?> GetRatesAsync(string baseCurrency, CancellationToken ct) { if (_cache.TryGetValue(baseCurrency, out var cached) && DateTime.Now - cached.At < CacheTtl) { return cached.Rates; } string url = "https://open.er-api.com/v6/latest/" + baseCurrency; using JsonDocument doc = JsonDocument.Parse(await _http.GetStringAsync(url, ct)); JsonElement root = doc.RootElement; if (!root.TryGetProperty("result", out var resultEl) || resultEl.GetString() != "success") { return null; } if (!root.TryGetProperty("rates", out var ratesEl)) { return null; } Dictionary rates = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (JsonProperty prop in ratesEl.EnumerateObject()) { rates[prop.Name] = prop.Value.GetDouble(); } _cache[baseCurrency] = (DateTime.Now, rates); return rates; } }