208 lines
6.7 KiB
Python
208 lines
6.7 KiB
Python
# Write minimal bot-only main.go
|
|||
|
|
bot_main = '''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 += "\n\n"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
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 + "\n"
|
||
|
|
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)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
'''
|
||
|
|
|
||
|
|
with open(r"E:\GitHub\blog\bot-go\cmd\writebot\main.go", "w", encoding="utf-8") as f:
|
||
|
|
f.write(bot_main)
|
||
|
|
|
||
|
|
print("Bot main.go: simplified, size:", len(bot_main))
|