tglistbot/glist/glist.go

170 lines
3.6 KiB
Go

// 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
}