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 ¤tWeather{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) } }