package main import ( "encoding/json" "fmt" "log" "os" "path/filepath" "sync" "time" ) const tianapiAIURL = "https://apis.tianapi.com/ai/index" type aiNewsResp struct { Code int `json:"code"` Result struct { Newslist []struct { ID string `json:"id"` CTime string `json:"ctime"` Title string `json:"title"` Description string `json:"description"` Source string `json:"source"` URL string `json:"url"` PicUrl string `json:"picUrl"` } `json:"newslist"` } `json:"result"` } type aiNewsItem struct { Title string `json:"title"` Description string `json:"description"` Source string `json:"source"` CTime string `json:"ctime"` URL string `json:"url"` PicURL string `json:"picUrl"` } var ( aiNewsMu sync.Mutex aiNewsCache []aiNewsItem aiNewsCacheAt time.Time ) func aiNewsCachePath() string { return filepath.Join(configDir(), "ainews_cache.json") } func fetchAINews() []aiNewsItem { aiNewsMu.Lock() if aiNewsCache != nil && time.Since(aiNewsCacheAt) < 2*time.Hour { cached := aiNewsCache aiNewsMu.Unlock() return cached } aiNewsMu.Unlock() key := loadConfig().tianapiKey() if key == "" { log.Println("未配置天聚数行 API Key") return nil } url := fmt.Sprintf("%s?key=%s", tianapiAIURL, key) data, err := httpGet(url) if err != nil { log.Println("AI资讯请求失败:", err) return nil } var resp aiNewsResp if json.Unmarshal(data, &resp) != nil || resp.Code != 200 { log.Println("AI资讯解析失败:", string(data[:min(len(data), 100)])) return nil } var items []aiNewsItem for _, n := range resp.Result.Newslist { items = append(items, aiNewsItem{ Title: n.Title, Description: n.Description, Source: n.Source, CTime: n.CTime, URL: n.URL, PicURL: n.PicUrl, }) } aiNewsMu.Lock() aiNewsCache = items aiNewsCacheAt = time.Now() aiNewsMu.Unlock() // 缓存到文件 cacheData, _ := json.Marshal(map[string]interface{}{ "items": items, "at": time.Now().Format(time.RFC3339), }) if err := os.WriteFile(aiNewsCachePath(), cacheData, 0644); err != nil { log.Println("AI资讯缓存写入失败:", err) } log.Printf("AI资讯已获取: %d条", len(items)) return items } func loadAINewsCache() []aiNewsItem { data, err := os.ReadFile(aiNewsCachePath()) if err != nil { return nil } var cached struct { Items []aiNewsItem `json:"items"` At string `json:"at"` } if json.Unmarshal(data, &cached) != nil { return nil } return cached.Items } func pushAINews(items []aiNewsItem) { if len(items) == 0 { return } jsonData, _ := json.Marshal(items) js := fmt.Sprintf(`if(window.updateAINewsFromGo) window.updateAINewsFromGo(%s)`, string(jsonData)) evalJS(js) } func aiNewsLoop() { cfg := loadConfig() if !cfg.HideAINews { // 先推送缓存 if cached := loadAINewsCache(); cached != nil { pushAINews(cached) } } else if cached := loadAINewsCache(); cached != nil { aiNewsMu.Lock() aiNewsCache = cached aiNewsCacheAt = time.Now() aiNewsMu.Unlock() } time.Sleep(8 * time.Second) cfg = loadConfig() if !cfg.HideAINews { var items []aiNewsItem for i := 0; i < 3; i++ { items = fetchAINews() if items != nil { break } time.Sleep(30 * time.Second) } if items != nil { pushAINews(items) } } ticker := time.NewTicker(2 * time.Hour) for range ticker.C { cfg := loadConfig() if !cfg.HideAINews { if items := fetchAINews(); items != nil { pushAINews(items) } } } } func triggerAINewsRefresh() { go func() { if items := fetchAINews(); items != nil { pushAINews(items) } }() }