init weather plugin

This commit is contained in:
2025-06-25 13:55:23 +08:00
committed by wanwenshan
commit a23f0b55b4
9 changed files with 661 additions and 0 deletions

456
Main.cs Normal file
View File

@ -0,0 +1,456 @@
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<Result> Query(Query query)
{
List<Result> resultList = new List<Result>();
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<string, string>();
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<ForecastData>(); }
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<ForecastData> 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; }
}
}