328 lines
7.1 KiB
Go
328 lines
7.1 KiB
Go
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()
|
|
}
|
|
}
|
|
}()
|
|
}
|