+// WhatsAppStateFile is the content of the state file: the number of the last
+// batch, the batches that are not in the log yet, and the send records, oldest
+// first.
+type WhatsAppStateFile struct {
+ Next int `json:"next"`
+ Batches []WhatsAppBatch `json:"batches"`
+ Sends []WhatsAppSend `json:"sends"`
+}
+
+// WhatsAppSend is the record of one send: the ID that the agent gave, the chat,
+// the SHA-256 of the text, an optional stored image name, the WhatsApp message
+// ID, the state, which is sending, sent, or failed, the answer of the tool, and
+// the time of its start.
+type WhatsAppSend struct {
+ ID string `json:"id"`
+ Chat string `json:"chat"`
+ Digest string `json:"digest"`
+ Image string `json:"image,omitempty"`
+ MessageID string `json:"message_id"`
+ State string `json:"state"`
+ Answer string `json:"answer,omitempty"`
+ Time time.Time `json:"time"`
+}
+
+// openWhatsAppState reads the batches and the send records of the state file at
+// path. A missing file holds none. A batch closes after
+// whatsappBatchIdleTimeout with no message, or after whatsappBatchTimeout at
+// the latest.
+func openWhatsAppState(path string) (*WhatsAppState, error) {
+ s := &WhatsAppState{
+ path: path,
+ }
+
+ data, err := os.ReadFile(path)
+ if errors.Is(err, os.ErrNotExist) {
+ return s, nil
+ }
+
+ if err != nil {
+ return nil, fmt.Errorf("read WhatsApp state: %w", err)
+ }
+
+ err = json.Unmarshal(data, &s.file)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", path, err)
+ }
+
+ return s, nil
+}
+