~samwhited/xmpp

ref: 196304307d9d915570ed0c5c4734b8247a75cc9d xmpp/history/query.go -rw-r--r-- 2.7 KiB
19630430Sam Whited history: add initial implementation of MAM 5 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Copyright 2021 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.

package history

import (
	"encoding/xml"
	"time"

	"mellium.im/xmlstream"
	"mellium.im/xmpp/form"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/paging"
)

// Query is a request to the archive for data.
// An empty query indicates all messages should be fetched without a filter and
// with a random ID.
type Query struct {
	// Query parameters
	ID string

	// Filters
	With     jid.JID
	Start    time.Time
	End      time.Time
	BeforeID string
	AfterID  string
	IDs      []string

	// Limit limits the total number of messages returned.
	Limit uint64

	// Last starts fetching from the last page.
	Last bool

	// Reverse flips messages returned within a page.
	Reverse bool
}

const (
	fieldWith   = "with"
	fieldStart  = "start"
	fieldEnd    = "end"
	fieldAfter  = "after-id"
	fieldBefore = "before-id"
	fieldIDs    = "ids"
)

// TokenReader implements xmlstream.Marshaler.
func (f Query) TokenReader() xml.TokenReader {
	dataForm := form.New(
		form.Hidden("FORM_TYPE", form.Value(NS)),
		form.JID(fieldWith),
		form.Text(fieldStart),
		form.Text(fieldEnd),
		form.Text(fieldAfter),
		form.Text(fieldBefore),
		form.ListMulti(fieldIDs),
	)
	if !f.With.Equal(jid.JID{}) {
		/* #nosec */
		dataForm.Set(fieldWith, f.With)
	}
	if !f.Start.IsZero() {
		/* #nosec */
		dataForm.Set(fieldStart, f.Start.UTC().Format(time.RFC3339))
	}
	if !f.End.IsZero() {
		/* #nosec */
		dataForm.Set(fieldEnd, f.End.UTC().Format(time.RFC3339))
	}
	if f.AfterID != "" {
		/* #nosec */
		dataForm.Set(fieldAfter, f.AfterID)
	}
	if f.BeforeID != "" {
		/* #nosec */
		dataForm.Set(fieldBefore, f.BeforeID)
	}
	if len(f.IDs) > 0 {
		/* #nosec */
		dataForm.Set(fieldIDs, f.IDs)
	}
	filter, _ := dataForm.Submit()

	inner := []xml.TokenReader{
		filter,
	}
	if f.Last {
		inner = append(inner, (&paging.RequestPrev{
			Max: f.Limit,
		}).TokenReader())
	} else {
		inner = append(inner, (&paging.RequestNext{
			Max: f.Limit,
		}).TokenReader())
	}
	if f.Reverse {
		inner = append(inner, xmlstream.Wrap(
			nil,
			xml.StartElement{Name: xml.Name{Local: "flip-page"}},
		))
	}
	return xmlstream.Wrap(
		xmlstream.MultiReader(inner...),
		xml.StartElement{
			Name: xml.Name{Space: NS, Local: "query"},
			Attr: []xml.Attr{{Name: xml.Name{Local: "queryid"}, Value: f.ID}},
		},
	)
}

// WriteXML implements xmlstream.WriterTo.
func (f *Query) WriteXML(w xmlstream.TokenWriter) (int, error) {
	return xmlstream.Copy(w, f.TokenReader())
}

// MarshalXML implements xml.Marshaler.
func (f *Query) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
	_, err := f.WriteXML(e)
	return err
}