Files
u-desktop/weather.go
T
lxy efe54d6a41 新增: 天气城市扩展至全国地级市附生成脚本
- 城市列表由18个常用扩至386个地级市(和风LocationList地市级)
- 附 gen-cities.ps1 从源数据一键再生成, 支持双源下载
- 前端城市选择器由后端 citiesByProv 驱动, 无需改动
2026-10-03 10:19:33 +08:00

730 lines
27 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"
"sync"
"time"
)
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", "北京", "北京", "北京"},
{"101030100", "天津", "天津", "天津"},
{"101020100", "上海", "上海", "上海"},
{"101040100", "重庆", "重庆", "重庆"},
{"101090101", "石家庄", "石家庄", "河北"},
{"101090201", "保定", "保定", "河北"},
{"101090301", "张家口", "张家口", "河北"},
{"101090401", "承德", "承德", "河北"},
{"101090501", "唐山", "唐山", "河北"},
{"101090601", "廊坊", "廊坊", "河北"},
{"101090701", "沧州", "沧州", "河北"},
{"101090801", "衡水", "衡水", "河北"},
{"101090901", "邢台", "邢台", "河北"},
{"101091001", "邯郸", "邯郸", "河北"},
{"101091101", "秦皇岛", "秦皇岛", "河北"},
{"101091201", "雄安新区", "雄安新区", "河北"},
{"101100101", "太原", "太原", "山西"},
{"101100201", "大同", "大同", "山西"},
{"101100301", "阳泉", "阳泉", "山西"},
{"101100401", "晋中", "晋中", "山西"},
{"101100501", "长治", "长治", "山西"},
{"101100601", "晋城", "晋城", "山西"},
{"101100701", "临汾", "临汾", "山西"},
{"101100801", "运城", "运城", "山西"},
{"101100901", "朔州", "朔州", "山西"},
{"101101001", "忻州", "忻州", "山西"},
{"101101100", "吕梁", "吕梁", "山西"},
{"101070101", "沈阳", "沈阳", "辽宁"},
{"101070201", "大连", "大连", "辽宁"},
{"101070301", "鞍山", "鞍山", "辽宁"},
{"101070401", "抚顺", "抚顺", "辽宁"},
{"101070501", "本溪", "本溪", "辽宁"},
{"101070601", "丹东", "丹东", "辽宁"},
{"101070701", "锦州", "锦州", "辽宁"},
{"101070801", "营口", "营口", "辽宁"},
{"101070901", "阜新", "阜新", "辽宁"},
{"101071001", "辽阳", "辽阳", "辽宁"},
{"101071101", "铁岭", "铁岭", "辽宁"},
{"101071201", "朝阳", "朝阳", "辽宁"},
{"101071301", "盘锦", "盘锦", "辽宁"},
{"101071401", "葫芦岛", "葫芦岛", "辽宁"},
{"101060101", "长春", "长春", "吉林"},
{"101060201", "吉林", "吉林", "吉林"},
{"101060301", "延边朝鲜族自治州", "延边朝鲜族自治州", "吉林"},
{"101060401", "四平", "四平", "吉林"},
{"101060501", "通化", "通化", "吉林"},
{"101060601", "白城", "白城", "吉林"},
{"101060701", "辽源", "辽源", "吉林"},
{"101060801", "松原", "松原", "吉林"},
{"101060901", "白山", "白山", "吉林"},
{"101050101", "哈尔滨", "哈尔滨", "黑龙江"},
{"101050201", "齐齐哈尔", "齐齐哈尔", "黑龙江"},
{"101050301", "牡丹江", "牡丹江", "黑龙江"},
{"101050401", "佳木斯", "佳木斯", "黑龙江"},
{"101050501", "绥化", "绥化", "黑龙江"},
{"101050601", "黑河", "黑河", "黑龙江"},
{"101050701", "大兴安岭地区", "大兴安岭地区", "黑龙江"},
{"101050801", "伊春", "伊春", "黑龙江"},
{"101050901", "大庆", "大庆", "黑龙江"},
{"101051001", "七台河", "七台河", "黑龙江"},
{"101051101", "鸡西", "鸡西", "黑龙江"},
{"101051201", "鹤岗", "鹤岗", "黑龙江"},
{"101051301", "双鸭山", "双鸭山", "黑龙江"},
{"101190101", "南京", "南京", "江苏"},
{"101190201", "无锡", "无锡", "江苏"},
{"101190301", "镇江", "镇江", "江苏"},
{"101190401", "苏州", "苏州", "江苏"},
{"101190501", "南通", "南通", "江苏"},
{"101190601", "扬州", "扬州", "江苏"},
{"101190701", "盐城", "盐城", "江苏"},
{"101190801", "徐州", "徐州", "江苏"},
{"101190901", "淮安", "淮安", "江苏"},
{"101191001", "连云港", "连云港", "江苏"},
{"101191101", "常州", "常州", "江苏"},
{"101191201", "泰州", "泰州", "江苏"},
{"101191301", "宿迁", "宿迁", "江苏"},
{"101210101", "杭州", "杭州", "浙江"},
{"101210201", "湖州", "湖州", "浙江"},
{"101210301", "嘉兴", "嘉兴", "浙江"},
{"101210401", "宁波", "宁波", "浙江"},
{"101210501", "绍兴", "绍兴", "浙江"},
{"101210601", "台州", "台州", "浙江"},
{"101210701", "温州", "温州", "浙江"},
{"101210801", "丽水", "丽水", "浙江"},
{"101210901", "金华", "金华", "浙江"},
{"101211001", "衢州", "衢州", "浙江"},
{"101211101", "舟山", "舟山", "浙江"},
{"101220101", "合肥", "合肥", "安徽"},
{"101220201", "蚌埠", "蚌埠", "安徽"},
{"101220301", "芜湖", "芜湖", "安徽"},
{"101220401", "淮南", "淮南", "安徽"},
{"101220501", "马鞍山", "马鞍山", "安徽"},
{"101220601", "安庆", "安庆", "安徽"},
{"101220701", "宿州", "宿州", "安徽"},
{"101220801", "阜阳", "阜阳", "安徽"},
{"101220901", "亳州", "亳州", "安徽"},
{"101221001", "黄山", "黄山", "安徽"},
{"101221101", "滁州", "滁州", "安徽"},
{"101221201", "淮北", "淮北", "安徽"},
{"101221301", "铜陵", "铜陵", "安徽"},
{"101221401", "宣城", "宣城", "安徽"},
{"101221501", "六安", "六安", "安徽"},
{"101221701", "池州", "池州", "安徽"},
{"101230101", "福州", "福州", "福建"},
{"101230201", "厦门", "厦门", "福建"},
{"101230301", "宁德", "宁德", "福建"},
{"101230401", "莆田", "莆田", "福建"},
{"101230501", "泉州", "泉州", "福建"},
{"101230601", "漳州", "漳州", "福建"},
{"101230701", "龙岩", "龙岩", "福建"},
{"101230801", "三明", "三明", "福建"},
{"101230901", "南平", "南平", "福建"},
{"101240101", "南昌", "南昌", "江西"},
{"101240201", "九江", "九江", "江西"},
{"101240301", "上饶", "上饶", "江西"},
{"101240401", "抚州", "抚州", "江西"},
{"101240501", "宜春", "宜春", "江西"},
{"101240601", "吉安", "吉安", "江西"},
{"101240701", "赣州", "赣州", "江西"},
{"101240801", "景德镇", "景德镇", "江西"},
{"101240901", "萍乡", "萍乡", "江西"},
{"101241001", "新余", "新余", "江西"},
{"101241101", "鹰潭", "鹰潭", "江西"},
{"101120101", "济南", "济南", "山东"},
{"101120201", "青岛", "青岛", "山东"},
{"101120301", "淄博", "淄博", "山东"},
{"101120401", "德州", "德州", "山东"},
{"101120501", "烟台", "烟台", "山东"},
{"101120601", "潍坊", "潍坊", "山东"},
{"101120701", "济宁", "济宁", "山东"},
{"101120801", "泰安", "泰安", "山东"},
{"101120901", "临沂", "临沂", "山东"},
{"101121001", "菏泽", "菏泽", "山东"},
{"101121101", "滨州", "滨州", "山东"},
{"101121201", "东营", "东营", "山东"},
{"101121301", "威海", "威海", "山东"},
{"101121401", "枣庄", "枣庄", "山东"},
{"101121501", "日照", "日照", "山东"},
{"101121701", "聊城", "聊城", "山东"},
{"101180101", "郑州", "郑州", "河南"},
{"101180201", "安阳", "安阳", "河南"},
{"101180301", "新乡", "新乡", "河南"},
{"101180401", "许昌", "许昌", "河南"},
{"101180501", "平顶山", "平顶山", "河南"},
{"101180601", "信阳", "信阳", "河南"},
{"101180701", "南阳", "南阳", "河南"},
{"101180801", "开封", "开封", "河南"},
{"101180901", "洛阳", "洛阳", "河南"},
{"101181001", "商丘", "商丘", "河南"},
{"101181101", "焦作", "焦作", "河南"},
{"101181201", "鹤壁", "鹤壁", "河南"},
{"101181301", "濮阳", "濮阳", "河南"},
{"101181401", "周口", "周口", "河南"},
{"101181501", "漯河", "漯河", "河南"},
{"101181601", "驻马店", "驻马店", "河南"},
{"101181701", "三门峡", "三门峡", "河南"},
{"101181801", "济源", "济源", "河南"},
{"101200101", "武汉", "武汉", "湖北"},
{"101200201", "襄阳", "襄阳", "湖北"},
{"101200301", "鄂州", "鄂州", "湖北"},
{"101200401", "孝感", "孝感", "湖北"},
{"101200501", "黄冈", "黄冈", "湖北"},
{"101200601", "黄石", "黄石", "湖北"},
{"101200701", "咸宁", "咸宁", "湖北"},
{"101200801", "荆州", "荆州", "湖北"},
{"101200901", "宜昌", "宜昌", "湖北"},
{"101201001", "恩施土家族苗族自治州", "恩施土家族苗族自治州", "湖北"},
{"101201101", "十堰", "十堰", "湖北"},
{"101201201", "神农架林区", "神农架林区", "湖北"},
{"101201301", "随州", "随州", "湖北"},
{"101201401", "荆门", "荆门", "湖北"},
{"101201501", "天门", "天门", "湖北"},
{"101201601", "仙桃", "仙桃", "湖北"},
{"101201701", "潜江", "潜江", "湖北"},
{"101250101", "长沙", "长沙", "湖南"},
{"101250201", "湘潭", "湘潭", "湖南"},
{"101250301", "株洲", "株洲", "湖南"},
{"101250401", "衡阳", "衡阳", "湖南"},
{"101250501", "郴州", "郴州", "湖南"},
{"101250601", "常德", "常德", "湖南"},
{"101250700", "益阳", "益阳", "湖南"},
{"101250801", "娄底", "娄底", "湖南"},
{"101250901", "邵阳", "邵阳", "湖南"},
{"101251001", "岳阳", "岳阳", "湖南"},
{"101251101", "张家界", "张家界", "湖南"},
{"101251201", "怀化", "怀化", "湖南"},
{"101251401", "永州", "永州", "湖南"},
{"101251501", "湘西土家族苗族自治州", "湘西土家族苗族自治州", "湖南"},
{"101280101", "广州", "广州", "广东"},
{"101280201", "韶关", "韶关", "广东"},
{"101280301", "惠州", "惠州", "广东"},
{"101280401", "梅州", "梅州", "广东"},
{"101280501", "汕头", "汕头", "广东"},
{"101280601", "深圳", "深圳", "广东"},
{"101280701", "珠海", "珠海", "广东"},
{"101280800", "佛山", "佛山", "广东"},
{"101280901", "肇庆", "肇庆", "广东"},
{"101281001", "湛江", "湛江", "广东"},
{"101281101", "江门", "江门", "广东"},
{"101281201", "河源", "河源", "广东"},
{"101281301", "清远", "清远", "广东"},
{"101281401", "云浮", "云浮", "广东"},
{"101281501", "潮州", "潮州", "广东"},
{"101281601", "东莞", "东莞", "广东"},
{"101281701", "中山", "中山", "广东"},
{"101281801", "阳江", "阳江", "广东"},
{"101281901", "揭阳", "揭阳", "广东"},
{"101282001", "茂名", "茂名", "广东"},
{"101282101", "汕尾", "汕尾", "广东"},
{"101310101", "海口", "海口", "海南"},
{"101310201", "三亚", "三亚", "海南"},
{"101310202", "东方", "东方", "海南"},
{"101310203", "临高县", "临高县", "海南"},
{"101310204", "澄迈县", "澄迈县", "海南"},
{"101310205", "儋州", "儋州", "海南"},
{"101310206", "昌江黎族自治县", "昌江黎族自治县", "海南"},
{"101310207", "白沙黎族自治县", "白沙黎族自治县", "海南"},
{"101310208", "琼中黎族苗族自治县", "琼中黎族苗族自治县", "海南"},
{"101310209", "定安县", "定安县", "海南"},
{"101310210", "屯昌县", "屯昌县", "海南"},
{"101310211", "琼海", "琼海", "海南"},
{"101310212", "文昌", "文昌", "海南"},
{"101310214", "保亭黎族苗族自治县", "保亭黎族苗族自治县", "海南"},
{"101310215", "万宁", "万宁", "海南"},
{"101310216", "陵水黎族自治县", "陵水黎族自治县", "海南"},
{"101310221", "乐东黎族自治县", "乐东黎族自治县", "海南"},
{"101310222", "五指山", "五指山", "海南"},
{"101310301", "三沙", "三沙", "海南"},
{"101270101", "成都", "成都", "四川"},
{"101270201", "攀枝花", "攀枝花", "四川"},
{"101270301", "自贡", "自贡", "四川"},
{"101270401", "绵阳", "绵阳", "四川"},
{"101270501", "南充", "南充", "四川"},
{"101270601", "达州", "达州", "四川"},
{"101270701", "遂宁", "遂宁", "四川"},
{"101270801", "广安", "广安", "四川"},
{"101270901", "巴中", "巴中", "四川"},
{"101271001", "泸州", "泸州", "四川"},
{"101271101", "宜宾", "宜宾", "四川"},
{"101271201", "内江", "内江", "四川"},
{"101271301", "资阳", "资阳", "四川"},
{"101271401", "乐山", "乐山", "四川"},
{"101271501", "眉山", "眉山", "四川"},
{"101271601", "凉山彝族自治州", "凉山彝族自治州", "四川"},
{"101271701", "雅安", "雅安", "四川"},
{"101271801", "甘孜藏族自治州", "甘孜藏族自治州", "四川"},
{"101271901", "阿坝藏族羌族自治州", "阿坝藏族羌族自治州", "四川"},
{"101272001", "德阳", "德阳", "四川"},
{"101272101", "广元", "广元", "四川"},
{"101260101", "贵阳", "贵阳", "贵州"},
{"101260201", "遵义", "遵义", "贵州"},
{"101260301", "安顺", "安顺", "贵州"},
{"101260401", "黔南布依族苗族自治州", "黔南布依族苗族自治州", "贵州"},
{"101260501", "黔东南苗族侗族自治州", "黔东南苗族侗族自治州", "贵州"},
{"101260601", "铜仁", "铜仁", "贵州"},
{"101260701", "毕节", "毕节", "贵州"},
{"101260801", "六盘水", "六盘水", "贵州"},
{"101260901", "黔西南布依族苗族自治州", "黔西南布依族苗族自治州", "贵州"},
{"101290101", "昆明", "昆明", "云南"},
{"101290201", "大理白族自治州", "大理白族自治州", "云南"},
{"101290301", "红河哈尼族彝族自治州", "红河哈尼族彝族自治州", "云南"},
{"101290401", "曲靖", "曲靖", "云南"},
{"101290501", "保山", "保山", "云南"},
{"101290601", "文山壮族苗族自治州", "文山壮族苗族自治州", "云南"},
{"101290701", "玉溪", "玉溪", "云南"},
{"101290801", "楚雄彝族自治州", "楚雄彝族自治州", "云南"},
{"101290901", "普洱", "普洱", "云南"},
{"101291001", "昭通", "昭通", "云南"},
{"101291101", "临沧", "临沧", "云南"},
{"101291201", "怒江傈僳族自治州", "怒江傈僳族自治州", "云南"},
{"101291301", "迪庆藏族自治州", "迪庆藏族自治州", "云南"},
{"101291401", "丽江", "丽江", "云南"},
{"101291501", "德宏傣族景颇族自治州", "德宏傣族景颇族自治州", "云南"},
{"101291601", "西双版纳傣族自治州", "西双版纳傣族自治州", "云南"},
{"101110101", "西安", "西安", "陕西"},
{"101110200", "咸阳", "咸阳", "陕西"},
{"101110300", "延安", "延安", "陕西"},
{"101110401", "榆林", "榆林", "陕西"},
{"101110501", "渭南", "渭南", "陕西"},
{"101110601", "商洛", "商洛", "陕西"},
{"101110701", "安康", "安康", "陕西"},
{"101110801", "汉中", "汉中", "陕西"},
{"101110901", "宝鸡", "宝鸡", "陕西"},
{"101111001", "铜川", "铜川", "陕西"},
{"101160101", "兰州", "兰州", "甘肃"},
{"101160201", "定西", "定西", "甘肃"},
{"101160301", "平凉", "平凉", "甘肃"},
{"101160401", "庆阳", "庆阳", "甘肃"},
{"101160501", "武威", "武威", "甘肃"},
{"101160601", "金昌", "金昌", "甘肃"},
{"101160701", "张掖", "张掖", "甘肃"},
{"101160801", "酒泉", "酒泉", "甘肃"},
{"101160901", "天水", "天水", "甘肃"},
{"101161001", "陇南", "陇南", "甘肃"},
{"101161101", "临夏回族自治州", "临夏回族自治州", "甘肃"},
{"101161201", "甘南藏族自治州", "甘南藏族自治州", "甘肃"},
{"101161301", "白银", "白银", "甘肃"},
{"101161401", "嘉峪关", "嘉峪关", "甘肃"},
{"101150101", "西宁", "西宁", "青海"},
{"101150201", "海东", "海东", "青海"},
{"101150301", "黄南藏族自治州", "黄南藏族自治州", "青海"},
{"101150401", "海南藏族自治州", "海南藏族自治州", "青海"},
{"101150501", "果洛藏族自治州", "果洛藏族自治州", "青海"},
{"101150601", "玉树藏族自治州", "玉树藏族自治州", "青海"},
{"101150701", "海西蒙古族藏族自治州", "海西蒙古族藏族自治州", "青海"},
{"101150801", "海北藏族自治州", "海北藏族自治州", "青海"},
{"101080101", "呼和浩特", "呼和浩特", "内蒙古"},
{"101080201", "包头", "包头", "内蒙古"},
{"101080301", "乌海", "乌海", "内蒙古"},
{"101080401", "乌兰察布", "乌兰察布", "内蒙古"},
{"101080501", "通辽", "通辽", "内蒙古"},
{"101080601", "赤峰", "赤峰", "内蒙古"},
{"101080701", "鄂尔多斯", "鄂尔多斯", "内蒙古"},
{"101080801", "巴彦淖尔", "巴彦淖尔", "内蒙古"},
{"101080901", "锡林郭勒盟", "锡林郭勒盟", "内蒙古"},
{"101081001", "呼伦贝尔", "呼伦贝尔", "内蒙古"},
{"101081101", "兴安盟", "兴安盟", "内蒙古"},
{"101081201", "阿拉善盟", "阿拉善盟", "内蒙古"},
{"101300101", "南宁", "南宁", "广西"},
{"101300201", "崇左", "崇左", "广西"},
{"101300301", "柳州", "柳州", "广西"},
{"101300401", "来宾", "来宾", "广西"},
{"101300501", "桂林", "桂林", "广西"},
{"101300601", "梧州", "梧州", "广西"},
{"101300701", "贺州", "贺州", "广西"},
{"101300801", "贵港", "贵港", "广西"},
{"101300901", "玉林", "玉林", "广西"},
{"101301001", "百色", "百色", "广西"},
{"101301101", "钦州", "钦州", "广西"},
{"101301201", "河池", "河池", "广西"},
{"101301301", "北海", "北海", "广西"},
{"101301401", "防城港", "防城港", "广西"},
{"101140101", "拉萨", "拉萨", "西藏"},
{"101140201", "日喀则", "日喀则", "西藏"},
{"101140301", "山南", "山南", "西藏"},
{"101140401", "林芝", "林芝", "西藏"},
{"101140501", "昌都", "昌都", "西藏"},
{"101140601", "那曲", "那曲", "西藏"},
{"101140701", "阿里地区", "阿里地区", "西藏"},
{"101170101", "银川", "银川", "宁夏"},
{"101170201", "石嘴山", "石嘴山", "宁夏"},
{"101170301", "吴忠", "吴忠", "宁夏"},
{"101170401", "固原", "固原", "宁夏"},
{"101170501", "中卫", "中卫", "宁夏"},
{"101130101", "乌鲁木齐", "乌鲁木齐", "新疆"},
{"101130201", "克拉玛依", "克拉玛依", "新疆"},
{"101130301", "石河子", "石河子", "新疆"},
{"101130401", "昌吉回族自治州", "昌吉回族自治州", "新疆"},
{"101130501", "吐鲁番", "吐鲁番", "新疆"},
{"101130601", "巴音郭楞蒙古自治州", "巴音郭楞蒙古自治州", "新疆"},
{"101130701", "阿拉尔", "阿拉尔", "新疆"},
{"101130801", "阿克苏地区", "阿克苏地区", "新疆"},
{"101130901", "喀什地区", "喀什地区", "新疆"},
{"101131001", "伊犁哈萨克自治州", "伊犁哈萨克自治州", "新疆"},
{"101131101", "塔城地区", "塔城地区", "新疆"},
{"101131201", "哈密", "哈密", "新疆"},
{"101131301", "和田地区", "和田地区", "新疆"},
{"101131401", "阿勒泰地区", "阿勒泰地区", "新疆"},
{"101131501", "克孜勒苏柯尔克孜自治州", "克孜勒苏柯尔克孜自治州", "新疆"},
{"101131601", "博尔塔拉蒙古自治州", "博尔塔拉蒙古自治州", "新疆"},
{"101131701", "图木舒克", "图木舒克", "新疆"},
{"101131801", "五家渠", "五家渠", "新疆"},
{"101131901", "铁门关", "铁门关", "新疆"},
{"101131920", "昆玉", "昆玉", "新疆"},
{"101132101", "北屯", "北屯", "新疆"},
{"101132201", "双河", "双河", "新疆"},
{"101132301", "可克达拉", "可克达拉", "新疆"},
{"101132401", "胡杨河", "胡杨河", "新疆"},
{"101132501", "新星", "新星", "新疆"},
{"101132601", "白杨", "白杨", "新疆"},
{"101320101", "香港特别行政区", "香港特别行政区", "香港"},
{"101330101", "澳门特别行政区", "澳门特别行政区", "澳门"},
{"101231001", "宜兰县", "宜兰县", "台湾"},
{"101340101", "台北", "台北", "台湾"},
{"101340102", "桃园", "桃园", "台湾"},
{"101340103", "新竹", "新竹", "台湾"},
{"101340201", "高雄", "高雄", "台湾"},
{"101340202", "嘉义", "嘉义", "台湾"},
{"101340203", "台南", "台南", "台湾"},
{"101340204", "台东县", "台东县", "台湾"},
{"101340205", "屏东县", "屏东县", "台湾"},
{"101340401", "台中", "台中", "台湾"},
{"101340402", "苗栗县", "苗栗县", "台湾"},
{"101340403", "彰化县", "彰化县", "台湾"},
{"101340404", "南投县", "南投县", "台湾"},
{"101340405", "花莲县", "花莲县", "台湾"},
}
var defaultCity = City{"101200101", "武汉", "武汉", "湖北"}
var httpClient = &http.Client{Timeout: 60 * 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 := locateByGeoAPI(); city != nil {
return *city
}
log.Println("所有定位失败,使用默认城市:", defaultCity.Name)
return defaultCity
}
var (
reIPIP = regexp.MustCompile(`来自于[::]\s*(.+?)\s*$`)
reIPAddr = regexp.MustCompile(`(\d+\.\d+\.\d+\.\d+)`)
)
func locateByIPIP() *City {
data, err := httpGet("https://myip.ipip.net")
if err != nil {
return nil
}
text := string(data)
m := reIPIP.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 locateByGeoAPI() *City {
data, err := httpGet("https://myip.ipip.net")
if err == nil {
if m := reIPAddr.FindStringSubmatch(string(data)); m != nil {
return geoLookup(m[1])
}
}
return nil
}
func geoLookup(ip string) *City {
key := loadConfig().qweatherKey()
if key == "" {
log.Println("未配置和风天气 API Key")
return nil
}
url := fmt.Sprintf(qweatherHost+"/v2/city/lookup?location=%s&key=%s", ip, key)
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"`
Pop string `json:"pop,omitempty"`
}
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"`
}
var (
weatherCacheMu sync.Mutex
weatherCache map[string]time.Time
weatherCacheData map[string]string
)
func init() {
weatherCache = make(map[string]time.Time)
weatherCacheData = make(map[string]string)
}
func fetchAndPushWeather(city City) {
// 5 分钟内同一城市不重复请求
weatherCacheMu.Lock()
if last, ok := weatherCache[city.ID]; ok && time.Since(last) < 5*time.Minute {
if cached, ok := weatherCacheData[city.ID]; ok {
weatherCacheMu.Unlock()
evalJS(fmt.Sprintf(`if(window.updateWeatherFromGo) window.updateWeatherFromGo(%s)`, cached))
return
}
}
weatherCacheMu.Unlock()
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)
weatherCacheMu.Lock()
weatherCache[city.ID] = time.Now()
weatherCacheData[city.ID] = string(jsonData)
weatherCacheMu.Unlock()
evalJS(fmt.Sprintf(`if(window.updateWeatherFromGo) window.updateWeatherFromGo(%s)`, string(jsonData)))
log.Println("天气已推送:", wd.Current)
}
func fetchCurrentWeather(cityID string) *currentWeather {
key := loadConfig().qweatherKey()
if key == "" {
log.Println("未配置和风天气 API Key")
return nil
}
url := fmt.Sprintf(qweatherHost+"/v7/weather/now?location=%s&key=%s", cityID, key)
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 {
key := loadConfig().qweatherKey()
if key == "" {
return nil
}
url := fmt.Sprintf(qweatherHost+"/v7/weather/24h?location=%s&key=%s", cityID, key)
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"`
Pop string `json:"pop"`
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), h.Pop})
}
return result
}
func fetchDailyForecast(cityID string) []dailyItem {
key := loadConfig().qweatherKey()
if key == "" {
return nil
}
url := fmt.Sprintf(qweatherHost+"/v7/weather/7d?location=%s&key=%s", cityID, key)
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)
}
}