// Package glist handles the list processing package glist import ( "encoding/json" "fmt" "log" "os" "path" "strings" "sync" "time" ) var DataPath string 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 } var persistReqC chan<- *GList func startPersistenceGoR() { reqs := make(chan *GList, 50) persistReqC = reqs go func() { for g := range reqs { lists := map[*GList]struct{}{ g: struct{}{}, } time.Sleep(5 * time.Second) // Collect all persist requests for 5 seconds for len(reqs) > 0 { g := <-reqs lists[g] = struct{}{} } for g := range lists { g.persist() } } }() } func init() { startPersistenceGoR() } func (g *GList) persist() { g.Mutex.Lock() defer g.Mutex.Unlock() data, err := json.Marshal(g) if err != nil { log.Panicln("failed to marshal") } filename := path.Join(DataPath, fmt.Sprintf("chkchat%d", g.ChatID)) if err := os.WriteFile(filename, data, 0644); err != nil { log.Panicln("failed to write to file") } } func (g *GList) Add(t string) { outer: for _, text := range strings.Split(t, "\n") { for _, item := range g.Items { if item.Text == text { continue outer } } g.Items = append(g.Items, Entry{text, false}) } persistReqC <- g } func (g *GList) Toggle(text string) { for i, item := range g.Items { if item.Text == text { g.Items[i].Checked = !g.Items[i].Checked persistReqC <- g return } } log.Printf("item not found in toggle, chat: %d, item: %s\n", g.ChatID, text) } func (g *GList) ClearChecked() { var remaining []Entry for _, item := range g.Items { if !item.Checked { remaining = append(remaining, item) } } g.Items = remaining persistReqC <- g } type SendMethod string var ( NEWLIST SendMethod = "sendMessage" EDITLIST SendMethod = "editMessageText" ) 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"` DisableNotification *bool `json:"disable_notification,omitempty"` 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(method SendMethod) ([]byte, error) { req := newListReq{ChatID: g.ChatID, MessageID: g.MessageID, Text: "List:"} if method == NEWLIST { disableNotification := true req.DisableNotification = &disableNotification } itemButtons := makeButtons(g.Items) controlButtons := []button{{"clear checked", "clear"}} req.ReplyMarkup.InlineKeyboard = append(itemButtons, controlButtons) data, err := json.Marshal(req) return data, err }