package main import ( "encoding/json" "fmt" "io" "log" "os" "path/filepath" "sort" "strings" "sync" "time" ) const bingAPIBase = "https://www.bing.com/HPImageArchive.aspx?format=js&n=8&mkt=zh-CN" type bingResponse struct { Images []struct { StartDate string `json:"startdate"` URL string `json:"url"` URLBase string `json:"urlbase"` Copyright string `json:"copyright"` } `json:"images"` } type BingRecord struct { Date string `json:"date"` URLBase string `json:"urlbase"` Copyright string `json:"copyright"` Filename string `json:"filename"` Favorited bool `json:"favorited"` } type BingHistory struct { Records []BingRecord `json:"records"` CurrentIdx int `json:"currentIdx"` } var bingMu sync.Mutex func bingDir() string { return filepath.Join(configDir(), "bing") } func bingHistoryPath() string { return filepath.Join(configDir(), "bing_history.json") } func loadBingHistory() *BingHistory { data, err := os.ReadFile(bingHistoryPath()) if err != nil { return &BingHistory{} } var h BingHistory if json.Unmarshal(data, &h) != nil { return &BingHistory{} } return &h } func saveBingHistory(h *BingHistory) error { data, _ := json.MarshalIndent(h, "", " ") return os.WriteFile(bingHistoryPath(), data, 0644) } func fetchBingHistory() { bingMu.Lock() defer bingMu.Unlock() os.MkdirAll(bingDir(), 0755) existing := loadBingHistory() existingMap := make(map[string]BingRecord) for _, r := range existing.Records { existingMap[r.Date] = r } // 分页下载: idx=0,8,16... 直到命中已有记录或无新图片 for idx := 0; idx < 80; idx += 8 { url := fmt.Sprintf("%s&idx=%d", bingAPIBase, idx) resp, err := httpClient.Get(url) if err != nil { log.Printf("Bing API 请求失败 (idx=%d): %v", idx, err) break } data, _ := io.ReadAll(resp.Body) resp.Body.Close() var br bingResponse if json.Unmarshal(data, &br) != nil || len(br.Images) == 0 { break } newCount := 0 for _, img := range br.Images { date := img.StartDate if date == "" { continue } if _, exists := existingMap[date]; exists { continue } imgURL := img.URL if !strings.HasPrefix(imgURL, "http") { imgURL = "https://www.bing.com" + imgURL } imgResp, err := httpClient.Get(imgURL) if err != nil { continue } imgData, _ := io.ReadAll(imgResp.Body) imgResp.Body.Close() if len(imgData) == 0 { continue } filename := date + ".jpg" localPath := filepath.Join(bingDir(), filename) os.WriteFile(localPath, imgData, 0644) log.Printf("Bing 壁纸已下载: %s (%d bytes)", filename, len(imgData)) existingMap[date] = BingRecord{ Date: date, URLBase: img.URLBase, Copyright: img.Copyright, Filename: filename, } newCount++ } if newCount == 0 && idx > 0 { break } time.Sleep(500 * time.Millisecond) } // 按 API 返回顺序重建 records (newest first) // 用 existingMap 里所有记录按 date 降序排列 var records []BingRecord for _, r := range existingMap { records = append(records, r) } sort.Slice(records, func(i, j int) bool { return records[i].Date > records[j].Date }) history := &BingHistory{ Records: records, CurrentIdx: 0, } if existing.CurrentIdx < len(records) { history.CurrentIdx = existing.CurrentIdx } saveBingHistory(history) log.Printf("Bing 壁纸: 共 %d 张", len(records)) reloadWallpaper() } func getCurrentBingPath() string { h := loadBingHistory() if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) { return "" } return filepath.Join(bingDir(), h.Records[h.CurrentIdx].Filename) } func getCurrentBingRecord() *BingRecord { h := loadBingHistory() if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) { return nil } return &h.Records[h.CurrentIdx] } func bingPrev() { bingMu.Lock() defer bingMu.Unlock() h := loadBingHistory() if len(h.Records) == 0 { return } h.CurrentIdx = (h.CurrentIdx + 1) % len(h.Records) saveBingHistory(h) log.Printf("Bing 壁纸: 上一个 (idx=%d, date=%s)", h.CurrentIdx, h.Records[h.CurrentIdx].Date) bingReloadImage() } func bingNext() { bingMu.Lock() defer bingMu.Unlock() h := loadBingHistory() if len(h.Records) == 0 { return } h.CurrentIdx = (h.CurrentIdx - 1 + len(h.Records)) % len(h.Records) saveBingHistory(h) log.Printf("Bing 壁纸: 下一个 (idx=%d, date=%s)", h.CurrentIdx, h.Records[h.CurrentIdx].Date) bingReloadImage() } func bingToggleFavorite() string { bingMu.Lock() defer bingMu.Unlock() h := loadBingHistory() if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) { return "" } r := &h.Records[h.CurrentIdx] r.Favorited = !r.Favorited saveBingHistory(h) if r.Favorited { log.Printf("Bing 壁纸: 已收藏 (date=%s)", r.Date) return "☆ 取消收藏" } log.Printf("Bing 壁纸: 已取消收藏 (date=%s)", r.Date) return "★ 收藏当前壁纸" } func bingCurrentState() string { h := loadBingHistory() if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) { return `{"fav":false,"label":"☆","copyright":"","idx":0,"total":0}` } r := h.Records[h.CurrentIdx] label := "☆" if r.Favorited { label = "⭐" } data, _ := json.Marshal(map[string]interface{}{ "fav": r.Favorited, "label": label, "copyright": r.Copyright, "date": r.Date, "filename": r.Filename, "idx": h.CurrentIdx, "total": len(h.Records), }) return string(data) } func bingFavoritesJSON() string { h := loadBingHistory() type favItem struct { Date string `json:"date"` Copyright string `json:"copyright"` Filename string `json:"filename"` Idx int `json:"idx"` } var favs []favItem for i, r := range h.Records { if r.Favorited { favs = append(favs, favItem{r.Date, r.Copyright, r.Filename, i}) } } if favs == nil { favs = []favItem{} } data, _ := json.Marshal(favs) return string(data) } func bingSetByIdx(idx int) string { bingMu.Lock() defer bingMu.Unlock() h := loadBingHistory() if idx < 0 || idx >= len(h.Records) { return bingCurrentState() } h.CurrentIdx = idx saveBingHistory(h) bingReloadImage() return bingCurrentState() } func bingThumbDataURI(filename string) string { p := filepath.Join(bingDir(), filename) return imageToDataURI(p) } func bingWallpaperLoop() { cfg := loadConfig() if cfg.WallpaperType == WPBing { h := loadBingHistory() if len(h.Records) == 0 { fetchBingHistory() } else { reloadWallpaper() } } // 定时切换壁纸 (1 小时间隔) go func() { ticker := time.NewTicker(1 * time.Hour) for range ticker.C { cfg := loadConfig() if cfg.WallpaperType != WPBing || !cfg.BingAutoRefresh { continue } bingMu.Lock() h := loadBingHistory() if len(h.Records) <= 1 { bingMu.Unlock() continue } h.CurrentIdx = (h.CurrentIdx + 1) % len(h.Records) saveBingHistory(h) bingMu.Unlock() bingReloadImage() log.Printf("Bing 自动切换: idx=%d/%d", h.CurrentIdx, len(h.Records)) } }() go func() { fetchTicker := time.NewTicker(4 * time.Hour) for range fetchTicker.C { cfg := loadConfig() if cfg.WallpaperType == WPBing { go fetchBingHistory() } } }() }