151 lines
5.2 KiB
C#
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;
|
|
}
|
|
}
|