using System.ComponentModel; using System.Net.Http.Headers; using System.Text.Json; namespace MAF1.Tools; public sealed class WeatherTools(WeatherOptions options, HttpClient http) { [Description("Look up live weather for a city or location. Always call this instead of guessing.")] public async Task GetWeather( [Description("City or location name, for example Amsterdam or 北京.")] string location) { if (string.IsNullOrWhiteSpace(location)) { return "未提供地点,无法查询天气。"; } string provider = options.Provider.Trim(); try { if (provider.Equals("OpenWeather", StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(options.OpenWeather.ApiKey)) { return "OpenWeather 已启用,但 appsettings.json 里 Weather:OpenWeather:ApiKey 为空。请填入密钥,或把 Provider 改回 Wttr。"; } return await QueryOpenWeatherAsync(location); } return await QueryWttrAsync(location); } catch (Exception ex) { return $"查询天气失败({provider} / {location}):{ex.Message}"; } } private async Task QueryWttrAsync(string location) { string url = Expand(options.Wttr.UrlTemplate, location, apiKey: null); using HttpResponseMessage response = await http.GetAsync(url); string body = (await response.Content.ReadAsStringAsync()).Trim(); if (!response.IsSuccessStatusCode) { return $"wttr.in 返回 {(int)response.StatusCode}:{body}"; } return string.IsNullOrWhiteSpace(body) ? $"wttr.in 没有返回 {location} 的天气。" : body; } private async Task QueryOpenWeatherAsync(string location) { string url = Expand(options.OpenWeather.UrlTemplate, location, options.OpenWeather.ApiKey); using HttpResponseMessage response = await http.GetAsync(url); string body = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { return $"OpenWeather 返回 {(int)response.StatusCode}:{body}"; } using JsonDocument doc = JsonDocument.Parse(body); JsonElement root = doc.RootElement; string name = root.TryGetProperty("name", out JsonElement nameEl) ? nameEl.GetString() ?? location : location; string description = root.TryGetProperty("weather", out JsonElement weather) && weather.ValueKind == JsonValueKind.Array && weather.GetArrayLength() > 0 && weather[0].TryGetProperty("description", out JsonElement descEl) ? descEl.GetString() ?? "" : ""; double temp = root.TryGetProperty("main", out JsonElement main) && main.TryGetProperty("temp", out JsonElement tempEl) ? tempEl.GetDouble() : double.NaN; int humidity = main.ValueKind == JsonValueKind.Object && main.TryGetProperty("humidity", out JsonElement humidityEl) ? humidityEl.GetInt32() : 0; return double.IsNaN(temp) ? body : $"{name}:{description},气温 {temp:0.#}°C,湿度 {humidity}%"; } private string Expand(string template, string location, string? apiKey) { return template .Replace("{location}", Uri.EscapeDataString(location), StringComparison.OrdinalIgnoreCase) .Replace("{lang}", Uri.EscapeDataString(options.Language), StringComparison.OrdinalIgnoreCase) .Replace("{apiKey}", apiKey ?? "", StringComparison.OrdinalIgnoreCase); } public static HttpClient CreateHttpClient() { HttpClient http = new() { Timeout = TimeSpan.FromSeconds(20) }; http.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("MAF1", "1.0")); http.DefaultRequestHeaders.AcceptLanguage.ParseAdd("zh-CN,zh;q=0.9,en;q=0.8"); return http; } }