Files
u-desktop/weather.go
绝尘 a804db3579 重构: 拆分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
2026-05-25 19:09:07 +08:00

307 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 &currentWeather{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)
}
}