using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using Wox.Plugin; namespace Wox.Plugin.Weather { public class Main : IPlugin { private PluginInitContext _context; private const string API_KEY = "f4aeaa37939b47aa864a6e1b3ad4a084"; private const string API_HOST = "nm6hewkf7w.re.qweatherapi.com"; public void Init(PluginInitContext context) { _context = context; LogInfo("Weather Plugin initialized with built-in API key"); } public List Query(Query query) { List resultList = new List(); try { string cityName; if (string.IsNullOrEmpty(query.Search)) { // 如果没有输入城市名称,通过IP获取当前位置 cityName = GetCityFromIP(); if (string.IsNullOrEmpty(cityName)) { cityName = "北京"; // 如果IP定位失败,使用默认城市 LogInfo("IP定位失败,使用默认城市:北京"); } else { LogInfo(string.Format("通过IP定位获取到城市:{0}", cityName)); } } else { cityName = query.Search; } LogInfo(string.Format("Weather query started for city: {0}", cityName)); var weatherData = GetWeatherData(cityName); if (weatherData != null) { LogInfo(string.Format("Weather data retrieved successfully for {0}: {1}℃, {2}", cityName, weatherData.Temperature, weatherData.Description)); resultList.Add(new Result { Title = string.Format("{0}, 实时: {1}℃, {2}", weatherData.City, weatherData.Temperature, weatherData.Description), SubTitle = string.Format("湿度: {0}%, 风速: {1}km/h", weatherData.Humidity, weatherData.WindSpeed), IcoPath = string.Format("Images\\weatherpic\\{0}.png", weatherData.IconCode), Action = e => false }); foreach (var forecast in weatherData.Forecasts) { string dayPrefix = GetDayPrefix(forecast.Date); resultList.Add(new Result { Title = string.Format("{0}, {1}-{2}℃", forecast.Description, forecast.MaxTemp, forecast.MinTemp), SubTitle = string.Format("{0}{1}, {2}", dayPrefix, GetWeekDay(forecast.Date.DayOfWeek), forecast.Date.ToShortDateString()), IcoPath = string.Format("Images\\weatherpic\\{0}.png", forecast.IconCode), Action = e => false }); } } else { LogError(string.Format("Failed to retrieve weather data for city: {0}", cityName)); resultList.Add(new Result { Title = string.Format("无法获取 {0} 的天气信息", cityName), SubTitle = "请检查城市名称是否正确,或稍后重试", IcoPath = "Images\\logo.png", Action = e => false }); } } catch (Exception ex) { LogError(string.Format("Exception in Query method: {0}", ex.Message)); resultList.Add(new Result { Title = "请输入城市名称或者拼音", SubTitle = "API调用失败,请稍后重试", IcoPath = "Images\\logo.png", Action = e => false }); } return resultList; } private WeatherData GetWeatherData(string cityName) { try { LogInfo(string.Format("Getting location ID for city: {0}", cityName)); string locationId = GetLocationId(cityName, API_KEY); if (string.IsNullOrEmpty(locationId)) { LogError(string.Format("Failed to get location ID for city: {0}", cityName)); return null; } LogInfo(string.Format("Location ID found: {0} for city: {1}", locationId, cityName)); string currentWeatherUrl = string.Format("https://{0}/v7/weather/now?location={1}&key={2}", API_HOST, locationId, API_KEY); LogInfo(string.Format("Requesting current weather from: {0}", currentWeatherUrl.Replace(API_KEY, "***"))); string currentResponse = MakeHttpRequest(currentWeatherUrl); string forecastUrl = string.Format("https://{0}/v7/weather/7d?location={1}&key={2}", API_HOST, locationId, API_KEY); LogInfo(string.Format("Requesting forecast from: {0}", forecastUrl.Replace(API_KEY, "***"))); string forecastResponse = MakeHttpRequest(forecastUrl); if (!string.IsNullOrEmpty(currentResponse) && !string.IsNullOrEmpty(forecastResponse)) { LogInfo(string.Format("API responses received - Current: {0} chars, Forecast: {1} chars", currentResponse.Length, forecastResponse.Length)); // 检查当前天气响应是否为有效JSON if (!currentResponse.TrimStart().StartsWith("{") && !currentResponse.TrimStart().StartsWith("[")) { LogError(string.Format("Current weather response is not valid JSON. First 200 chars: {0}", currentResponse.Length > 200 ? currentResponse.Substring(0, 200) : currentResponse)); return null; } // 检查预报响应是否为有效JSON if (!forecastResponse.TrimStart().StartsWith("{") && !forecastResponse.TrimStart().StartsWith("[")) { LogError(string.Format("Forecast response is not valid JSON. First 200 chars: {0}", forecastResponse.Length > 200 ? forecastResponse.Substring(0, 200) : forecastResponse)); return null; } var currentJson = JObject.Parse(currentResponse); var forecastJson = JObject.Parse(forecastResponse); string currentCode = (string)currentJson["code"]; string forecastCode = (string)forecastJson["code"]; LogInfo(string.Format("API response codes - Current: {0}, Forecast: {1}", currentCode, forecastCode)); if (currentCode != "200" || forecastCode != "200") { LogError(string.Format("API returned error codes - Current: {0}, Forecast: {1}", currentCode, forecastCode)); return null; } var current = currentJson["now"]; var dailyForecasts = forecastJson["daily"]; var weatherData = new WeatherData(); weatherData.City = cityName; weatherData.Temperature = (int)current["temp"]; weatherData.Description = (string)current["text"]; weatherData.Humidity = (int)current["humidity"]; weatherData.WindSpeed = (int)current["windSpeed"]; weatherData.IconCode = GetHeFengIconCode((string)current["icon"]); foreach (var day in dailyForecasts.Take(5)) { var forecast = new ForecastData(); forecast.Date = DateTime.Parse((string)day["fxDate"]); forecast.MaxTemp = (int)day["tempMax"]; forecast.MinTemp = (int)day["tempMin"]; forecast.Description = (string)day["textDay"]; forecast.IconCode = GetHeFengIconCode((string)day["iconDay"]); weatherData.Forecasts.Add(forecast); } return weatherData; } else { LogError("Empty response received from weather API"); } } catch (Exception ex) { LogError(string.Format("Exception in GetWeatherData: {0}", ex.Message)); } return null; } private string GetLocationId(string cityName, string apiKey) { try { // 根据公告,geoapi需要特殊处理:域名替换为API Host,路径从v2改为geo/v2 string searchUrl = string.Format("https://{0}/geo/v2/city/lookup?location={1}&key={2}", API_HOST, Uri.EscapeDataString(cityName), apiKey); LogInfo(string.Format("Requesting location ID from: {0}", searchUrl.Replace(apiKey, "***"))); string response = MakeHttpRequest(searchUrl); if (!string.IsNullOrEmpty(response)) { LogInfo(string.Format("Location search response: {0} chars", response.Length)); // 记录响应内容的前100个字符用于调试 string responsePreview = response.Length > 100 ? response.Substring(0, 100) + "..." : response; LogInfo(string.Format("Response preview: {0}", responsePreview)); // 检查响应是否看起来像JSON if (!response.TrimStart().StartsWith("{") && !response.TrimStart().StartsWith("[")) { LogError(string.Format("Response does not appear to be JSON. First 200 chars: {0}", response.Length > 200 ? response.Substring(0, 200) : response)); return null; } var json = JObject.Parse(response); string code = (string)json["code"]; LogInfo(string.Format("Location search response code: {0}", code)); if (code == "200" && json["location"] != null && json["location"].HasValues) { string locationId = (string)json["location"][0]["id"]; LogInfo(string.Format("Location ID found: {0}", locationId)); return locationId; } else { LogError(string.Format("Location search failed - Code: {0}, HasLocation: {1}", code, json["location"] != null && json["location"].HasValues)); } } else { LogError("Empty response from location search API"); } } catch (Exception ex) { LogError(string.Format("Exception in GetLocationId: {0}", ex.Message)); } return null; } private string GetCityFromIP() { try { LogInfo("开始通过IP获取城市位置"); // 使用免费的ip-api.com服务获取IP位置信息 string ipApiUrl = "http://ip-api.com/json/?lang=zh-CN&fields=status,message,country,regionName,city"; LogInfo(string.Format("请求IP定位API: {0}", ipApiUrl)); string response = MakeHttpRequest(ipApiUrl); if (!string.IsNullOrEmpty(response)) { LogInfo(string.Format("IP定位API响应: {0} chars", response.Length)); var json = JObject.Parse(response); string status = (string)json["status"]; if (status == "success") { string country = (string)json["country"]; string region = (string)json["regionName"]; string city = (string)json["city"]; LogInfo(string.Format("IP定位成功 - 国家: {0}, 省份: {1}, 城市: {2}", country, region, city)); // 优先返回城市,如果没有城市则返回省份 if (!string.IsNullOrEmpty(city)) { return city; } else if (!string.IsNullOrEmpty(region)) { return region; } else { LogError("IP定位成功但未获取到有效的城市或省份信息"); return null; } } else { string message = (string)json["message"]; LogError(string.Format("IP定位失败 - Status: {0}, Message: {1}", status, message)); return null; } } else { LogError("IP定位API返回空响应"); return null; } } catch (Exception ex) { LogError(string.Format("IP定位异常: {0}", ex.Message)); return null; } } private string MakeHttpRequest(string url) { try { LogInfo(string.Format("Making HTTP request to: {0}", url.Replace(API_KEY, "***"))); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "GET"; request.Timeout = 10000; // 增加超时时间到10秒 request.UserAgent = "Wox Weather Plugin/1.2.0"; // 明确设置接受的编码和内容类型 request.Accept = "application/json, text/plain, */*"; request.Headers.Add("Accept-Encoding", "gzip, deflate"); request.Headers.Add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"); // 自动解压缩响应 request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { LogInfo(string.Format("HTTP response received - Status: {0}, ContentLength: {1}, ContentEncoding: {2}, ContentType: {3}", response.StatusCode, response.ContentLength, response.ContentEncoding, response.ContentType)); using (Stream responseStream = response.GetResponseStream()) { // 根据响应头确定编码 Encoding encoding = Encoding.UTF8; if (!string.IsNullOrEmpty(response.CharacterSet)) { try { encoding = Encoding.GetEncoding(response.CharacterSet); LogInfo(string.Format("Using encoding from response header: {0}", response.CharacterSet)); } catch { LogInfo("Failed to parse charset from response, using UTF-8"); encoding = Encoding.UTF8; } } using (StreamReader reader = new StreamReader(responseStream, encoding)) { string result = reader.ReadToEnd(); LogInfo(string.Format("HTTP response content length: {0} characters", result.Length)); // 记录响应的前200个字符用于调试 string debugPreview = result.Length > 200 ? result.Substring(0, 200) + "..." : result; LogInfo(string.Format("Response content preview: {0}", debugPreview)); return result; } } } } catch (Exception ex) { LogError(string.Format("HTTP request failed for URL {0}: {1}", url.Replace(API_KEY, "***"), ex.Message)); return null; } } private string GetHeFengIconCode(string heFengIcon) { var codeMap = new Dictionary(); codeMap.Add("100", "100"); codeMap.Add("150", "100"); codeMap.Add("101", "101"); codeMap.Add("102", "102"); codeMap.Add("103", "103"); codeMap.Add("151", "101"); codeMap.Add("152", "102"); codeMap.Add("153", "103"); codeMap.Add("104", "104"); codeMap.Add("300", "300"); codeMap.Add("301", "301"); codeMap.Add("302", "302"); codeMap.Add("303", "303"); codeMap.Add("304", "304"); codeMap.Add("305", "305"); codeMap.Add("306", "306"); codeMap.Add("307", "307"); codeMap.Add("308", "308"); codeMap.Add("309", "309"); codeMap.Add("310", "310"); codeMap.Add("400", "400"); codeMap.Add("401", "401"); codeMap.Add("402", "402"); codeMap.Add("403", "403"); codeMap.Add("404", "404"); codeMap.Add("500", "501"); return codeMap.ContainsKey(heFengIcon) ? codeMap[heFengIcon] : "999"; } private string GetDayPrefix(DateTime date) { if (date.Date == DateTime.Today) return "今天, "; else if (date.Date == DateTime.Today.AddDays(1)) return "明天, "; else if (date.Date == DateTime.Today.AddDays(2)) return "后天, "; else return ""; } private string GetWeekDay(DayOfWeek dayOfWeek) { string[] weekDays = { "周日", "周一", "周二", "周三", "周四", "周五", "周六" }; return weekDays[(int)dayOfWeek]; } private void LogInfo(string message) { try { WriteLog("INFO", message); } catch { // 如果日志记录失败,忽略错误以避免影响主要功能 } } private void LogError(string message) { try { WriteLog("ERROR", message); } catch { // 如果日志记录失败,忽略错误以避免影响主要功能 } } private void WriteLog(string level, string message) { try { string logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Wox", "Logs"); if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir); string logFile = Path.Combine(logDir, "Weather.Plugin.log"); string logEntry = string.Format("[{0}] [{1}] {2}: {3}{4}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), level, "Weather Plugin", message, Environment.NewLine); File.AppendAllText(logFile, logEntry); } catch { // 如果日志记录失败,忽略错误以避免影响主要功能 } } } public class WeatherData { public WeatherData() { Forecasts = new List(); } public string City { get; set; } public int Temperature { get; set; } public string Description { get; set; } public int Humidity { get; set; } public int WindSpeed { get; set; } public string IconCode { get; set; } public List Forecasts { get; set; } } public class ForecastData { public DateTime Date { get; set; } public int MaxTemp { get; set; } public int MinTemp { get; set; } public string Description { get; set; } public string IconCode { get; set; } } }