重构: 拆分main.go为5个文件 + 壁纸HTML移至web目录
- main.go: 入口+单实例互斥锁 - win32.go: Win32 API/embed/evalJS/全局变量 - config.go: 配置读写+图标生成 - weather.go: 天气API/定位/天气循环 - systray.go: 托盘菜单/WebView/全屏监控 - wallpaper.html → web/wallpaper.html
This commit is contained in:
306
weather.go
Normal file
306
weather.go
Normal file
@@ -0,0 +1,306 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const qweatherKey = "3b67b65a53c04170b602d2d1a7e6096f"
|
||||
const qweatherHost = "https://pb4nmv4qnu.re.qweatherapi.com"
|
||||
|
||||
type City struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Adm2 string `json:"adm2"`
|
||||
Adm1 string `json:"adm1"`
|
||||
}
|
||||
|
||||
var cities = []City{
|
||||
{"101010100", "北京", "北京", "北京"},
|
||||
{"101020100", "上海", "上海", "上海"},
|
||||
{"101280101", "广州", "广州", "广东"},
|
||||
{"101280601", "深圳", "深圳", "广东"},
|
||||
{"101200101", "武汉", "武汉", "湖北"},
|
||||
{"101110101", "西安", "西安", "陕西"},
|
||||
{"101210101", "杭州", "杭州", "浙江"},
|
||||
{"101190101", "南京", "南京", "江苏"},
|
||||
{"101070101", "沈阳", "沈阳", "辽宁"},
|
||||
{"101050101", "哈尔滨", "哈尔滨", "黑龙江"},
|
||||
{"101250101", "长沙", "长沙", "湖南"},
|
||||
{"101270101", "成都", "成都", "四川"},
|
||||
}
|
||||
|
||||
var defaultCity = City{"101200101", "武汉", "武汉", "湖北"}
|
||||
|
||||
var httpClient = &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
var weatherIcons = map[string]string{
|
||||
"晴": "☀️", "多云": "⛅", "阴": "☁️",
|
||||
"雨": "🌧️", "雪": "❄️", "雷": "⛈️",
|
||||
"雾": "🌫️", "霾": "😷", "风": "💨",
|
||||
}
|
||||
|
||||
func getWeatherIcon(text string) string {
|
||||
for k, v := range weatherIcons {
|
||||
if strings.Contains(text, k) {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return "🌤️"
|
||||
}
|
||||
|
||||
func httpGet(url string) ([]byte, error) {
|
||||
resp, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
// ── 定位 ──
|
||||
|
||||
func getLocation() City {
|
||||
if city := locateByIPIP(); city != nil {
|
||||
return *city
|
||||
}
|
||||
if city := locateByQWeather(); city != nil {
|
||||
return *city
|
||||
}
|
||||
log.Println("所有定位失败,使用默认城市:", defaultCity.Name)
|
||||
return defaultCity
|
||||
}
|
||||
|
||||
func locateByIPIP() *City {
|
||||
data, err := httpGet("https://myip.ipip.net")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
text := string(data)
|
||||
re := regexp.MustCompile(`来自于[::]\s*(.+?)\s*$`)
|
||||
m := re.FindStringSubmatch(text)
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Fields(m[1])
|
||||
cityName := parts[0]
|
||||
if len(parts) >= 3 {
|
||||
cityName = parts[2]
|
||||
} else if len(parts) >= 2 {
|
||||
cityName = parts[1]
|
||||
}
|
||||
for _, c := range cities {
|
||||
if c.Name == cityName {
|
||||
log.Println("ipip.net 匹配:", c.Name)
|
||||
return &c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func locateByQWeather() *City {
|
||||
data, err := httpGet("https://myip.ipip.net")
|
||||
if err == nil {
|
||||
re := regexp.MustCompile(`(\d+\.\d+\.\d+\.\d+)`)
|
||||
if m := re.FindStringSubmatch(string(data)); m != nil {
|
||||
return geoLookup(m[1])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func geoLookup(ip string) *City {
|
||||
url := fmt.Sprintf(qweatherHost+"/v2/city/lookup?location=%s&key=%s", ip, qweatherKey)
|
||||
data, err := httpGet(url)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var resp struct {
|
||||
Code string `json:"code"`
|
||||
Location []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Adm2 string `json:"adm2"`
|
||||
Adm1 string `json:"adm1"`
|
||||
} `json:"location"`
|
||||
}
|
||||
if json.Unmarshal(data, &resp) != nil || resp.Code != "200" || len(resp.Location) == 0 {
|
||||
return nil
|
||||
}
|
||||
loc := resp.Location[0]
|
||||
for _, c := range cities {
|
||||
if c.Name == loc.Name {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
return &City{loc.ID, loc.Name, loc.Adm2, loc.Adm1}
|
||||
}
|
||||
|
||||
func getCurrentCity() City {
|
||||
cfg := loadConfig()
|
||||
if cfg.City != "" {
|
||||
for _, c := range cities {
|
||||
if c.ID == cfg.City {
|
||||
return c
|
||||
}
|
||||
}
|
||||
}
|
||||
return getLocation()
|
||||
}
|
||||
|
||||
// ── 天气数据 ──
|
||||
|
||||
type hourlyItem struct {
|
||||
Time string `json:"time"`
|
||||
Temp string `json:"temp"`
|
||||
Icon string `json:"icon"`
|
||||
}
|
||||
|
||||
type dailyItem struct {
|
||||
Date string `json:"date"`
|
||||
Icon string `json:"icon"`
|
||||
TempMin string `json:"tempMin"`
|
||||
TempMax string `json:"tempMax"`
|
||||
}
|
||||
|
||||
type currentWeather struct {
|
||||
Text string `json:"text"`
|
||||
Temp string `json:"temp"`
|
||||
}
|
||||
|
||||
func fetchAndPushWeather(city City) {
|
||||
type weatherData struct {
|
||||
Current string `json:"current"`
|
||||
Hourly []hourlyItem `json:"hourly"`
|
||||
Daily []dailyItem `json:"daily"`
|
||||
City string `json:"city"`
|
||||
}
|
||||
|
||||
wd := weatherData{}
|
||||
cityName := city.Name
|
||||
if city.Adm2 != city.Name {
|
||||
cityName = city.Adm2 + " " + city.Name
|
||||
}
|
||||
wd.City = cityName
|
||||
|
||||
if now := fetchCurrentWeather(city.ID); now != nil {
|
||||
icon := getWeatherIcon(now.Text)
|
||||
wd.Current = fmt.Sprintf("📍 %s %s %s %s°C", cityName, icon, now.Text, now.Temp)
|
||||
} else {
|
||||
wd.Current = fmt.Sprintf("📍 %s ☀️ 晴 24°C", cityName)
|
||||
}
|
||||
|
||||
wd.Hourly = fetchHourlyForecast(city.ID)
|
||||
wd.Daily = fetchDailyForecast(city.ID)
|
||||
|
||||
jsonData, _ := json.Marshal(wd)
|
||||
evalJS(fmt.Sprintf(`if(window.updateWeatherFromGo) window.updateWeatherFromGo(%s)`, string(jsonData)))
|
||||
log.Println("天气已推送:", wd.Current)
|
||||
}
|
||||
|
||||
func fetchCurrentWeather(cityID string) *currentWeather {
|
||||
url := fmt.Sprintf(qweatherHost+"/v7/weather/now?location=%s&key=%s", cityID, qweatherKey)
|
||||
data, err := httpGet(url)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var resp struct {
|
||||
Code string `json:"code"`
|
||||
Now struct {
|
||||
Text string `json:"text"`
|
||||
Temp string `json:"temp"`
|
||||
} `json:"now"`
|
||||
}
|
||||
if json.Unmarshal(data, &resp) != nil || resp.Code != "200" {
|
||||
return nil
|
||||
}
|
||||
return ¤tWeather{resp.Now.Text, resp.Now.Temp}
|
||||
}
|
||||
|
||||
func fetchHourlyForecast(cityID string) []hourlyItem {
|
||||
url := fmt.Sprintf(qweatherHost+"/v7/weather/24h?location=%s&key=%s", cityID, qweatherKey)
|
||||
data, err := httpGet(url)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var resp struct {
|
||||
Code string `json:"code"`
|
||||
Hourly []struct {
|
||||
FxTime string `json:"fxTime"`
|
||||
Temp string `json:"temp"`
|
||||
Text string `json:"text"`
|
||||
} `json:"hourly"`
|
||||
}
|
||||
if json.Unmarshal(data, &resp) != nil || resp.Code != "200" {
|
||||
return nil
|
||||
}
|
||||
var result []hourlyItem
|
||||
for i, h := range resp.Hourly {
|
||||
if i >= 8 {
|
||||
break
|
||||
}
|
||||
t := h.FxTime
|
||||
if idx := strings.Index(t, "T"); idx >= 0 {
|
||||
t = t[idx+1:]
|
||||
if len(t) >= 5 {
|
||||
t = t[:5]
|
||||
}
|
||||
}
|
||||
result = append(result, hourlyItem{t, h.Temp + "°", getWeatherIcon(h.Text)})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func fetchDailyForecast(cityID string) []dailyItem {
|
||||
url := fmt.Sprintf(qweatherHost+"/v7/weather/7d?location=%s&key=%s", cityID, qweatherKey)
|
||||
data, err := httpGet(url)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var resp struct {
|
||||
Code string `json:"code"`
|
||||
Daily []struct {
|
||||
FxDate string `json:"fxDate"`
|
||||
TextDay string `json:"textDay"`
|
||||
TempMin string `json:"tempMin"`
|
||||
TempMax string `json:"tempMax"`
|
||||
} `json:"daily"`
|
||||
}
|
||||
if json.Unmarshal(data, &resp) != nil || resp.Code != "200" {
|
||||
return nil
|
||||
}
|
||||
weekdays := []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}
|
||||
today := time.Now().Format("2006-01-02")
|
||||
var result []dailyItem
|
||||
for _, d := range resp.Daily {
|
||||
date := d.FxDate
|
||||
if date == today {
|
||||
date = "今天"
|
||||
} else {
|
||||
if t, err := time.Parse("2006-01-02", date); err == nil {
|
||||
date = weekdays[t.Weekday()]
|
||||
}
|
||||
}
|
||||
result = append(result, dailyItem{date, getWeatherIcon(d.TextDay), d.TempMin, d.TempMax})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func weatherLoop() {
|
||||
time.Sleep(3 * time.Second)
|
||||
city := getCurrentCity()
|
||||
log.Println("初始城市:", city.Name)
|
||||
fetchAndPushWeather(city)
|
||||
|
||||
ticker := time.NewTicker(10 * time.Minute)
|
||||
for range ticker.C {
|
||||
city := getCurrentCity()
|
||||
fetchAndPushWeather(city)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user