X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/4343ca5fb6cc86eb887951f4fe16063bab29a238..c781f79f3c8cb9a7577ee1574d196988da2719b3:/whatsapp_import.go diff --git a/whatsapp_import.go b/whatsapp_import.go index 1092b49..0021bc8 100644 --- a/whatsapp_import.go +++ b/whatsapp_import.go @@ -34,6 +34,88 @@ var ( whatsappImportUnknown = regexp.MustCompile(`^\[WhatsApp\] (.*), [A-Za-z]+ (\d{4}-\d{2}-\d{2})$`) ) +// WhatsAppImportPlan holds the new chat-day batches and the size of the merged +// log. Its estimate uses the measured cost of 1.3 calls per message, not a +// promise about a particular export or compaction retry. +type WhatsAppImportPlan struct { + agent string + cutoffs map[string]time.Time + added []DatedMessage + chats []string + cutoff time.Time + first time.Time + last time.Time + bytes int + messages int + skipped int + ownerKnown bool +} + +// WhatsAppImportOptions selects the target, exports, and plan-only mode. +type WhatsAppImportOptions struct { + planOnly bool + agent string + paths []string +} + +// Write prints the plan without chat text or the owner's profile name. +func (p WhatsAppImportPlan) Write(out io.Writer) error { + cutoff := "none (no live WhatsApp batch)" + if !p.cutoff.IsZero() { + cutoff = p.cutoff.Format(time.RFC3339) + } + + dateRange := "none" + if !p.first.IsZero() { + dateRange = p.first.Format(time.RFC3339) + " to " + p.last.Format(time.RFC3339) + } + + owner := "known (mapped to You)" + if !p.ownerKnown { + owner = "unknown (sender names kept as written)" + } + + _, err := fmt.Fprintf(out, "agent: %s\nchats: %d\n", p.agent, len(p.chats)) + if err != nil { + return fmt.Errorf("write import plan: %w", err) + } + + for _, chat := range p.chats { + line := " " + chat + cutoff, found := p.cutoffs[chat] + if found { + line += " (cutoff: " + cutoff.Format(time.RFC3339) + ")" + } + + _, err = fmt.Fprintln(out, line) + if err != nil { + return fmt.Errorf("write import plan: %w", err) + } + } + + estimate := 1.3 * float64(p.messages) + if len(p.added) == 0 { + estimate = 0 + } + + lines := []string{ + fmt.Sprintf("batches: %d (%d chat-days skipped)", len(p.added), p.skipped), + "dates: " + dateRange, + "cutoff: " + cutoff, + "owner: " + owner, + fmt.Sprintf("bytes: %d (new batch text)", p.bytes), + fmt.Sprintf("messages: %d (merged log)", p.messages), + fmt.Sprintf("estimated calls: %.0f (about 1.3 per message, limit %d)", estimate, rebuildCallLimit), + } + + _, err = io.WriteString(out, strings.Join(lines, "\n")+"\n") + if err != nil { + return fmt.Errorf("write import plan: %w", err) + } + + return nil +} + // whatsappImport runs the offline import command. It holds the same token lock // as the daemon and probe, even for a plan. The plan reads no credentials for a // model and makes no network call. Only a completed rebuild replaces the agent @@ -156,88 +238,6 @@ func whatsappImport(ctx context.Context, args []string, out io.Writer) (err erro return nil } -// WhatsAppImportPlan holds the new chat-day batches and the size of the merged -// log. Its estimate uses the measured cost of 1.3 calls per message, not a -// promise about a particular export or compaction retry. -type WhatsAppImportPlan struct { - agent string - cutoffs map[string]time.Time - added []DatedMessage - chats []string - cutoff time.Time - first time.Time - last time.Time - bytes int - messages int - skipped int - ownerKnown bool -} - -// Write prints the plan without chat text or the owner's profile name. -func (p WhatsAppImportPlan) Write(out io.Writer) error { - cutoff := "none (no live WhatsApp batch)" - if !p.cutoff.IsZero() { - cutoff = p.cutoff.Format(time.RFC3339) - } - - dateRange := "none" - if !p.first.IsZero() { - dateRange = p.first.Format(time.RFC3339) + " to " + p.last.Format(time.RFC3339) - } - - owner := "known (mapped to You)" - if !p.ownerKnown { - owner = "unknown (sender names kept as written)" - } - - _, err := fmt.Fprintf(out, "agent: %s\nchats: %d\n", p.agent, len(p.chats)) - if err != nil { - return fmt.Errorf("write import plan: %w", err) - } - - for _, chat := range p.chats { - line := " " + chat - cutoff, found := p.cutoffs[chat] - if found { - line += " (cutoff: " + cutoff.Format(time.RFC3339) + ")" - } - - _, err = fmt.Fprintln(out, line) - if err != nil { - return fmt.Errorf("write import plan: %w", err) - } - } - - estimate := 1.3 * float64(p.messages) - if len(p.added) == 0 { - estimate = 0 - } - - lines := []string{ - fmt.Sprintf("batches: %d (%d chat-days skipped)", len(p.added), p.skipped), - "dates: " + dateRange, - "cutoff: " + cutoff, - "owner: " + owner, - fmt.Sprintf("bytes: %d (new batch text)", p.bytes), - fmt.Sprintf("messages: %d (merged log)", p.messages), - fmt.Sprintf("estimated calls: %.0f (about 1.3 per message, limit %d)", estimate, rebuildCallLimit), - } - - _, err = io.WriteString(out, strings.Join(lines, "\n")+"\n") - if err != nil { - return fmt.Errorf("write import plan: %w", err) - } - - return nil -} - -// WhatsAppImportOptions selects the target, exports, and plan-only mode. -type WhatsAppImportOptions struct { - planOnly bool - agent string - paths []string -} - // whatsappImportParse accepts GNU-style flags, including --agent=NAME and -- // before paths that start with a dash. func whatsappImportParse(args []string) (WhatsAppImportOptions, error) {