diff --git a/glist/glist.go b/glist/glist.go new file mode 100644 index 0000000..b049a4a --- /dev/null +++ b/glist/glist.go @@ -0,0 +1,105 @@ +package glist + +import ( + "encoding/json" + "fmt" + "sync" +) + +type Entry struct { + Text string `json:"text"` + Checked bool `json:"checked"` +} + +type GList struct { + AllMsgCounter int `json:"-"` + Mutex sync.Mutex `json:"-"` + ChatID int `json:"chat_id"` + MessageID *int `json:"message_id"` + Items []Entry `json:"items"` +} + +func NewGList(chatID int, items ...string) *GList { + g := GList{ChatID: chatID} + for _, text := range items { + g.Items = append(g.Items, Entry{text, false}) + } + return &g +} + +func (g *GList) Add(text string) error { + for _, item := range g.Items { + if item.Text == text { + return fmt.Errorf("dupe:%s", text) + } + } + g.Items = append(g.Items, Entry{text, false}) + return nil +} + +func (g *GList) Toggle(text string) error { + for i, item := range g.Items { + if item.Text == text { + g.Items[i].Checked = !g.Items[i].Checked + return nil + } + } + return fmt.Errorf("not found:%s", text) +} + +func (g *GList) ClearChecked() { + var remaining []Entry + for _, item := range g.Items { + if !item.Checked { + remaining = append(remaining, item) + } + } + g.Items = remaining +} + +type button struct { + Text string `json:"text"` + CallbackData string `json:"callback_data"` +} + +type newListReq struct { + ChatID int `json:"chat_id"` + MessageID *int `json:"message_id,omitempty"` + Text string `json:"text"` + ReplyMarkup struct { + InlineKeyboard [][]button `json:"inline_keyboard"` + } `json:"reply_markup"` +} + +func makeButton(e Entry) button { + b := button{Text: e.Text, CallbackData: e.Text} + if e.Checked { + b.Text = fmt.Sprintf("✓ %s", e.Text) + } + return b +} + +func makeButtons(items []Entry) [][]button { + var buttons [][]button + var current []button + for _, item := range items { + current = append(current, makeButton(item)) + if len(current) == 2 { + buttons = append(buttons, current) + current = []button{} + } + } + if len(current) != 0 { + buttons = append(buttons, current) + } + return buttons +} + +func (g *GList) GenSendListReq() ([]byte, error) { + req := newListReq{ChatID: g.ChatID, MessageID: g.MessageID, Text: "ok"} + itemButtons := makeButtons(g.Items) + controlButtons := []button{{"clear checked", "clear"}} + req.ReplyMarkup.InlineKeyboard = append(itemButtons, controlButtons) + data, err := json.Marshal(req) + return data, err +} diff --git a/glist/glist_test.go b/glist/glist_test.go new file mode 100644 index 0000000..9ae4691 --- /dev/null +++ b/glist/glist_test.go @@ -0,0 +1,20 @@ +package glist + +import ( + "sync" + "testing" +) + +func TestGList(t *testing.T) { + var ti int + var m sync.Mutex + g := GList{ti, m, 4342, nil, []Entry{{"foo", true}}} + data, err := g.GenSendListReq() + if err != nil { + t.Fatal(err) + } + expected := `{"chat_id":4342,"text":"ok","reply_markup":{"inline_keyboard":[[{"text":"✓ foo","callback_data":"foo"}]]}}` + if expected != string(data) { + t.Fatalf("expected: %s\n got:%s\n", expected, string(data)) + } +} diff --git a/main.go b/main.go index 1942e82..3105089 100644 --- a/main.go +++ b/main.go @@ -1,21 +1,23 @@ package main import ( + "bytes" "encoding/json" "fmt" "io" "log" "net/http" - "strings" + "sync" + "time" + + "gitea.balki.me/telegram-msgchkbox/glist" ) var apiToken = "421791796:AAE4wPbcqfLP1GNeGR3RTBiyX16fCj3HPAM" func main() { + var chats sync.Map fmt.Println("vim-go") - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - }) http.HandleFunc("/zeexkfcsdjpmncirkyelwzotjmmefcqtcogrfwnafidionxiacwnslwuhbwfuppjgwzbmazd", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) body, err := io.ReadAll(r.Body) @@ -50,39 +52,70 @@ func main() { } if update.Message != nil { - go sendButton(update.Message.Chat.ID, update.Message.ID, update.Message.Text) - go DeleteMessage(update.Message.Chat.ID, update.Message.ID) + + 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 { - go markButtonChecked(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Message.ID, update.CallbackQuery.Data) - go answerCallbackQuery(update.CallbackQuery.ID) + 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) + } }) port := 28923 log.Panic(http.ListenAndServe(fmt.Sprintf(":%v", port), nil)) } -func markButtonChecked(chatID int, messageID int, text string) { - url := fmt.Sprintf("https://api.telegram.org/bot%s/editMessageText", apiToken) - text = fmt.Sprintf("✓ %s", text) - sendMsgFmt := ` - { - "chat_id": "%d", - "message_id": "%d", - "text": "ok", - "reply_markup": { - "inline_keyboard": [ - [ - { - "text": "%s", - "callback_data": "%s" - } - ] - ] - } - } - ` - sendMsgReq := fmt.Sprintf(sendMsgFmt, chatID, messageID, text, text) - resp, err := http.Post(url, "application/json", strings.NewReader(sendMsgReq)) +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(30*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) + } + + 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 @@ -100,33 +133,7 @@ func answerCallbackQuery(callbackQueryID string) { logBody(resp.Body) } -func sendButton(chatID int, messageID int, text string) { - url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", apiToken) - sendMsgFmt := ` - { - "chat_id": "%d", - "text": "ok", - "reply_markup": { - "inline_keyboard": [ - [ - { - "text": "%s", - "callback_data": "%s" - } - ] - ] - } - } - ` - sendMsgReq := fmt.Sprintf(sendMsgFmt, chatID, text, text) - resp, err := http.Post(url, "application/json", strings.NewReader(sendMsgReq)) - if err != nil { - log.Println(err) - return - } - logBody(resp.Body) -} -func DeleteMessage(chatID int, messageID int) { +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 { @@ -151,7 +158,7 @@ func logBody(respBody io.ReadCloser) { log.Println(string(body)) } -/* +/* Example data update_id: 547400623 message: message_id: 869