feat: add message list filters (#153) (thanks @draix)
This commit is contained in:
parent
372e1fc257
commit
dffcda4481
@ -5,6 +5,7 @@
|
||||
### Added
|
||||
|
||||
- CLI: add `--full` to disable table truncation; piped output now keeps full message IDs. (#13 — thanks @rickhallett)
|
||||
- Messages: add `messages list --sender`, `--from-me`, `--from-them`, and `--asc` filters. (#153 — thanks @draix)
|
||||
|
||||
### Security
|
||||
|
||||
|
||||
@ -25,9 +25,13 @@ func newMessagesCmd(flags *rootFlags) *cobra.Command {
|
||||
|
||||
func newMessagesListCmd(flags *rootFlags) *cobra.Command {
|
||||
var chat string
|
||||
var sender string
|
||||
var limit int
|
||||
var afterStr string
|
||||
var beforeStr string
|
||||
var fromMe bool
|
||||
var fromThem bool
|
||||
var asc bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
@ -36,6 +40,10 @@ func newMessagesListCmd(flags *rootFlags) *cobra.Command {
|
||||
ctx, cancel := withTimeout(context.Background(), flags)
|
||||
defer cancel()
|
||||
|
||||
if fromMe && fromThem {
|
||||
return fmt.Errorf("--from-me and --from-them are mutually exclusive")
|
||||
}
|
||||
|
||||
a, lk, err := newApp(ctx, flags, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -59,11 +67,24 @@ func newMessagesListCmd(flags *rootFlags) *cobra.Command {
|
||||
before = &t
|
||||
}
|
||||
|
||||
var fromMeFilter *bool
|
||||
switch {
|
||||
case fromMe:
|
||||
v := true
|
||||
fromMeFilter = &v
|
||||
case fromThem:
|
||||
v := false
|
||||
fromMeFilter = &v
|
||||
}
|
||||
|
||||
msgs, err := a.DB().ListMessages(store.ListMessagesParams{
|
||||
ChatJID: chat,
|
||||
Limit: limit,
|
||||
After: after,
|
||||
Before: before,
|
||||
ChatJID: chat,
|
||||
SenderJID: sender,
|
||||
Limit: limit,
|
||||
After: after,
|
||||
Before: before,
|
||||
FromMe: fromMeFilter,
|
||||
Asc: asc,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -80,10 +101,14 @@ func newMessagesListCmd(flags *rootFlags) *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&chat, "chat", "", "chat JID")
|
||||
cmd.Flags().IntVar(&limit, "limit", 50, "limit results")
|
||||
cmd.Flags().StringVar(&chat, "chat", "", "filter by chat JID")
|
||||
cmd.Flags().StringVar(&sender, "sender", "", "filter by sender JID")
|
||||
cmd.Flags().IntVar(&limit, "limit", 50, "max number of messages to return")
|
||||
cmd.Flags().StringVar(&afterStr, "after", "", "only messages after time (RFC3339 or YYYY-MM-DD)")
|
||||
cmd.Flags().StringVar(&beforeStr, "before", "", "only messages before time (RFC3339 or YYYY-MM-DD)")
|
||||
cmd.Flags().BoolVar(&fromMe, "from-me", false, "only messages sent by me")
|
||||
cmd.Flags().BoolVar(&fromThem, "from-them", false, "only messages received (not sent by me)")
|
||||
cmd.Flags().BoolVar(&asc, "asc", false, "show oldest messages first (default: newest first)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
||||
@ -165,7 +165,7 @@ WhatsApp Web history is best-effort. If you want to try fetching *older* message
|
||||
|
||||
### Messages
|
||||
|
||||
- `wacli messages list [--chat JID] [--limit N] [--before TS] [--after TS]`
|
||||
- `wacli messages list [--chat JID] [--sender JID] [--from-me|--from-them] [--asc] [--limit N] [--before TS] [--after TS]`
|
||||
- `wacli messages search <query> [--chat JID] [--from JID] [--limit N] [--before TS] [--after TS] [--type text|image|video|audio|document]`
|
||||
- `wacli messages show --chat JID --id MSG_ID`
|
||||
- `wacli messages context --chat JID --id MSG_ID [--before N] [--after N]`
|
||||
|
||||
@ -59,10 +59,13 @@ func (d *DB) UpsertMessage(p UpsertMessageParams) error {
|
||||
}
|
||||
|
||||
type ListMessagesParams struct {
|
||||
ChatJID string
|
||||
Limit int
|
||||
Before *time.Time
|
||||
After *time.Time
|
||||
ChatJID string
|
||||
SenderJID string
|
||||
Limit int
|
||||
Before *time.Time
|
||||
After *time.Time
|
||||
FromMe *bool
|
||||
Asc bool
|
||||
}
|
||||
|
||||
func (d *DB) ListMessages(p ListMessagesParams) ([]Message, error) {
|
||||
@ -87,7 +90,19 @@ func (d *DB) ListMessages(p ListMessagesParams) ([]Message, error) {
|
||||
query += " AND m.ts < ?"
|
||||
args = append(args, unix(*p.Before))
|
||||
}
|
||||
query += " ORDER BY m.ts DESC LIMIT ?"
|
||||
if strings.TrimSpace(p.SenderJID) != "" {
|
||||
query += " AND m.sender_jid = ?"
|
||||
args = append(args, strings.TrimSpace(p.SenderJID))
|
||||
}
|
||||
if p.FromMe != nil {
|
||||
query += " AND m.from_me = ?"
|
||||
args = append(args, boolToInt(*p.FromMe))
|
||||
}
|
||||
if p.Asc {
|
||||
query += " ORDER BY m.ts ASC LIMIT ?"
|
||||
} else {
|
||||
query += " ORDER BY m.ts DESC LIMIT ?"
|
||||
}
|
||||
args = append(args, p.Limit)
|
||||
return d.scanMessages(query, args...)
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package store
|
||||
import (
|
||||
"database/sql"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@ -128,6 +129,69 @@ func TestMessageUpsertIdempotentAndContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListMessagesFiltersAndOrdering(t *testing.T) {
|
||||
db := openTestDB(t)
|
||||
chat := "chat@s.whatsapp.net"
|
||||
otherChat := "other@s.whatsapp.net"
|
||||
base := time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC)
|
||||
for _, jid := range []string{chat, otherChat} {
|
||||
if err := db.UpsertChat(jid, "dm", jid, base); err != nil {
|
||||
t.Fatalf("UpsertChat %s: %v", jid, err)
|
||||
}
|
||||
}
|
||||
rows := []UpsertMessageParams{
|
||||
{ChatJID: chat, MsgID: "old-from-alice", SenderJID: "alice@s.whatsapp.net", Timestamp: base, Text: "old"},
|
||||
{ChatJID: chat, MsgID: "new-from-me", SenderJID: "me@s.whatsapp.net", Timestamp: base.Add(time.Second), FromMe: true, Text: "new"},
|
||||
{ChatJID: otherChat, MsgID: "other-chat", SenderJID: "alice@s.whatsapp.net", Timestamp: base.Add(2 * time.Second), Text: "other"},
|
||||
}
|
||||
for _, row := range rows {
|
||||
if err := db.UpsertMessage(row); err != nil {
|
||||
t.Fatalf("UpsertMessage %s: %v", row.MsgID, err)
|
||||
}
|
||||
}
|
||||
|
||||
msgs, err := db.ListMessages(ListMessagesParams{ChatJID: chat, Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("ListMessages: %v", err)
|
||||
}
|
||||
if got := messageIDs(msgs); got != "new-from-me,old-from-alice" {
|
||||
t.Fatalf("default order = %s", got)
|
||||
}
|
||||
|
||||
msgs, err = db.ListMessages(ListMessagesParams{ChatJID: chat, Limit: 10, Asc: true})
|
||||
if err != nil {
|
||||
t.Fatalf("ListMessages asc: %v", err)
|
||||
}
|
||||
if got := messageIDs(msgs); got != "old-from-alice,new-from-me" {
|
||||
t.Fatalf("asc order = %s", got)
|
||||
}
|
||||
|
||||
fromMe := true
|
||||
msgs, err = db.ListMessages(ListMessagesParams{ChatJID: chat, Limit: 10, FromMe: &fromMe})
|
||||
if err != nil {
|
||||
t.Fatalf("ListMessages fromMe: %v", err)
|
||||
}
|
||||
if got := messageIDs(msgs); got != "new-from-me" {
|
||||
t.Fatalf("fromMe filter = %s", got)
|
||||
}
|
||||
|
||||
msgs, err = db.ListMessages(ListMessagesParams{ChatJID: chat, SenderJID: "alice@s.whatsapp.net", Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("ListMessages sender: %v", err)
|
||||
}
|
||||
if got := messageIDs(msgs); got != "old-from-alice" {
|
||||
t.Fatalf("sender filter = %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func messageIDs(msgs []Message) string {
|
||||
out := make([]string, 0, len(msgs))
|
||||
for _, msg := range msgs {
|
||||
out = append(out, msg.MsgID)
|
||||
}
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
|
||||
func TestMediaDownloadInfoAndMarkDownloaded(t *testing.T) {
|
||||
db := openTestDB(t)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user