// This is the main package package main import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http" "os" "path" "strconv" "strings" "sync" "time" "gitea.balki.me/telegram-msgchkbox/glist" ) var apiToken string func main() { apiToken = os.Getenv("CHKBOT_API_TOKEN") if apiToken == "" { log.Panicln("CHKBOT_API_TOKEN is empty") } port := func() int { portStr := os.Getenv("CHKBOT_PORT") port, err := strconv.Atoi(portStr) if err == nil { return port } return 28923 }() dataPath := func() string { dataPath := os.Getenv("CHKBOT_DATA_PATH") if dataPath == "" { dataPath = "." } err := os.MkdirAll(dataPath, 0755) if err != nil { log.Panicf("Failed to create datapath, path: %s, err: %s", dataPath, err) } return dataPath }() glist.DataPath = dataPath log.Printf("Grocery List bot starting with datapath:%s, port:%d\n", dataPath, port) var chats sync.Map if err := loadData(dataPath, &chats); err != nil { log.Panicf("failed to load data, err: %s", err) } botPath := fmt.Sprintf("/bot%s", apiToken) http.HandleFunc(botPath, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) body, err := io.ReadAll(r.Body) if err != nil { log.Println(err) return } log.Println(string(body)) update := struct { Message *struct { ID int `json:"message_id"` Chat struct { ID int `json:"id"` } Text string } CallbackQuery *struct { ID string `json:"id"` Message struct { ID int `json:"message_id"` Chat struct { ID int `json:"id"` } } Data string } `json:"callback_query"` }{} if err := json.Unmarshal(body, &update); err != nil { log.Println(err) return } if update.Message != nil && update.Message.Text != "" { chatID := update.Message.Chat.ID g, _ := chats.LoadOrStore(chatID, glist.NewGList(chatID)) gl := g.(*glist.GList) go handleTextAdded(gl, update.Message.Text) } else if update.CallbackQuery != nil { defer func() { go answerCallbackQuery(update.CallbackQuery.ID) }() chatID := update.CallbackQuery.Message.Chat.ID g, ok := chats.Load(chatID) if !ok { log.Println("Chat not found: %s", chatID) return } gl := g.(*glist.GList) go handleButtonClick(gl, update.CallbackQuery.Message.ID, update.CallbackQuery.Data) } }) log.Panic(http.ListenAndServe(fmt.Sprintf(":%v", port), nil)) } func handleTextAdded(gl *glist.GList, text string) { gl.Mutex.Lock() defer gl.Mutex.Unlock() gl.Add(text) count := gl.AllMsgCounter + 1 gl.AllMsgCounter = count time.AfterFunc(10*time.Second, func() { gl.Mutex.Lock() defer gl.Mutex.Unlock() if count == gl.AllMsgCounter { sendList(gl, "sendMessage") } }) } func handleButtonClick(gl *glist.GList, messageID int, text string) { gl.Mutex.Lock() defer gl.Mutex.Unlock() if gl.MessageID != nil { if messageID != *gl.MessageID { go deleteMessage(gl.ChatID, *gl.MessageID) } } gl.MessageID = &messageID if text == "clear" { gl.ClearChecked() } else { gl.Toggle(text) } if len(gl.Items) == 0 { deleteMessage(gl.ChatID, messageID) } else { sendList(gl, "editMessageText") } } func sendList(gl *glist.GList, method string) { url := fmt.Sprintf("https://api.telegram.org/bot%s/%s", apiToken, method) sendMsgReq, err := gl.GenSendListReq() if err != nil { log.Println(err) return } resp, err := http.Post(url, "application/json", bytes.NewReader(sendMsgReq)) if err != nil { log.Println(err) return } logBody(resp.Body) } func answerCallbackQuery(callbackQueryID string) { answerURL := fmt.Sprintf("https://api.telegram.org/bot%s/answerCallbackQuery?callback_query_id=%stext=ok", apiToken, callbackQueryID) resp, err := http.Get(answerURL) if err != nil { log.Println(err) return } logBody(resp.Body) } func deleteMessage(chatID int, messageID int) { deleteURL := fmt.Sprintf("https://api.telegram.org/bot%s/deleteMessage?chat_id=%d&message_id=%d", apiToken, chatID, messageID) resp, err := http.Get(deleteURL) if err != nil { log.Println(err) return } logBody(resp.Body) } func logBody(respBody io.ReadCloser) { defer func() { err := respBody.Close() if err != nil { log.Println(err) } }() body, err := io.ReadAll(respBody) if err != nil { log.Println(err) return } log.Println(string(body)) } func loadData(dataPath string, chats *sync.Map) error { items, err := os.ReadDir(dataPath) if err != nil { return err } for _, de := range items { if de.IsDir() { continue } name := de.Name() if !strings.HasPrefix(name, "chkchat") { continue } var gl glist.GList data, err := os.ReadFile(path.Join(dataPath, name)) if err != nil { return fmt.Errorf("failed read file, name: %s, err:%w", name, err) } err = json.Unmarshal(data, &gl) if err != nil { return fmt.Errorf("failed to parse data, data:%s, err:%w", data, err) } chats.Store(gl.ChatID, &gl) } return nil }