]> git.giacomolli.uk - onechat/blobdiff - whatsapp_batch.go
Import WhatsApp exports before live messages
[onechat] / whatsapp_batch.go
index 37b78857129b9198bf8cbf69bdfaad1fe078977b..08dfb6aec76733faf0d58e8774836e9166cd4783 100644 (file)
@@ -1,32 +1,12 @@
 package main
 
 import (
-       "encoding/json"
-       "errors"
-       "fmt"
-       "os"
-       "path/filepath"
        "slices"
        "strings"
-       "sync"
        "time"
 )
 
 const (
-       // whatsappStateFileName is the state file in the WhatsApp directory:
-       // the batches that are not in the log yet.
-       whatsappStateFileName = "state.json"
-
-       // whatsappBatchQuiet is how long a chat is quiet before its batch
-       // closes.
-       whatsappBatchQuiet = 20 * time.Second
-
-       // whatsappBatchWindow is the longest time of a batch.
-       whatsappBatchWindow = 2 * time.Minute
-
-       // whatsappBatchMessages is the most messages of a batch.
-       whatsappBatchMessages = 50
-
        // whatsappBatchEmptyReply is the last line of a batch that starts a
        // turn.
        whatsappBatchEmptyReply = "If nothing in this batch needs the user, end your turn with an empty reply."
@@ -36,227 +16,6 @@ const (
        whatsappBatchDayLayout = "Monday 2006-01-02"
 )
 
-// openWhatsAppBatches reads the batches of the state file at path. A missing
-// file holds no batches. A batch closes after quiet with no message, or after
-// window at the latest.
-func openWhatsAppBatches(path string, quiet, window time.Duration) (*WhatsAppBatches, error) {
-       b := &WhatsAppBatches{
-               path:   path,
-               quiet:  quiet,
-               window: window,
-       }
-
-       data, err := os.ReadFile(path)
-       if errors.Is(err, os.ErrNotExist) {
-               return b, nil
-       }
-
-       if err != nil {
-               return nil, fmt.Errorf("read WhatsApp state: %w", err)
-       }
-
-       err = json.Unmarshal(data, &b.state)
-       if err != nil {
-               return nil, fmt.Errorf("%s: %w", path, err)
-       }
-
-       return b, nil
-}
-
-// WhatsAppBatches holds the batches of the WhatsApp messages that are not in
-// the log yet, open and closed, in the order of their first message. The
-// messages of one chat form one batch while the times of the messages are at
-// most quiet apart, at most window after the first, and the batch has fewer
-// than whatsappBatchMessages messages. The times are the WhatsApp times, so a
-// catch-up after a stop gives the same batches as live messages. A batch also
-// closes when quiet passes after the arrival of its last message, or window
-// after the arrival of its first message, in real time.
-//
-// Each change writes the state file whole, so the batches survive a restart.
-// The mutex protects the batches, because the event handler of the library adds
-// messages while Run closes and removes batches.
-type WhatsAppBatches struct {
-       path   string
-       quiet  time.Duration
-       window time.Duration
-
-       mutex sync.Mutex
-       state WhatsAppStateFile
-}
-
-// Add puts message of chat into the open batch of chat, or into a new batch,
-// with arrival as its time of arrival. A message that the batch holds already,
-// which the servers sent again, changes nothing. It returns true when a batch
-// closed.
-func (b *WhatsAppBatches) Add(chat WhatsAppChat, message WhatsAppMessage, arrival time.Time) (bool, error) {
-       b.mutex.Lock()
-       defer b.mutex.Unlock()
-
-       closed := false
-       index := b.open(chat.ID)
-       if index >= 0 {
-               batch := &b.state.Batches[index]
-               if slices.ContainsFunc(batch.Messages, func(held WhatsAppMessage) bool {
-                       return held.ID == message.ID
-               }) {
-                       return false, nil
-               }
-
-               if message.Time.Sub(batch.Messages[len(batch.Messages)-1].Time) > b.quiet || message.Time.Sub(batch.Messages[0].Time) > b.window {
-                       batch.Closed = true
-                       closed = true
-                       index = -1
-               }
-       }
-
-       if index < 0 {
-               b.state.Next++
-               b.state.Batches = append(b.state.Batches, WhatsAppBatch{
-                       Number: b.state.Next,
-                       Chat:   chat,
-                       First:  arrival,
-               })
-               index = len(b.state.Batches) - 1
-       }
-
-       batch := &b.state.Batches[index]
-       batch.Messages = append(batch.Messages, message)
-       batch.Last = arrival
-       if len(batch.Messages) >= whatsappBatchMessages {
-               batch.Closed = true
-               closed = true
-       }
-
-       return closed, b.save()
-}
-
-// CloseDue closes each open batch whose time is up at now and tells if one
-// closed.
-func (b *WhatsAppBatches) CloseDue(now time.Time) (bool, error) {
-       b.mutex.Lock()
-       defer b.mutex.Unlock()
-
-       closed := false
-       for index := range b.state.Batches {
-               batch := &b.state.Batches[index]
-               if !batch.Closed && !now.Before(b.deadline(*batch)) {
-                       batch.Closed = true
-                       closed = true
-               }
-       }
-
-       if !closed {
-               return false, nil
-       }
-
-       return true, b.save()
-}
-
-// CloseAll closes every open batch.
-func (b *WhatsAppBatches) CloseAll() error {
-       b.mutex.Lock()
-       defer b.mutex.Unlock()
-
-       for index := range b.state.Batches {
-               b.state.Batches[index].Closed = true
-       }
-
-       return b.save()
-}
-
-// Closed returns the closed batches in order.
-func (b *WhatsAppBatches) Closed() []WhatsAppBatch {
-       b.mutex.Lock()
-       defer b.mutex.Unlock()
-
-       var closed []WhatsAppBatch
-       for _, batch := range b.state.Batches {
-               if batch.Closed {
-                       closed = append(closed, batch)
-               }
-       }
-
-       return closed
-}
-
-// Remove removes the batch with number, which is in the log.
-func (b *WhatsAppBatches) Remove(number int) error {
-       b.mutex.Lock()
-       defer b.mutex.Unlock()
-
-       b.state.Batches = slices.DeleteFunc(b.state.Batches, func(batch WhatsAppBatch) bool {
-               return batch.Number == number
-       })
-
-       return b.save()
-}
-
-// Wait returns how long after now the first open batch closes, or false when no
-// batch is open.
-func (b *WhatsAppBatches) Wait(now time.Time) (time.Duration, bool) {
-       b.mutex.Lock()
-       defer b.mutex.Unlock()
-
-       var first time.Time
-       for _, batch := range b.state.Batches {
-               deadline := b.deadline(batch)
-               if !batch.Closed && (first.IsZero() || deadline.Before(first)) {
-                       first = deadline
-               }
-       }
-
-       if first.IsZero() {
-               return 0, false
-       }
-
-       return max(first.Sub(now), 0), true
-}
-
-// open returns the index of the open batch of chat, or -1. The caller holds the
-// mutex.
-func (b *WhatsAppBatches) open(chat string) int {
-       return slices.IndexFunc(b.state.Batches, func(batch WhatsAppBatch) bool {
-               return !batch.Closed && batch.Chat.ID == chat
-       })
-}
-
-// deadline returns when batch closes in real time.
-func (b *WhatsAppBatches) deadline(batch WhatsAppBatch) time.Time {
-       quiet := batch.Last.Add(b.quiet)
-       window := batch.First.Add(b.window)
-       if window.Before(quiet) {
-               return window
-       }
-
-       return quiet
-}
-
-// save writes the state file whole. The caller holds the mutex.
-func (b *WhatsAppBatches) save() error {
-       data, err := json.Marshal(b.state)
-       if err != nil {
-               return fmt.Errorf("encode WhatsApp state: %w", err)
-       }
-
-       err = os.MkdirAll(filepath.Dir(b.path), 0o700)
-       if err == nil {
-               err = writeFileAtomic(b.path, append(data, '\n'))
-       }
-
-       if err != nil {
-               return fmt.Errorf("save WhatsApp state: %w", err)
-       }
-
-       return nil
-}
-
-// WhatsAppStateFile is the content of the state file: the number of the last
-// batch and the batches that are not in the log yet.
-type WhatsAppStateFile struct {
-       Next    int             `json:"next"`
-       Batches []WhatsAppBatch `json:"batches"`
-}
-
 // WhatsAppBatch is one batch of the messages of one chat, with the arrival
 // times of its first and its last message.
 type WhatsAppBatch struct {