From db87655c8725634d6d0c9fbb4e0ce5f80e7c96e5 Mon Sep 17 00:00:00 2001
From: Vaica <94612053+zqlit@users.noreply.github.com>
Date: Tue, 2 Jun 2026 00:23:04 +0800
Subject: [PATCH] 6.2
---
.idea/.gitignore | 5 -
.idea/blog.iml | 12 -
.idea/modules.xml | 8 -
.idea/vcs.xml | 6 -
bot-go/.gitignore | 2 -
bot-go/_fix_import.py | 7 -
bot-go/_fix_inline.py | 14 -
bot-go/_fix_nil.py | 11 -
bot-go/cmd/writebot/main.go | 204 ----
bot-go/config.go | 89 --
bot-go/go.mod | 3 -
bot-go/internal/adminpanel/adminpanel.go | 158 ---
bot-go/internal/botcfg/config.go | 72 --
bot-go/internal/config/config.go | 99 --
bot-go/internal/helpers/helpers.go | 159 ---
bot-go/internal/logutil/logutil.go | 14 -
bot-go/internal/sessions/sessions.go | 71 --
bot-go/internal/tgcore/tgcore.go | 301 ------
bot-go/internal/tghandlers/tghandlers.go | 622 ------------
bot-go/internal/writeserver/writeserver.go | 156 ---
bot-go/start_bot.bat | 5 -
.../meta/2026-06-01-深圳真的好热.json | 7 +
.../.recycle/meta/2026-06-01-深圳真的热.json | 7 +
write/.recycle/meta/202605311204.json | 7 +
.../posts/2026-06-01-深圳真的好热/index.md | 24 +
.../1780325877248-AgACAgQA.jpg | Bin 0 -> 166422 bytes
.../posts/2026-06-01-深圳真的热/index.md | 20 +
write/.recycle/posts/202605311204/index.md | 35 +
write/docs/bot_design/design.md | 914 ++++++++++++++++++
write/docs/integration-plan.md | 477 +++++++++
write/install-startup.bat | 13 +
write/install-startup.ps1 | 21 +
write/package-lock.json | 13 +
write/package.json | 1 +
write/setup-autostart.bat | 33 +
write/src/app/api/bot/restart/route.ts | 15 +
write/src/app/api/bot/status/route.ts | 14 +
write/src/app/api/deploy/route.ts | 77 +-
write/src/app/api/hugo/route.ts | 57 +-
write/src/app/bot/page.tsx | 101 ++
write/src/components/Sidebar.tsx | 3 +-
write/src/instrumentation.ts | 8 +
write/src/lib/bot/cache.ts | 30 +
write/src/lib/bot/config.ts | 5 +
write/src/lib/bot/handlers.ts | 654 +++++++++++++
write/src/lib/bot/helpers.ts | 37 +
write/src/lib/bot/index.ts | 21 +
write/src/lib/bot/poll.ts | 246 +++++
write/src/lib/bot/sessions.ts | 77 ++
write/src/lib/bot/tg.ts | 52 +
write/src/lib/config.ts | 32 +
write/src/lib/git.ts | 137 +++
write/src/lib/hugo.ts | 114 +++
write/src/lib/posts.ts | 2 +-
write/src/lib/rssapi.ts | 29 +-
write/src/types/marked.d.ts | 4 +
write/start-silent.bat | 4 +
write/startup.vbs | 3 +
58 files changed, 3184 insertions(+), 2128 deletions(-)
delete mode 100644 .idea/.gitignore
delete mode 100644 .idea/blog.iml
delete mode 100644 .idea/modules.xml
delete mode 100644 .idea/vcs.xml
delete mode 100644 bot-go/.gitignore
delete mode 100644 bot-go/_fix_import.py
delete mode 100644 bot-go/_fix_inline.py
delete mode 100644 bot-go/_fix_nil.py
delete mode 100644 bot-go/cmd/writebot/main.go
delete mode 100644 bot-go/config.go
delete mode 100644 bot-go/go.mod
delete mode 100644 bot-go/internal/adminpanel/adminpanel.go
delete mode 100644 bot-go/internal/botcfg/config.go
delete mode 100644 bot-go/internal/config/config.go
delete mode 100644 bot-go/internal/helpers/helpers.go
delete mode 100644 bot-go/internal/logutil/logutil.go
delete mode 100644 bot-go/internal/sessions/sessions.go
delete mode 100644 bot-go/internal/tgcore/tgcore.go
delete mode 100644 bot-go/internal/tghandlers/tghandlers.go
delete mode 100644 bot-go/internal/writeserver/writeserver.go
delete mode 100644 bot-go/start_bot.bat
create mode 100644 write/.recycle/meta/2026-06-01-深圳真的好热.json
create mode 100644 write/.recycle/meta/2026-06-01-深圳真的热.json
create mode 100644 write/.recycle/meta/202605311204.json
create mode 100644 write/.recycle/posts/2026-06-01-深圳真的好热/index.md
create mode 100644 write/.recycle/posts/2026-06-01-深圳真的热/1780325877248-AgACAgQA.jpg
create mode 100644 write/.recycle/posts/2026-06-01-深圳真的热/index.md
create mode 100644 write/.recycle/posts/202605311204/index.md
create mode 100644 write/docs/bot_design/design.md
create mode 100644 write/docs/integration-plan.md
create mode 100644 write/install-startup.bat
create mode 100644 write/install-startup.ps1
create mode 100644 write/setup-autostart.bat
create mode 100644 write/src/app/api/bot/restart/route.ts
create mode 100644 write/src/app/api/bot/status/route.ts
create mode 100644 write/src/app/bot/page.tsx
create mode 100644 write/src/instrumentation.ts
create mode 100644 write/src/lib/bot/cache.ts
create mode 100644 write/src/lib/bot/config.ts
create mode 100644 write/src/lib/bot/handlers.ts
create mode 100644 write/src/lib/bot/helpers.ts
create mode 100644 write/src/lib/bot/index.ts
create mode 100644 write/src/lib/bot/poll.ts
create mode 100644 write/src/lib/bot/sessions.ts
create mode 100644 write/src/lib/bot/tg.ts
create mode 100644 write/src/lib/git.ts
create mode 100644 write/src/lib/hugo.ts
create mode 100644 write/src/types/marked.d.ts
create mode 100644 write/start-silent.bat
create mode 100644 write/startup.vbs
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index b58b603f..00000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/blog.iml b/.idea/blog.iml
deleted file mode 100644
index 24643cc3..00000000
--- a/.idea/blog.iml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index e81aaae0..00000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1ddf..00000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/bot-go/.gitignore b/bot-go/.gitignore
deleted file mode 100644
index 2514bd4e..00000000
--- a/bot-go/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-writebot.exe
-.env
diff --git a/bot-go/_fix_import.py b/bot-go/_fix_import.py
deleted file mode 100644
index fd984f92..00000000
--- a/bot-go/_fix_import.py
+++ /dev/null
@@ -1,7 +0,0 @@
-p = r"E:\GitHub\blog\bot-go\cmd\writebot\main.go"
-with open(p, "r", encoding="utf-8") as f:
- c = f.read()
-c = c.replace('"net/http"\n', '')
-with open(p, "w", encoding="utf-8", newline="\n") as f:
- f.write(c)
-print("fixed")
diff --git a/bot-go/_fix_inline.py b/bot-go/_fix_inline.py
deleted file mode 100644
index e854c14a..00000000
--- a/bot-go/_fix_inline.py
+++ /dev/null
@@ -1,14 +0,0 @@
-p = r"E:\GitHub\blog\bot-go\cmd\writebot\main.go"
-with open(p, "r", encoding="utf-8") as f:
- c = f.read()
-
-# Replace logutil.Init + log = logutil.Logger with inline
-c = c.replace(
- 'logutil.Init("writebot")\n\tlog = logutil.Logger',
- 'log = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})).With("svc", "writebot")'
-)
-c = c.replace('\t"writebot/internal/logutil"\n', '')
-
-with open(p, "w", encoding="utf-8", newline="\n") as f:
- f.write(c)
-print("Fixed bot-go inline slog")
diff --git a/bot-go/_fix_nil.py b/bot-go/_fix_nil.py
deleted file mode 100644
index 4a53ffc4..00000000
--- a/bot-go/_fix_nil.py
+++ /dev/null
@@ -1,11 +0,0 @@
-p = r"E:\GitHub\blog\bot-go\cmd\writebot\main.go"
-with open(p, "r", encoding="utf-8") as f:
- c = f.read()
-
-# Fix: move log usage after logutil.Init
-c = c.replace("logutil.Init(\"writebot\")\n\tlog = logutil.Logger\n\n\tcfg := config.Load()",
- "cfg := config.Load()\n\tlogutil.Init(\"writebot\")\n\tlog = logutil.Logger")
-
-with open(p, "w", encoding="utf-8", newline="\n") as f:
- f.write(c)
-print("Fixed bot-go logger init order")
diff --git a/bot-go/cmd/writebot/main.go b/bot-go/cmd/writebot/main.go
deleted file mode 100644
index 2a58c7d2..00000000
--- a/bot-go/cmd/writebot/main.go
+++ /dev/null
@@ -1,204 +0,0 @@
-package main
-
-import (
- "fmt"
- "log/slog"
- "os"
- "os/signal"
- "strings"
- "syscall"
- "time"
-
- "writebot/internal/botcfg"
- "writebot/internal/sessions"
- "writebot/internal/tgcore"
- "writebot/internal/tghandlers"
-
- "write-server/pkg/posts"
-)
-
-var log *slog.Logger
-
-func main() {
- cfg := botcfg.Load()
- posts.Init()
- tgcore.Init(cfg.TGToken)
-
- log = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})).With("svc", "writebot")
- log.Info("WriteBot starting")
-
- if cfg.TGToken == "" {
- log.Error("no TG_BOT_TOKEN configured")
- os.Exit(1)
- }
-
- // Register bot
- me, err := tgcore.TG("getMe", map[string]interface{}{})
- if err == nil {
- if ok, _ := me["ok"].(bool); ok {
- if result, ok := me["result"].(map[string]interface{}); ok {
- log.Info("telegram connected", "username", tgcore.GetString(result, "username"))
- }
- }
- }
- tgcore.TG("setMyCommands", map[string]interface{}{
- "commands": []map[string]string{
- {"command": "start", "description": "启动"},
- {"command": "new", "description": "写新文章"},
- {"command": "list", "description": "文章列表"},
- {"command": "edit", "description": "编辑文章"},
- {"command": "save", "description": "保存草稿"},
- {"command": "publish", "description": "发布到 GitHub"},
- {"command": "stats", "description": "统计"},
- {"command": "help", "description": "帮助"},
- },
- })
-
- h := tghandlers.New(&tghandlers.Config{
- WritePort: cfg.WritePort,
- ControlPort: cfg.ControlPort,
- PreferIface: cfg.PreferIface,
- })
-
- argCommands := map[string]bool{
- "new": true, "title": true, "categories": true, "tags": true, "author": true,
- "edit": true, "view": true, "delete": true,
- "link_add": true, "link_del": true, "link_toggle": true,
- "feed_add": true, "feed_del": true,
- "list": true, "links": true, "feeds": true,
- }
-
- sigCh := make(chan os.Signal, 1)
- signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
-
- go func() {
- var lastOffset int64
- for {
- data, err := tgcore.TG("getUpdates", map[string]interface{}{
- "offset": lastOffset + 1, "timeout": 30,
- "allowed_updates": []string{"message", "callback_query"},
- })
- if err != nil {
- log.Warn("tg poll error", "err", err)
- time.Sleep(3 * time.Second)
- continue
- }
- ok, _ := data["ok"].(bool)
- if !ok { continue }
- results, _ := data["result"].([]interface{})
- for _, updateRaw := range results {
- update, _ := updateRaw.(map[string]interface{})
- if updateID, ok := update["update_id"].(float64); ok { lastOffset = int64(updateID) }
-
- if cbRaw, ok := update["callback_query"]; ok {
- cb, _ := cbRaw.(map[string]interface{})
- cbMsg, _ := cb["message"].(map[string]interface{})
- cbChat, _ := cbMsg["chat"].(map[string]interface{})
- cbFrom, _ := cb["from"].(map[string]interface{})
- var cbChatID int64
- if cbChat != nil { cbChatID = tgcore.GetInt64(cbChat, "id") }
- if cbChatID == 0 && cbFrom != nil { cbChatID = tgcore.GetInt64(cbFrom, "id") }
- queryID := tgcore.GetString(cb, "id")
- cbData := tgcore.GetString(cb, "data")
- tgcore.HandleCallback(cbChatID, queryID, cbData, h)
- continue
- }
-
- msg, _ := update["message"].(map[string]interface{})
- if msg == nil { continue }
- chat, _ := msg["chat"].(map[string]interface{})
- if chat == nil { continue }
- chatID := tgcore.GetInt64(chat, "id")
- if !tgcore.Allowed(chatID, cfg.AllowedIDs) { continue }
-
- if photos, ok := msg["photo"]; ok {
- photoArr, _ := photos.([]interface{})
- if len(photoArr) > 0 && sessions.HasSession(chatID) {
- s := sessions.GetSession(chatID)
- largest := photoArr[len(photoArr)-1].(map[string]interface{})
- fileID := tgcore.GetString(largest, "file_id")
- fileRes, err := tgcore.TG("getFile", map[string]interface{}{"file_id": fileID})
- if err == nil && fileRes["ok"] == true {
- if fileResult, ok := fileRes["result"].(map[string]interface{}); ok {
- filePath := tgcore.GetString(fileResult, "file_path")
- caption := tgcore.GetString(msg, "caption")
- if caption == "" { caption = "image" }
- imgURL := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", cfg.TGToken, filePath)
- s.Content += "
-
-"
- }
- }
- }
- continue
- }
-
- text := tgcore.GetString(msg, "text")
- if text == "" { continue }
- fullText := strings.TrimSpace(text)
- if idx := strings.Index(fullText, "@"); idx >= 0 {
- afterAt := fullText[idx:]
- if spaceIdx := strings.Index(afterAt, " "); spaceIdx >= 0 { afterAt = afterAt[spaceIdx:] } else { afterAt = "" }
- fullText = strings.TrimSpace(fullText[:idx] + afterAt)
- }
- parts := strings.SplitN(fullText, " ", 2)
- slashCmd := ""; slashArg := ""
- if strings.HasPrefix(parts[0], "/") {
- slashCmd = strings.ToLower(parts[0][1:])
- if len(parts) > 1 { slashArg = parts[1] }
- }
- if sessions.HasSession(chatID) && !strings.HasPrefix(fullText, "/") {
- s := sessions.GetSession(chatID)
- s.Content += fullText + "
-"
- continue
- }
- if slashCmd == "" { continue }
- log.Info("tg command", "chat", chatID, "cmd", slashCmd)
- handleCmd(h, chatID, slashCmd, slashArg, argCommands)
- }
- }
- }()
-
- sig := <-sigCh
- log.Info("shutting down", "signal", sig.String())
-}
-
-func handleCmd(h *tghandlers.Handlers, chatID int64, cmd, arg string, argCmds map[string]bool) {
- defer func() { if r := recover(); r != nil { log.Error("handler panic", "cmd", cmd, "panic", r) } }()
- if argCmds[cmd] {
- switch cmd {
- case "new": h.New(chatID, arg)
- case "title": h.Title(chatID, arg)
- case "categories": h.Categories(chatID, arg)
- case "tags": h.Tags(chatID, arg)
- case "author": h.Author(chatID, arg)
- case "edit": h.Edit(chatID, arg)
- case "view": h.View(chatID, arg)
- case "delete": h.Delete(chatID, arg)
- case "link_add": h.LinkAdd(chatID, arg)
- case "link_del": h.LinkDel(chatID, arg)
- case "link_toggle": h.LinkToggle(chatID, arg)
- case "feed_add": h.FeedAdd(chatID, arg)
- case "feed_del": h.FeedDel(chatID, arg)
- case "list": h.List(chatID, arg)
- case "links": h.Links(chatID, arg)
- case "feeds": h.Feeds(chatID, arg)
- default: tgcore.SendMessage(chatID, "? Unknown: /"+cmd, nil)
- }
- } else {
- switch cmd {
- case "start": h.Start(chatID)
- case "help": h.Help(chatID)
- case "save": h.Save(chatID)
- case "publish": h.Publish(chatID)
- case "draft": h.Draft(chatID)
- case "preview": h.Preview(chatID)
- case "cancel": h.Cancel(chatID)
- case "stats": h.Stats(chatID)
- case "status": h.Status(chatID)
- case "ip": h.IP(chatID)
- default: tgcore.SendMessage(chatID, "? Unknown: /"+cmd, nil)
- }
- }
-}
diff --git a/bot-go/config.go b/bot-go/config.go
deleted file mode 100644
index 849763a6..00000000
--- a/bot-go/config.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package main
-
-import (
- "bufio"
- "os"
- "path/filepath"
- "strconv"
- "strings"
-)
-
-type Config struct {
- TGToken string
- AllowedIDs []int64
- WritePort int
- ControlPort int
- PreferIface string
- WriteDir string
- BlogDir string // parent of WriteDir, for hugo
-}
-
-func LoadConfig() *Config {
- c := &Config{
- WritePort: 8016,
- ControlPort: 8017,
- PreferIface: "WLAN",
- }
-
- // Try .env next to executable first, then fallback to cwd
- exe, _ := os.Executable()
- envPath := filepath.Join(filepath.Dir(exe), ".env")
- if _, err := os.Stat(envPath); os.IsNotExist(err) {
- envPath = ".env"
- }
-
- f, err := os.Open(envPath)
- if err != nil {
- return c
- }
- defer f.Close()
-
- scanner := bufio.NewScanner(f)
- for scanner.Scan() {
- line := strings.TrimSpace(scanner.Text())
- if line == "" || strings.HasPrefix(line, "#") {
- continue
- }
- parts := strings.SplitN(line, "=", 2)
- if len(parts) != 2 {
- continue
- }
- key := strings.TrimSpace(parts[0])
- val := strings.TrimSpace(parts[1])
-
- switch key {
- case "TG_BOT_TOKEN":
- c.TGToken = val
- case "TG_ALLOWED_CHAT_IDS":
- for _, s := range strings.Split(val, ",") {
- s = strings.TrimSpace(s)
- if id, err := strconv.ParseInt(s, 10, 64); err == nil {
- c.AllowedIDs = append(c.AllowedIDs, id)
- }
- }
- case "WRITE_PORT":
- if p, err := strconv.Atoi(val); err == nil {
- c.WritePort = p
- }
- case "BOT_PORT":
- if p, err := strconv.Atoi(val); err == nil {
- c.ControlPort = p
- }
- case "PREFER_IFACE":
- c.PreferIface = val
- }
- }
-
- // WriteDir and BlogDir derived from executable location
- exeDir := filepath.Dir(exe)
- writeDir := filepath.Join(exeDir, "..", "write")
- blogDir := filepath.Join(exeDir, "..")
- if abs, err := filepath.Abs(writeDir); err == nil {
- c.WriteDir = abs
- }
- if abs, err := filepath.Abs(blogDir); err == nil {
- c.BlogDir = abs
- }
-
- return c
-}
diff --git a/bot-go/go.mod b/bot-go/go.mod
deleted file mode 100644
index 603a46b9..00000000
--- a/bot-go/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module writebot
-
-go 1.26.3
diff --git a/bot-go/internal/adminpanel/adminpanel.go b/bot-go/internal/adminpanel/adminpanel.go
deleted file mode 100644
index 717bfba8..00000000
--- a/bot-go/internal/adminpanel/adminpanel.go
+++ /dev/null
@@ -1,158 +0,0 @@
-package adminpanel
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "strings"
-
- "writebot/internal/helpers"
- "writebot/internal/writeserver"
-)
-
-var (
- writePort int
- controlPort int
-)
-
-func Init(wp, cp int) {
- writePort = wp
- controlPort = cp
-}
-
-const adminHTML = `
-
-
-
-
-WriteBot
-
-
-
-
-
WriteBot
-
Ctrl+D bookmark this page
-
-
Loading...
-
-
-
-
-
-
-
-
-
-
-
-`
-
-func CreateControlServer() *http.Server {
- html := strings.ReplaceAll(adminHTML, "WRITE_PORT", fmt.Sprintf("%d", writePort))
-
- mux := http.NewServeMux()
-
- mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- path := r.URL.Path
- w.Header().Set("Access-Control-Allow-Origin", "*")
-
- switch path {
- case "/":
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- w.Write([]byte(html))
- case "/status":
- w.Header().Set("Content-Type", "application/json")
- s := writeserver.GetWriteStatus()
- s["ip"] = helpers.GetLanIP("WLAN")
- s["port"] = writePort
- json.NewEncoder(w).Encode(s)
- case "/start":
- if r.Method == "POST" {
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(writeserver.StartWriteServer())
- } else {
- http.NotFound(w, r)
- }
- case "/stop":
- if r.Method == "POST" {
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]bool{"ok": writeserver.StopWriteServer()})
- } else {
- http.NotFound(w, r)
- }
- case "/ip":
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]interface{}{
- "ip": helpers.GetLanIP("WLAN"),
- "port": writePort,
- })
- case "/health":
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]bool{"ok": true})
- default:
- w.WriteHeader(http.StatusNotFound)
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]string{"error": "not found"})
- }
- })
-
- addr := fmt.Sprintf("127.0.0.1:%d", controlPort)
- srv := &http.Server{
- Addr: addr,
- Handler: mux,
- }
-
- fmt.Printf("[control] http://%s\n", addr)
-
- return srv
-}
diff --git a/bot-go/internal/botcfg/config.go b/bot-go/internal/botcfg/config.go
deleted file mode 100644
index c6bca069..00000000
--- a/bot-go/internal/botcfg/config.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package botcfg
-
-import (
- "bufio"
- "os"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
-)
-
-type Config struct {
- TGToken string
- AllowedIDs []int64
- WritePort int
- ControlPort int
- PreferIface string
-}
-
-func Load() *Config {
- cfg := &Config{
- WritePort: 8016,
- ControlPort: 8017,
- PreferIface: "WLAN",
- }
-
- _, filename, _, _ := runtime.Caller(0)
- srcDir := filepath.Dir(filename)
- rootDir := filepath.Dir(filepath.Dir(filepath.Dir(srcDir)))
-
- envPaths := []string{
- filepath.Join(rootDir, "bot-go", ".env"),
- filepath.Join(rootDir, ".env"),
- }
- var f *os.File
- var err error
- for _, envPath := range envPaths {
- f, err = os.Open(envPath)
- if err == nil { break }
- }
- if err != nil { return cfg }
- defer f.Close()
-
- scanner := bufio.NewScanner(f)
- for scanner.Scan() {
- line := strings.TrimSpace(scanner.Text())
- if line == "" || strings.HasPrefix(line, "#") { continue }
- parts := strings.SplitN(line, "=", 2)
- if len(parts) != 2 { continue }
- key := strings.TrimSpace(parts[0])
- val := strings.TrimSpace(parts[1])
- if os.Getenv(key) == "" { os.Setenv(key, val) }
- }
-
- cfg.TGToken = os.Getenv("TG_BOT_TOKEN")
- allowedStr := os.Getenv("TG_ALLOWED_CHAT_IDS")
- if allowedStr != "" {
- for _, s := range strings.Split(allowedStr, ",") {
- s = strings.TrimSpace(s)
- if id, err := strconv.ParseInt(s, 10, 64); err == nil { cfg.AllowedIDs = append(cfg.AllowedIDs, id) }
- }
- }
- if p := os.Getenv("WRITE_PORT"); p != "" {
- if port, err := strconv.Atoi(p); err == nil { cfg.WritePort = port }
- }
- if p := os.Getenv("BOT_PORT"); p != "" {
- if port, err := strconv.Atoi(p); err == nil { cfg.ControlPort = port }
- }
- if p := os.Getenv("PREFER_IFACE"); p != "" { cfg.PreferIface = p }
-
- return cfg
-}
diff --git a/bot-go/internal/config/config.go b/bot-go/internal/config/config.go
deleted file mode 100644
index 4afaff69..00000000
--- a/bot-go/internal/config/config.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package config
-
-import (
- "bufio"
- "os"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
-)
-
-type Config struct {
- TGToken string
- AllowedIDs []int64
- WritePort int
- ControlPort int
- PreferIface string
- WriteDir string
-}
-
-func Load() *Config {
- cfg := &Config{
- WritePort: 8016,
- ControlPort: 8017,
- PreferIface: "WLAN",
- }
-
- _, filename, _, _ := runtime.Caller(0)
- srcDir := filepath.Dir(filename)
- rootDir := filepath.Dir(filepath.Dir(filepath.Dir(srcDir)))
- // Try multiple .env locations: bot-go/.env first, then blog/.env
- envPaths := []string{
- filepath.Join(rootDir, "bot-go", ".env"),
- filepath.Join(rootDir, ".env"),
- filepath.Join(filepath.Dir(rootDir), "bot", ".env"),
- }
- var f *os.File
- var err error
- for _, envPath := range envPaths {
- f, err = os.Open(envPath)
- if err == nil {
- break
- }
- }
- if err != nil {
- return cfg
- }
- defer f.Close()
-
- scanner := bufio.NewScanner(f)
- for scanner.Scan() {
- line := strings.TrimSpace(scanner.Text())
- if line == "" || strings.HasPrefix(line, "#") {
- continue
- }
- parts := strings.SplitN(line, "=", 2)
- if len(parts) != 2 {
- continue
- }
- key := strings.TrimSpace(parts[0])
- val := strings.TrimSpace(parts[1])
- if existing := os.Getenv(key); existing != "" {
- continue
- }
- os.Setenv(key, val)
- }
-
- cfg.TGToken = os.Getenv("TG_BOT_TOKEN")
- allowedStr := os.Getenv("TG_ALLOWED_CHAT_IDS")
- if allowedStr != "" {
- for _, s := range strings.Split(allowedStr, ",") {
- s = strings.TrimSpace(s)
- if s == "" {
- continue
- }
- if id, err := strconv.ParseInt(s, 10, 64); err == nil {
- cfg.AllowedIDs = append(cfg.AllowedIDs, id)
- }
- }
- }
-
- if p := os.Getenv("WRITE_PORT"); p != "" {
- if port, err := strconv.Atoi(p); err == nil {
- cfg.WritePort = port
- }
- }
- if p := os.Getenv("BOT_PORT"); p != "" {
- if port, err := strconv.Atoi(p); err == nil {
- cfg.ControlPort = port
- }
- }
- if p := os.Getenv("PREFER_IFACE"); p != "" {
- cfg.PreferIface = p
- }
-
- exe, _ := os.Executable(); exeDir := filepath.Dir(exe); cfg.WriteDir = filepath.Join(filepath.Dir(exeDir), "write-go"); if _, err := os.Stat(cfg.WriteDir); os.IsNotExist(err) { cfg.WriteDir = filepath.Join(filepath.Dir(rootDir), "write-go") }
-
- return cfg
-}
diff --git a/bot-go/internal/helpers/helpers.go b/bot-go/internal/helpers/helpers.go
deleted file mode 100644
index 5a9d48e2..00000000
--- a/bot-go/internal/helpers/helpers.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package helpers
-
-import (
- "fmt"
- "net"
- "strings"
- "time"
-)
-
-var lanIPCache struct {
- ip string
- ts time.Time
-}
-
-func GetLanIP(preferIface string) string {
- if lanIPCache.ip != "" && time.Since(lanIPCache.ts) < 30*time.Second {
- return lanIPCache.ip
- }
-
- ip := "0.0.0.0"
- ifaces, err := net.Interfaces()
- if err != nil {
- lanIPCache.ip = ip
- lanIPCache.ts = time.Now()
- return ip
- }
-
- type candidate struct {
- name string
- address string
- }
- var candidates []candidate
-
- for _, iface := range ifaces {
- addrs, err := iface.Addrs()
- if err != nil {
- continue
- }
- for _, addr := range addrs {
- ipNet, ok := addr.(*net.IPNet)
- if !ok {
- continue
- }
- ip4 := ipNet.IP.To4()
- if ip4 == nil {
- continue
- }
- if ip4.IsLoopback() {
- continue
- }
- ipStr := ip4.String()
- if strings.HasPrefix(ipStr, "169.254.") {
- continue
- }
- candidates = append(candidates, candidate{name: iface.Name, address: ipStr})
- }
- }
-
- prefLower := strings.ToLower(preferIface)
- for _, c := range candidates {
- if strings.Contains(strings.ToLower(c.name), prefLower) {
- ip = c.address
- lanIPCache.ip = ip
- lanIPCache.ts = time.Now()
- return ip
- }
- }
-
- for _, c := range candidates {
- n := strings.ToLower(c.name)
- if !strings.Contains(n, "vmware") && !strings.Contains(n, "virtual") && !strings.Contains(n, "vethernet") {
- ip = c.address
- lanIPCache.ip = ip
- lanIPCache.ts = time.Now()
- return ip
- }
- }
-
- if len(candidates) > 0 {
- ip = candidates[0].address
- }
-
- lanIPCache.ip = ip
- lanIPCache.ts = time.Now()
- return ip
-}
-
-func CheckPort(port int) bool {
- conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 1500*time.Millisecond)
- if err != nil {
- return false
- }
- conn.Close()
- return true
-}
-
-func WaitForReady(port int, timeoutMs int) bool {
- if timeoutMs <= 0 {
- timeoutMs = 30000
- }
- deadline := time.Now().Add(time.Duration(timeoutMs) * time.Millisecond)
- for time.Now().Before(deadline) {
- if CheckPort(port) {
- return true
- }
- time.Sleep(1500 * time.Millisecond)
- }
- return false
-}
-
-func MakeSlug(title string) string {
- dateStr := time.Now().Format("2006-01-02")
- slugPart := title
- // Replace non-word, non-CJK chars with -
- slugPart = strings.Map(func(r rune) rune {
- if (r >= 0x4e00 && r <= 0x9fff) || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' {
- return r
- }
- return '-'
- }, slugPart)
- slugPart = strings.Trim(slugPart, "-")
- slugPart = strings.ToLower(slugPart)
- if slugPart == "" {
- slugPart = "post"
- }
- return dateStr + "-" + slugPart
-}
-
-func SessionSummary(title string, categories, tags []string, content string) string {
- lines := 0
- chars := len([]rune(content))
- for _, ch := range content {
- if ch == '\n' {
- lines++
- }
- }
- if content != "" {
- lines++
- }
-
- displayTitle := title
- if displayTitle == "" {
- displayTitle = "未命名"
- }
-
- parts := []string{
- fmt.Sprintf("📝 %s", displayTitle),
- }
- if len(categories) > 0 {
- parts = append(parts, "📂 "+strings.Join(categories, "、"))
- }
- if len(tags) > 0 {
- parts = append(parts, "🏷 "+strings.Join(tags, "、"))
- }
- if chars > 0 {
- parts = append(parts, fmt.Sprintf("✍ %d 行 %d 字", lines, chars))
- }
- return strings.Join(parts, " · ")
-}
diff --git a/bot-go/internal/logutil/logutil.go b/bot-go/internal/logutil/logutil.go
deleted file mode 100644
index 3a953922..00000000
--- a/bot-go/internal/logutil/logutil.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package logutil
-
-import (
- "log/slog"
- "os"
-)
-
-var Logger *slog.Logger
-
-func Init(service string) {
- Logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
- Level: slog.LevelInfo,
- })).With("svc", service)
-}
diff --git a/bot-go/internal/sessions/sessions.go b/bot-go/internal/sessions/sessions.go
deleted file mode 100644
index 715b3afd..00000000
--- a/bot-go/internal/sessions/sessions.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package sessions
-
-import (
- "sync"
-
- "write-server/pkg/posts"
-)
-
-type Session struct {
- Title string `json:"title"`
- Slug string `json:"slug"`
- Content string `json:"content"`
- Categories []string `json:"categories"`
- Tags []string `json:"tags"`
- Author string `json:"author"`
- Draft bool `json:"draft"`
-}
-
-var mu sync.Mutex
-var sessions = make(map[int64]*Session)
-
-func GetSession(chatID int64) *Session {
- mu.Lock()
- defer mu.Unlock()
- if s, ok := sessions[chatID]; ok {
- return s
- }
- s := &Session{Author: "qunlin", Draft: true}
- sessions[chatID] = s
- return s
-}
-
-func ClearSession(chatID int64) {
- mu.Lock()
- defer mu.Unlock()
- delete(sessions, chatID)
-}
-
-func HasSession(chatID int64) bool {
- mu.Lock()
- defer mu.Unlock()
- _, ok := sessions[chatID]
- return ok
-}
-
-func Save(s *Session, isDraft bool) error {
- fm := posts.FrontMatter{
- Title: s.Title,
- Slug: s.Slug,
- Draft: isDraft,
- Categories: s.Categories,
- Tags: s.Tags,
- Author: s.Author,
- Layout: "post",
- }
-
- existing := posts.GetPost(s.Slug)
- if existing != nil {
- updates := map[string]interface{}{}
- updates["title"] = s.Title
- updates["draft"] = isDraft
- updates["categories"] = s.Categories
- updates["tags"] = s.Tags
- updates["author"] = s.Author
- _, err := posts.UpdatePost(s.Slug, updates, &s.Content)
- return err
- }
-
- _, err := posts.CreatePost(fm, s.Content)
- return err
-}
diff --git a/bot-go/internal/tgcore/tgcore.go b/bot-go/internal/tgcore/tgcore.go
deleted file mode 100644
index fd9853ad..00000000
--- a/bot-go/internal/tgcore/tgcore.go
+++ /dev/null
@@ -1,301 +0,0 @@
-package tgcore
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "net"
- "net/http"
- "strings"
- "time"
-)
-
-var API string
-
-func Init(token string) {
- API = "https://api.telegram.org/bot" + token
-}
-
-func TG(method string, body interface{}) (map[string]interface{}, error) {
- jsonBody, err := json.Marshal(body)
- if err != nil {
- return nil, err
- }
- resp, err := http.Post(API+"/"+method, "application/json", bytes.NewReader(jsonBody))
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- data, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- var result map[string]interface{}
- if err := json.Unmarshal(data, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func Allowed(chatID int64, allowedIDs []int64) bool {
- if len(allowedIDs) == 0 {
- return true
- }
- for _, id := range allowedIDs {
- if id == chatID {
- return true
- }
- }
- return false
-}
-
-func SendMessage(chatID int64, text string, opts map[string]interface{}) error {
- safeText := text
- body := map[string]interface{}{
- "chat_id": chatID,
- "text": safeText,
- "parse_mode": "HTML",
- "disable_web_page_preview": true,
- }
- if opts != nil {
- for k, v := range opts {
- body[k] = v
- }
- }
-
- _, err := TG("sendMessage", body)
- if err != nil && (strings.Contains(err.Error(), "parse") || strings.Contains(err.Error(), "HTML")) {
- delete(body, "parse_mode")
- _, err = TG("sendMessage", body)
- }
- return err
-}
-
-// TgHandler is the callback handler interface
-type TgHandler interface {
- List(chatID int64, page string)
- Links(chatID int64, page string)
- Feeds(chatID int64, page string)
- // Session callbacks
- SessionCallback(chatID int64, action string)
- MenuCallback(chatID int64, action string)
-}
-
-func HandleCallback(chatID int64, queryID string, data string, h TgHandler) {
- if data == "" {
- return
- }
-
- // Session callbacks
- if strings.HasPrefix(data, "session_") {
- action := strings.TrimPrefix(data, "session_")
- if h != nil {
- h.SessionCallback(chatID, action)
- }
- TG("answerCallbackQuery", map[string]interface{}{"callback_query_id": queryID})
- return
- }
-
- // Menu callbacks
- if strings.HasPrefix(data, "menu_") {
- action := strings.TrimPrefix(data, "menu_")
- if h != nil {
- h.MenuCallback(chatID, action)
- }
- TG("answerCallbackQuery", map[string]interface{}{"callback_query_id": queryID})
- return
- }
-
- // Pagination callbacks: list_N, links_N, feeds_N
- parts := strings.SplitN(data, "_", 2)
- if len(parts) == 2 {
- cmd := parts[0]
- page := parts[1]
- if h != nil {
- switch cmd {
- case "list":
- h.List(chatID, page)
- case "links":
- h.Links(chatID, page)
- case "feeds":
- h.Feeds(chatID, page)
- }
- }
- }
- TG("answerCallbackQuery", map[string]interface{}{"callback_query_id": queryID})
-}
-
-func GetInt64(m map[string]interface{}, key string) int64 {
- if v, ok := m[key]; ok {
- switch n := v.(type) {
- case float64:
- return int64(n)
- case int64:
- return n
- }
- }
- return 0
-}
-
-func GetString(m map[string]interface{}, key string) string {
- if v, ok := m[key]; ok {
- if s, ok := v.(string); ok {
- return s
- }
- }
- return ""
-}
-
-// === Inlined from helpers ===
-
-var lanIPCache struct {
- ip string
- ts time.Time
-}
-
-func GetLanIP(preferIface string) string {
- if lanIPCache.ip != "" && time.Since(lanIPCache.ts) < 30*time.Second {
- return lanIPCache.ip
- }
-
- ip := "0.0.0.0"
- ifaces, err := net.Interfaces()
- if err != nil {
- lanIPCache.ip = ip
- lanIPCache.ts = time.Now()
- return ip
- }
-
- type candidate struct {
- name string
- address string
- }
- var candidates []candidate
-
- for _, iface := range ifaces {
- addrs, err := iface.Addrs()
- if err != nil {
- continue
- }
- for _, addr := range addrs {
- ipNet, ok := addr.(*net.IPNet)
- if !ok {
- continue
- }
- ip4 := ipNet.IP.To4()
- if ip4 == nil {
- continue
- }
- if ip4.IsLoopback() {
- continue
- }
- ipStr := ip4.String()
- if strings.HasPrefix(ipStr, "169.254.") {
- continue
- }
- candidates = append(candidates, candidate{name: iface.Name, address: ipStr})
- }
- }
-
- prefLower := strings.ToLower(preferIface)
- for _, c := range candidates {
- if strings.Contains(strings.ToLower(c.name), prefLower) {
- ip = c.address
- lanIPCache.ip = ip
- lanIPCache.ts = time.Now()
- return ip
- }
- }
-
- for _, c := range candidates {
- n := strings.ToLower(c.name)
- if !strings.Contains(n, "vmware") && !strings.Contains(n, "virtual") && !strings.Contains(n, "vethernet") {
- ip = c.address
- lanIPCache.ip = ip
- lanIPCache.ts = time.Now()
- return ip
- }
- }
-
- if len(candidates) > 0 {
- ip = candidates[0].address
- }
-
- lanIPCache.ip = ip
- lanIPCache.ts = time.Now()
- return ip
-}
-
-func CheckPort(port int) bool {
- conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 1500*time.Millisecond)
- if err != nil {
- return false
- }
- conn.Close()
- return true
-}
-
-func WaitForReady(port int, timeoutMs int) bool {
- if timeoutMs <= 0 {
- timeoutMs = 30000
- }
- deadline := time.Now().Add(time.Duration(timeoutMs) * time.Millisecond)
- for time.Now().Before(deadline) {
- if CheckPort(port) {
- return true
- }
- time.Sleep(1500 * time.Millisecond)
- }
- return false
-}
-
-func MakeSlug(title string) string {
- dateStr := time.Now().Format("2006-01-02")
- slugPart := title
- // Replace non-word, non-CJK chars with -
- slugPart = strings.Map(func(r rune) rune {
- if (r >= 0x4e00 && r <= 0x9fff) || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' {
- return r
- }
- return '-'
- }, slugPart)
- slugPart = strings.Trim(slugPart, "-")
- slugPart = strings.ToLower(slugPart)
- if slugPart == "" {
- slugPart = "post"
- }
- return dateStr + "-" + slugPart
-}
-
-func SessionSummary(title string, categories, tags []string, content string) string {
- lines := 0
- chars := len([]rune(content))
- for _, ch := range content {
- if ch == '\n' {
- lines++
- }
- }
- if content != "" {
- lines++
- }
-
- displayTitle := title
- if displayTitle == "" {
- displayTitle = "未命名"
- }
-
- parts := []string{
- fmt.Sprintf("📝 %s", displayTitle),
- }
- if len(categories) > 0 {
- parts = append(parts, "📂 "+strings.Join(categories, "、"))
- }
- if len(tags) > 0 {
- parts = append(parts, "🏷 "+strings.Join(tags, "、"))
- }
- if chars > 0 {
- parts = append(parts, fmt.Sprintf("✍ %d 行 %d 字", lines, chars))
- }
- return strings.Join(parts, " · ")
-}
diff --git a/bot-go/internal/tghandlers/tghandlers.go b/bot-go/internal/tghandlers/tghandlers.go
deleted file mode 100644
index 2694926c..00000000
--- a/bot-go/internal/tghandlers/tghandlers.go
+++ /dev/null
@@ -1,622 +0,0 @@
-package tghandlers
-
-import (
- "fmt"
- "math"
- "strconv"
- "strings"
-
- "write-server/internal/helpers"
- "write-server/internal/sessions"
- "write-server/internal/tgcore"
- "write-server/internal/writeserver"
-
- "write-server/pkg/posts"
- "write-server/pkg/rssapi"
- "write-server/pkg/recycle"
-)
-
-type Handlers struct {
- cfg *Config
-}
-
-type Config struct {
- WritePort int
- ControlPort int
- PreferIface string
-}
-
-func New(cfg *Config) *Handlers {
- return &Handlers{cfg: cfg}
-}
-
-func (h *Handlers) dispatchList(chatID int64, pageStr string) error {
- page, _ := strconv.Atoi(pageStr)
- if page < 1 { page = 1 }
- perPage := 5
-
- list, total, _ := posts.ListPosts("", page, 9999, true)
- if len(list) == 0 {
- return tgcore.SendMessage(chatID, "📭 暂无文章。", nil)
- }
-
- totalPages := int(math.Ceil(float64(total) / float64(perPage)))
- if page > totalPages { page = totalPages }
-
- start := (page - 1) * perPage
- end := start + perPage
- if end > len(list) { end = len(list) }
- pageItems := list[start:end]
-
- header := fmt.Sprintf("📋 文章列表 共%d篇 · 第%d/%d页", total, page, totalPages)
- lines := []string{header}
- for _, p := range pageItems {
- emoji := "✅"
- if p.Draft { emoji = "📝" }
- date := ""
- if len(p.Date) >= 10 { date = p.Date[:10] }
- lines = append(lines, fmt.Sprintf("%s %s /edit_%s %s", emoji, p.Title, p.Slug, date))
- }
-
- kb := map[string]interface{}{}
- var nav []map[string]interface{}
- if page > 1 {
- nav = append(nav, map[string]interface{}{"text": "◀ 上一页", "callback_data": fmt.Sprintf("list_%d", page-1)})
- }
- if page < totalPages {
- nav = append(nav, map[string]interface{}{"text": "下一页 ▶", "callback_data": fmt.Sprintf("list_%d", page+1)})
- }
- if len(nav) > 0 {
- kb["inline_keyboard"] = []interface{}{nav}
- tgcore.TG("sendMessage", map[string]interface{}{
- "chat_id": chatID,
- "text": strings.Join(lines, "\n"),
- "parse_mode": "HTML",
- "reply_markup": kb,
- "disable_web_page_preview": true,
- })
- return nil
- }
- return tgcore.SendMessage(chatID, strings.Join(lines, "\n"), nil)
-}
-
-func (h *Handlers) List(chatID int64, page string) { h.dispatchList(chatID, page) }
-
-
-func (h *Handlers) Start(chatID int64) {
- ip := helpers.GetLanIP(h.cfg.PreferIface)
- ready := EnsureWriteReady(chatID, h.cfg.WritePort)
- if ready {
- kb := map[string]interface{}{
- "inline_keyboard": []interface{}{
- []map[string]interface{}{
- {"text": "📝 开始写作", "callback_data": "menu_new"},
- {"text": "📋 文章列表", "callback_data": "list_1"},
- },
- []map[string]interface{}{
- {"text": "🔗 友链管理", "callback_data": "links_1"},
- {"text": "📡 订阅源", "callback_data": "feeds_1"},
- },
- []map[string]interface{}{
- {"text": "📊 网站数据", "callback_data": "menu_stats"},
- {"text": "ℹ️ 帮助", "callback_data": "menu_help"},
- },
- },
- }
- tgcore.TG("sendMessage", map[string]interface{}{
- "chat_id": chatID,
- "text": fmt.Sprintf("✅ Write 已就绪\n\n📱 手机: http://%s:%d/\n💻 电脑: http://localhost:%d/\n📰 文章: http://%s:1313/posts/", ip, h.cfg.WritePort, h.cfg.WritePort, ip),
- "parse_mode": "HTML",
- "reply_markup": kb,
- "disable_web_page_preview": true,
- })
- return
- }
- tgcore.SendMessage(chatID, "❌ 启动超时,请查看控制台日志。", nil)
-}
-
-func (h *Handlers) Stop(chatID int64) {
- if writeserver.StopWriteServer() {
- tgcore.SendMessage(chatID, "⚡ Write 已停止。", nil)
- } else {
- tgcore.SendMessage(chatID, "⏸ Write 未运行。", nil)
- }
-}
-
-func (h *Handlers) Status(chatID int64) {
- s := writeserver.GetWriteStatus()
- status := "⏸ 未运行"
- if s["running"].(bool) { status = "✅ 运行中" }
- if s["starting"].(bool) { status = "⏳ 启动中..." }
- lines := []string{"Write 状态: " + status}
- if s["running"].(bool) {
- ip := helpers.GetLanIP(h.cfg.PreferIface)
- lines = append(lines, fmt.Sprintf("🌐 局域网: http://%s:%d", ip, h.cfg.WritePort))
- lines = append(lines, fmt.Sprintf("💻 本地: http://localhost:%d", h.cfg.WritePort))
- }
- tgcore.SendMessage(chatID, strings.Join(lines, "\n"), nil)
-}
-
-func (h *Handlers) IP(chatID int64) {
- ip := helpers.GetLanIP(h.cfg.PreferIface)
- tgcore.SendMessage(chatID, fmt.Sprintf("📱 手机访问: http://%s:%d\n💻 电脑访问: http://localhost:%d", ip, h.cfg.WritePort, h.cfg.WritePort), nil)
-}
-
-func (h *Handlers) Help(chatID int64) {
- kb := map[string]interface{}{
- "inline_keyboard": []interface{}{
- []map[string]interface{}{{"text": "📝 开始写作", "callback_data": "menu_new"}},
- []map[string]interface{}{{"text": "📋 文章列表", "callback_data": "list_1"}},
- []map[string]interface{}{{"text": "🔗 友链", "callback_data": "links_1"}, {"text": "📡 订阅", "callback_data": "feeds_1"}},
- },
- }
- tgcore.TG("sendMessage", map[string]interface{}{
- "chat_id": chatID,
- "text": "🤖 WriteBot 命令\n\n📝 /new 标题 — 开始写作\n✏️ /edit slug — 编辑文章\n📋 /list — 文章列表\n🔍 /view slug — 查看文章\n🗑 /delete slug — 删除文章\n\n✍️ 写作中:\n/publish — 发布\n/draft — 存草稿\n/preview — 预览\n/cancel — 丢弃\n\n🔗 友链 /links\n📡 订阅 /feeds\n📊 /stats 统计",
- "parse_mode": "HTML",
- "reply_markup": kb,
- "disable_web_page_preview": true,
- })
-}
-
-
-// --- Writing commands ---
-
-func (h *Handlers) New(chatID int64, title string) {
- if title == "" {
- tgcore.SendMessage(chatID, "📝 请提供标题:/new 你的标题", nil)
- return
- }
- s := sessions.GetSession(chatID)
- s.Title = title
- s.Slug = helpers.MakeSlug(title)
- s.Content = ""
- s.Categories = nil
- s.Tags = nil
- s.Draft = true
- s.Author = "qunlin"
-
- kb := map[string]interface{}{
- "inline_keyboard": []interface{}{
- []map[string]interface{}{
- {"text": "📂 分类", "callback_data": "session_categories"},
- {"text": "🏷 标签", "callback_data": "session_tags"},
- },
- []map[string]interface{}{
- {"text": "🚀 发布", "callback_data": "session_publish"},
- {"text": "📄 存草稿", "callback_data": "session_draft"},
- },
- []map[string]interface{}{
- {"text": "🔍 预览", "callback_data": "session_preview"},
- {"text": "❌ 丢弃", "callback_data": "session_cancel"},
- },
- },
- }
- tgcore.TG("sendMessage", map[string]interface{}{
- "chat_id": chatID,
- "text": fmt.Sprintf("📝 %s\n\n直接发消息开始写作\n用下方按钮管理文章属性", title),
- "parse_mode": "HTML",
- "reply_markup": kb,
- })
-}
-
-func (h *Handlers) Edit(chatID int64, slug string) {
- if slug == "" {
- tgcore.SendMessage(chatID, "✏️ 用法:/edit slug\n先用 /list 查看 slug。", nil)
- return
- }
- p := posts.GetPost(slug)
- if p == nil {
- tgcore.SendMessage(chatID, "❌ 未找到该文章。", nil)
- return
- }
-
- s := sessions.GetSession(chatID)
- s.Title = p.FrontMatter.Title
- s.Slug = p.FrontMatter.Slug
- s.Content = p.Content
- s.Categories = p.FrontMatter.Categories
- s.Tags = p.FrontMatter.Tags
- s.Author = p.FrontMatter.Author
- if s.Author == "" { s.Author = "qunlin" }
- s.Draft = p.FrontMatter.Draft
-
- summary := helpers.SessionSummary(s.Title, s.Categories, s.Tags, s.Content)
- kb := map[string]interface{}{
- "inline_keyboard": []interface{}{
- []map[string]interface{}{
- {"text": "📂 分类", "callback_data": "session_categories"},
- {"text": "🏷 标签", "callback_data": "session_tags"},
- },
- []map[string]interface{}{
- {"text": "🚀 发布", "callback_data": "session_publish"},
- {"text": "📄 存草稿", "callback_data": "session_draft"},
- },
- []map[string]interface{}{
- {"text": "🔍 预览", "callback_data": "session_preview"},
- {"text": "❌ 丢弃", "callback_data": "session_cancel"},
- },
- },
- }
- tgcore.TG("sendMessage", map[string]interface{}{
- "chat_id": chatID,
- "text": fmt.Sprintf("✏️ %s\n\n%s\n\n直接发消息继续写作", s.Title, summary),
- "parse_mode": "HTML",
- "reply_markup": kb,
- })
-}
-
-func (h *Handlers) Delete(chatID int64, slug string) {
- if slug == "" {
- tgcore.SendMessage(chatID, "🗑 用法:/delete slug\n先用 /list 查看。", nil)
- return
- }
- p := posts.GetPost(slug)
- if p == nil {
- tgcore.SendMessage(chatID, "❌ 未找到该文章。", nil)
- return
- }
- if err := recycle.MoveToRecycle(slug, p.DirPath, p.FrontMatter.Title); err != nil {
- tgcore.SendMessage(chatID, "❌ "+err.Error(), nil)
- return
- }
- tgcore.SendMessage(chatID, "🗑 "+p.FrontMatter.Title+" 已移入回收站(30天后自动清除)。", nil)
-}
-
-func (h *Handlers) View(chatID int64, slug string) {
- if slug == "" {
- tgcore.SendMessage(chatID, "🔍 用法:/view slug", nil)
- return
- }
- p := posts.GetPost(slug)
- if p == nil {
- tgcore.SendMessage(chatID, "❌ 未找到该文章。", nil)
- return
- }
- draftMark := "✅"
- if p.FrontMatter.Draft { draftMark = "📝" }
- header := fmt.Sprintf("%s %s\n📂 %s 🏷 %s ✍️ %s 📅 %s",
- draftMark, p.FrontMatter.Title,
- strings.Join(p.FrontMatter.Categories, "、"),
- strings.Join(p.FrontMatter.Tags, "、"),
- p.FrontMatter.Author,
- p.FrontMatter.Date[:10])
-
- content := p.Content
- if len([]rune(content)) > 3500 {
- content = string([]rune(content)[:3500]) + "\n\n..."
- }
- tgcore.SendMessage(chatID, header+"\n\n"+content, nil)
-}
-
-
-func (h *Handlers) Title(chatID int64, arg string) {
- s := sessions.GetSession(chatID)
- if arg == "" {
- tgcore.SendMessage(chatID, fmt.Sprintf("当前标题:%s\n修改:/title 新标题", s.Title), nil)
- return
- }
- s.Title = arg
- tgcore.SendMessage(chatID, "✅ 标题已更新:"+arg+"", nil)
-}
-
-func (h *Handlers) Categories(chatID int64, arg string) {
- s := sessions.GetSession(chatID)
- if arg == "" {
- tgcore.SendMessage(chatID, fmt.Sprintf("当前分类:%s\n修改:/categories 分类1,分类2", strings.Join(s.Categories, "、")), nil)
- return
- }
- s.Categories = nil
- for _, item := range strings.Split(arg, ",") {
- item = strings.TrimSpace(item)
- if item != "" { s.Categories = append(s.Categories, item) }
- }
- tgcore.SendMessage(chatID, "✅ 分类:"+strings.Join(s.Categories, "、")+"", nil)
-}
-
-func (h *Handlers) Tags(chatID int64, arg string) {
- s := sessions.GetSession(chatID)
- if arg == "" {
- tgcore.SendMessage(chatID, fmt.Sprintf("当前标签:%s\n修改:/tags 标签1,标签2", strings.Join(s.Tags, "、")), nil)
- return
- }
- s.Tags = nil
- for _, item := range strings.Split(arg, ",") {
- item = strings.TrimSpace(item)
- if item != "" { s.Tags = append(s.Tags, item) }
- }
- tgcore.SendMessage(chatID, "✅ 标签:"+strings.Join(s.Tags, "、")+"", nil)
-}
-
-func (h *Handlers) Author(chatID int64, arg string) {
- s := sessions.GetSession(chatID)
- if arg == "" {
- tgcore.SendMessage(chatID, fmt.Sprintf("当前作者:%s\n修改:/author 新作者", s.Author), nil)
- return
- }
- s.Author = arg
- tgcore.SendMessage(chatID, "✅ 作者:"+arg+"", nil)
-}
-
-func (h *Handlers) Publish(chatID int64) {
- s := sessions.GetSession(chatID)
- if s.Title == "" {
- tgcore.SendMessage(chatID, "❌ 请先 /new 标题 开始写作。", nil)
- return
- }
- if err := sessions.Save(s, false); err != nil {
- tgcore.SendMessage(chatID, "❌ "+err.Error(), nil)
- return
- }
- sessions.ClearSession(chatID)
- tgcore.SendMessage(chatID, "🚀 "+s.Title+" 已发布!", nil)
-}
-
-func (h *Handlers) Draft(chatID int64) {
- s := sessions.GetSession(chatID)
- if s.Title == "" {
- tgcore.SendMessage(chatID, "❌ 请先 /new 标题 开始写作。", nil)
- return
- }
- if err := sessions.Save(s, true); err != nil {
- tgcore.SendMessage(chatID, "❌ "+err.Error(), nil)
- return
- }
- sessions.ClearSession(chatID)
- tgcore.SendMessage(chatID, "📄 "+s.Title+" 已存为草稿。", nil)
-}
-
-func (h *Handlers) Preview(chatID int64) {
- s := sessions.GetSession(chatID)
- if s.Title == "" {
- tgcore.SendMessage(chatID, "❌ 请先 /new 标题 开始写作。", nil)
- return
- }
- summary := helpers.SessionSummary(s.Title, s.Categories, s.Tags, s.Content)
- preview := s.Content
- if len([]rune(preview)) > 3500 {
- preview = string([]rune(preview)[:3500]) + "\n\n... (内容过长)"
- }
- tgcore.SendMessage(chatID, summary+"\n\n"+preview, nil)
-}
-
-func (h *Handlers) Cancel(chatID int64) {
- sessions.ClearSession(chatID)
- tgcore.SendMessage(chatID, "🗑 已丢弃当前写作内容。", nil)
-}
-
-
-// --- Links ---
-
-func (h *Handlers) Links(chatID int64, page string) { h.dispatchLinks(chatID, page) }
-
-func (h *Handlers) dispatchLinks(chatID int64, pageStr string) error {
- page, _ := strconv.Atoi(pageStr)
- if page < 1 { page = 1 }
- perPage := 8
-
- links, err := rssapi.GetLinks(true)
- if err != nil || len(links) == 0 {
- return tgcore.SendMessage(chatID, "🔗 暂无友链。", nil)
- }
-
- totalPages := int(math.Ceil(float64(len(links)) / float64(perPage)))
- if page > totalPages { page = totalPages }
- start := (page - 1) * perPage
- end := start + perPage
- if end > len(links) { end = len(links) }
-
- lines := []string{fmt.Sprintf("🔗 友链 共%d个 · 第%d/%d页", len(links), page, totalPages)}
- for _, l := range links[start:end] {
- icon := "🌐"
- if l.Hidden { icon = "🔒" }
- lines = append(lines, fmt.Sprintf("%s %s\n %s", icon, l.Name, l.URL))
- }
-
- kb := map[string]interface{}{}
- var nav []map[string]interface{}
- if page > 1 {
- nav = append(nav, map[string]interface{}{"text": "◀", "callback_data": fmt.Sprintf("links_%d", page-1)})
- }
- if page < totalPages {
- nav = append(nav, map[string]interface{}{"text": "▶", "callback_data": fmt.Sprintf("links_%d", page+1)})
- }
- if len(nav) > 0 {
- kb["inline_keyboard"] = []interface{}{nav}
- tgcore.TG("sendMessage", map[string]interface{}{
- "chat_id": chatID, "text": strings.Join(lines, "\n"),
- "parse_mode": "HTML", "reply_markup": kb, "disable_web_page_preview": true,
- })
- return nil
- }
- return tgcore.SendMessage(chatID, strings.Join(lines, "\n"), nil)
-}
-
-func (h *Handlers) LinkAdd(chatID int64, arg string) {
- if arg == "" {
- tgcore.SendMessage(chatID, "用法:/link_add url 名称", nil)
- return
- }
- parts := strings.SplitN(arg, " ", 2)
- url := parts[0]
- name := ""
- if len(parts) > 1 { name = parts[1] }
- if err := rssapi.AddLink(rssapi.Link{URL: url, Name: name}); err != nil {
- tgcore.SendMessage(chatID, "❌ "+err.Error(), nil)
- return
- }
- tgcore.SendMessage(chatID, "✅ 友链已添加:"+url, nil)
-}
-
-func (h *Handlers) LinkDel(chatID int64, url string) {
- if url == "" { tgcore.SendMessage(chatID, "用法:/link_del url", nil); return }
- if err := rssapi.DeleteLink(url); err != nil {
- tgcore.SendMessage(chatID, "❌ "+err.Error(), nil)
- return
- }
- tgcore.SendMessage(chatID, "✅ 友链已删除:"+url, nil)
-}
-
-func (h *Handlers) LinkToggle(chatID int64, url string) {
- if url == "" { tgcore.SendMessage(chatID, "用法:/link_toggle url", nil); return }
- links, _ := rssapi.GetLinks(true)
- var target *rssapi.Link
- for i := range links {
- if links[i].URL == url { target = &links[i]; break }
- }
- if target == nil { tgcore.SendMessage(chatID, "❌ 未找到该友链。", nil); return }
-
- newHidden := !target.Hidden
- rssapi.UpdateLink(url, map[string]interface{}{"hidden": newHidden})
- label := "🌐 已显示"
- if newHidden { label = "🔒 已隐藏" }
- tgcore.SendMessage(chatID, label+": "+url, nil)
-}
-
-// --- Feeds ---
-
-func (h *Handlers) Feeds(chatID int64, page string) { h.dispatchFeeds(chatID, page) }
-
-func (h *Handlers) dispatchFeeds(chatID int64, pageStr string) error {
- page, _ := strconv.Atoi(pageStr)
- if page < 1 { page = 1 }
- perPage := 8
-
- feeds, err := rssapi.GetFeeds()
- if err != nil || len(feeds) == 0 {
- return tgcore.SendMessage(chatID, "📡 暂无订阅源。", nil)
- }
-
- totalPages := int(math.Ceil(float64(len(feeds)) / float64(perPage)))
- if page > totalPages { page = totalPages }
- start := (page - 1) * perPage
- end := start + perPage
- if end > len(feeds) { end = len(feeds) }
-
- lines := []string{fmt.Sprintf("📡 订阅源 共%d个 · 第%d/%d页", len(feeds), page, totalPages)}
- for _, f := range feeds[start:end] {
- title := f.FeedTitle
- if title == "" { title = f.URL }
- if len([]rune(title)) > 50 { title = string([]rune(title)[:50]) }
- lines = append(lines, fmt.Sprintf("🗜 %s\n %s", title, f.URL))
- }
-
- kb := map[string]interface{}{}
- var nav []map[string]interface{}
- if page > 1 {
- nav = append(nav, map[string]interface{}{"text": "◀", "callback_data": fmt.Sprintf("feeds_%d", page-1)})
- }
- if page < totalPages {
- nav = append(nav, map[string]interface{}{"text": "▶", "callback_data": fmt.Sprintf("feeds_%d", page+1)})
- }
- if len(nav) > 0 {
- kb["inline_keyboard"] = []interface{}{nav}
- tgcore.TG("sendMessage", map[string]interface{}{
- "chat_id": chatID, "text": strings.Join(lines, "\n"),
- "parse_mode": "HTML", "reply_markup": kb, "disable_web_page_preview": true,
- })
- return nil
- }
- return tgcore.SendMessage(chatID, strings.Join(lines, "\n"), nil)
-}
-
-func (h *Handlers) FeedAdd(chatID int64, arg string) {
- if arg == "" { tgcore.SendMessage(chatID, "用法:/feed_add url", nil); return }
- parts := strings.SplitN(arg, " ", 2)
- url := parts[0]
- title := ""
- if len(parts) > 1 { title = parts[1] }
- if err := rssapi.AddFeed(url, title); err != nil {
- tgcore.SendMessage(chatID, "❌ "+err.Error(), nil)
- return
- }
- tgcore.SendMessage(chatID, "✅ 订阅源已添加:"+url, nil)
-}
-
-func (h *Handlers) FeedDel(chatID int64, url string) {
- if url == "" { tgcore.SendMessage(chatID, "用法:/feed_del url", nil); return }
- if err := rssapi.DeleteFeed(url); err != nil {
- tgcore.SendMessage(chatID, "❌ "+err.Error(), nil)
- return
- }
- tgcore.SendMessage(chatID, "✅ 订阅源已删除:"+url, nil)
-}
-
-// --- Stats ---
-
-func (h *Handlers) Stats(chatID int64) {
- postCount := posts.GetPostCount()
- linkCount := 0
- feedCount := 0
- if links, err := rssapi.GetLinks(true); err == nil { linkCount = len(links) }
- if feeds, err := rssapi.GetFeeds(); err == nil { feedCount = len(feeds) }
-
- tgcore.SendMessage(chatID, fmt.Sprintf("📊 网站数据\n\n📝 文章:%d 篇\n🔗 友链:%d 个\n📡 订阅源:%d 个\n\n🌐 usj.cc", postCount, linkCount, feedCount), nil)
-}
-
-
-// --- EnsureWriteReady ---
-
-func EnsureWriteReady(chatID int64, writePort int) bool {
- s := writeserver.GetWriteStatus()
- running, _ := s["running"].(bool)
- starting, _ := s["starting"].(bool)
-
- if running {
- writeserver.StartHugoIfNeeded()
- return true
- }
- if starting {
- tgcore.SendMessage(chatID, "⌛ Write 正在启动...", nil)
- if helpers.WaitForReady(writePort, 0) {
- writeserver.SetWriteReady(true)
- writeserver.StartHugoIfNeeded()
- return true
- }
- return false
- }
-
- result := writeserver.StartWriteServer()
- if started, ok := result["started"].(bool); !ok || !started {
- return false
- }
- tgcore.SendMessage(chatID, "⌛ 正在启动 Write...", nil)
- if helpers.WaitForReady(writePort, 0) {
- writeserver.SetWriteReady(true)
- writeserver.StartHugoIfNeeded()
- return true
- }
- return false
-}
-
-// --- Callback handlers for inline buttons ---
-
-func (h *Handlers) SessionCallback(chatID int64, action string) {
- switch action {
- case "categories":
- s := sessions.GetSession(chatID)
- tgcore.SendMessage(chatID, fmt.Sprintf("📂 当前分类:%s\n\n请发送新的分类(逗号分隔):", strings.Join(s.Categories, "、")), nil)
- case "tags":
- s := sessions.GetSession(chatID)
- tgcore.SendMessage(chatID, fmt.Sprintf("🏷 当前标签:%s\n\n请发送新的标签(逗号分隔):", strings.Join(s.Tags, "、")), nil)
- case "publish":
- h.Publish(chatID)
- case "draft":
- h.Draft(chatID)
- case "preview":
- h.Preview(chatID)
- case "cancel":
- h.Cancel(chatID)
- }
-}
-
-func (h *Handlers) MenuCallback(chatID int64, action string) {
- switch action {
- case "new":
- tgcore.SendMessage(chatID, "📝 请输入:/new 文章标题", nil)
- case "help":
- h.Help(chatID)
- case "stats":
- h.Stats(chatID)
- }
-}
diff --git a/bot-go/internal/writeserver/writeserver.go b/bot-go/internal/writeserver/writeserver.go
deleted file mode 100644
index 2fb8aa51..00000000
--- a/bot-go/internal/writeserver/writeserver.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package writeserver
-
-import (
- "fmt"
- "net"
- "os/exec"
- "path/filepath"
- "sync"
- "time"
-)
-
-var (
- mu sync.Mutex
- writeProc *exec.Cmd
- writeReady bool
- hugoProc *exec.Cmd
- WritePort int
- WriteDir string
-)
-
-func Init(writePort int, writeDir string) {
- WritePort = writePort
- WriteDir = writeDir
-}
-
-func StartWriteServer() map[string]interface{} {
- mu.Lock()
- defer mu.Unlock()
-
- if writeProc != nil {
- if writeReady {
- return map[string]interface{}{"started": false, "reason": "already_running"}
- }
- return map[string]interface{}{"started": false, "reason": "starting"}
- }
-
- writeReady = false
- writeProc = exec.Command(filepath.Join(WriteDir, "writeserver.exe"))
- writeProc.Dir = WriteDir
-
- if err := writeProc.Start(); err != nil {
- writeProc = nil
- return map[string]interface{}{"started": false, "reason": err.Error()}
- }
-
- writeReady = true
-
- go func() {
- err := writeProc.Wait()
- mu.Lock()
- defer mu.Unlock()
- fmt.Printf("[write] exited: %v\n", err)
- writeProc = nil
- writeReady = false
- }()
-
- return map[string]interface{}{"started": true}
-}
-
-func StopWriteServer() bool {
- mu.Lock()
- defer mu.Unlock()
-
- if hugoProc != nil {
- killProcessTree(hugoProc)
- hugoProc = nil
- }
-
- if writeProc == nil {
- return false
- }
-
- killProcessTree(writeProc)
- writeProc = nil
- writeReady = false
- return true
-}
-
-func killProcessTree(cmd *exec.Cmd) {
- if cmd == nil || cmd.Process == nil {
- return
- }
- // On Windows, use taskkill /T to kill process tree
- pid := cmd.Process.Pid
- killCmd := exec.Command("taskkill", "/PID", fmt.Sprintf("%d", pid), "/T", "/F")
- killCmd.Run()
- cmd.Process.Kill()
-}
-
-func StartHugoIfNeeded() {
- mu.Lock()
- defer mu.Unlock()
-
- if hugoProc != nil {
- return
- }
-
- hugoDir := WriteDir + "/.."
- hugoProc = exec.Command("hugo", "server", "--bind", "0.0.0.0", "--port", "1313", "--disableFastRender")
- hugoProc.Dir = hugoDir
-
- if err := hugoProc.Start(); err != nil {
- fmt.Printf("[hugo] start error: %v\n", err)
- hugoProc = nil
- return
- }
-
- fmt.Println("[hugo] started on 0.0.0.0:1313")
-
- go func() {
- err := hugoProc.Wait()
- mu.Lock()
- defer mu.Unlock()
- fmt.Printf("[hugo] exited: %v\n", err)
- hugoProc = nil
- }()
-}
-
-func GetWriteStatus() map[string]interface{} {
- mu.Lock()
- running := writeProc != nil && writeReady
- starting := writeProc != nil && !writeReady
- mu.Unlock()
-
- // Also check via TCP if we are not tracking a process
- if !running && !starting {
- conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", WritePort), 2*time.Second)
- if err == nil {
- conn.Close()
- running = true
- }
- }
-
- return map[string]interface{}{
- "running": running,
- "starting": starting,
- }
-}
-
-func SetWriteReady(ready bool) {
- mu.Lock()
- defer mu.Unlock()
- writeReady = ready
-}
-
-func IsWriteReady() bool {
- mu.Lock()
- defer mu.Unlock()
- return writeReady
-}
-
-func IsWriteRunning() bool {
- mu.Lock()
- defer mu.Unlock()
- return writeProc != nil
-}
diff --git a/bot-go/start_bot.bat b/bot-go/start_bot.bat
deleted file mode 100644
index 20305000..00000000
--- a/bot-go/start_bot.bat
+++ /dev/null
@@ -1,5 +0,0 @@
-@echo off
-title WriteBot
-cd /d E:\GitHub\blog\bot-go
-echo [%date% %time%] WriteBot starting...
-writebot.exe
diff --git a/write/.recycle/meta/2026-06-01-深圳真的好热.json b/write/.recycle/meta/2026-06-01-深圳真的好热.json
new file mode 100644
index 00000000..07465d5e
--- /dev/null
+++ b/write/.recycle/meta/2026-06-01-深圳真的好热.json
@@ -0,0 +1,7 @@
+{
+ "slug": "2026-06-01-深圳真的好热",
+ "title": "深圳真的好热",
+ "deletedAt": "2026-06-01T16:21:45.062Z",
+ "originalPath": "E:\\GitHub\\blog\\content\\post\\2026\\2026-06-01-深圳真的好热",
+ "expiresAt": "2026-07-01T16:21:45.062Z"
+}
\ No newline at end of file
diff --git a/write/.recycle/meta/2026-06-01-深圳真的热.json b/write/.recycle/meta/2026-06-01-深圳真的热.json
new file mode 100644
index 00000000..49bd22d7
--- /dev/null
+++ b/write/.recycle/meta/2026-06-01-深圳真的热.json
@@ -0,0 +1,7 @@
+{
+ "slug": "2026-06-01-深圳真的热",
+ "title": "深圳真的热",
+ "deletedAt": "2026-06-01T14:59:57.582Z",
+ "originalPath": "E:\\GitHub\\blog\\content\\post\\2026\\2026-06-01-深圳真的热",
+ "expiresAt": "2026-07-01T14:59:57.582Z"
+}
\ No newline at end of file
diff --git a/write/.recycle/meta/202605311204.json b/write/.recycle/meta/202605311204.json
new file mode 100644
index 00000000..75f5d903
--- /dev/null
+++ b/write/.recycle/meta/202605311204.json
@@ -0,0 +1,7 @@
+{
+ "slug": "202605311204",
+ "title": "周末我把手机系统降级了一个大版本",
+ "deletedAt": "2026-06-01T15:06:56.071Z",
+ "originalPath": "E:\\GitHub\\blog\\content\\post\\2026\\2026-05-31-周末我把手机系统降级了一个大版本",
+ "expiresAt": "2026-07-01T15:06:56.071Z"
+}
\ No newline at end of file
diff --git a/write/.recycle/posts/2026-06-01-深圳真的好热/index.md b/write/.recycle/posts/2026-06-01-深圳真的好热/index.md
new file mode 100644
index 00000000..4b83c075
--- /dev/null
+++ b/write/.recycle/posts/2026-06-01-深圳真的好热/index.md
@@ -0,0 +1,24 @@
+---
+title: 深圳真的好热
+date: '2026-06-01T00:00:00.000Z'
+slug: 2026-06-01-深圳真的好热
+draft: false
+categories:
+ - 技术
+tags:
+ - 测试
+author: 小赵
+layout: post
+---
+你好
+
+
+
+
+
+今天的天气不错
+
+你觉得呢
+
+哈哈哈哈
+
diff --git a/write/.recycle/posts/2026-06-01-深圳真的热/1780325877248-AgACAgQA.jpg b/write/.recycle/posts/2026-06-01-深圳真的热/1780325877248-AgACAgQA.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f58c5462710693cde2125a847cddba7f74eef885
GIT binary patch
literal 166422
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