Files
AX-Copilot-Codex/.decompiledproj/AxCopilot/Handlers/CurrencyConverter.cs

151 lines
5.2 KiB
C#

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<string, (DateTime At, Dictionary<string, double> Rates)> _cache = new Dictionary<string, (DateTime, Dictionary<string, double>)>();
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<string, string> _names = new Dictionary<string, string>(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<IEnumerable<LauncherItem>> ConvertAsync(string input, CancellationToken ct)
{
Match m = _pattern.Match(input.Trim());
if (!m.Success)
{
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(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<LauncherItem>(new LauncherItem("숫자 형식 오류", "", null, null, null, "\uea39"));
}
string from = m.Groups[2].Value.ToUpperInvariant();
string to = m.Groups[3].Value.ToUpperInvariant();
try
{
Dictionary<string, double> rates = await GetRatesAsync(from, ct);
if (rates == null)
{
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("환율 조회 실패", "네트워크 연결을 확인하세요", null, null, null, "\ue7ba"));
}
if (!rates.TryGetValue(to, out var rate))
{
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(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<LauncherItem>(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<LauncherItem>();
}
catch (Exception ex2)
{
LogService.Warn("환율 조회 오류: " + ex2.Message);
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("환율 조회 실패", ex2.Message, null, null, null, "\ue7ba"));
}
}
private static async Task<Dictionary<string, double>?> 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<string, double> rates = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
foreach (JsonProperty prop in ratesEl.EnumerateObject())
{
rates[prop.Name] = prop.Value.GetDouble();
}
_cache[baseCurrency] = (DateTime.Now, rates);
return rates;
}
}