1 | package soju
|
---|
2 |
|
---|
3 | import (
|
---|
4 | "bytes"
|
---|
5 | "encoding/base64"
|
---|
6 | "fmt"
|
---|
7 | "time"
|
---|
8 |
|
---|
9 | "git.sr.ht/~sircmpwn/go-bare"
|
---|
10 | "gopkg.in/irc.v3"
|
---|
11 | )
|
---|
12 |
|
---|
13 | // messageStore is a per-user store for IRC messages.
|
---|
14 | type messageStore interface {
|
---|
15 | Close() error
|
---|
16 | // LastMsgID queries the last message ID for the given network, entity and
|
---|
17 | // date. The message ID returned may not refer to a valid message, but can be
|
---|
18 | // used in history queries.
|
---|
19 | LastMsgID(network *network, entity string, t time.Time) (string, error)
|
---|
20 | // LoadLatestID queries the latest non-event messages for the given network,
|
---|
21 | // entity and date, up to a count of limit messages, sorted from oldest to newest.
|
---|
22 | LoadLatestID(network *network, entity, id string, limit int) ([]*irc.Message, error)
|
---|
23 | Append(network *network, entity string, msg *irc.Message) (id string, err error)
|
---|
24 | }
|
---|
25 |
|
---|
26 | type chatHistoryTarget struct {
|
---|
27 | Name string
|
---|
28 | LatestMessage time.Time
|
---|
29 | }
|
---|
30 |
|
---|
31 | // chatHistoryMessageStore is a message store that supports chat history
|
---|
32 | // operations.
|
---|
33 | type chatHistoryMessageStore interface {
|
---|
34 | messageStore
|
---|
35 |
|
---|
36 | // ListTargets lists channels and nicknames by time of the latest message.
|
---|
37 | // It returns up to limit targets, starting from start and ending on end,
|
---|
38 | // both excluded. end may be before or after start.
|
---|
39 | // If events is false, only PRIVMSG/NOTICE messages are considered.
|
---|
40 | ListTargets(network *network, start, end time.Time, limit int, events bool) ([]chatHistoryTarget, error)
|
---|
41 | // LoadBeforeTime loads up to limit messages before start down to end. The
|
---|
42 | // returned messages must be between and excluding the provided bounds.
|
---|
43 | // end is before start.
|
---|
44 | // If events is false, only PRIVMSG/NOTICE messages are considered.
|
---|
45 | LoadBeforeTime(network *network, entity string, start, end time.Time, limit int, events bool) ([]*irc.Message, error)
|
---|
46 | // LoadBeforeTime loads up to limit messages after start up to end. The
|
---|
47 | // returned messages must be between and excluding the provided bounds.
|
---|
48 | // end is after start.
|
---|
49 | // If events is false, only PRIVMSG/NOTICE messages are considered.
|
---|
50 | LoadAfterTime(network *network, entity string, start, end time.Time, limit int, events bool) ([]*irc.Message, error)
|
---|
51 | }
|
---|
52 |
|
---|
53 | type msgIDType uint
|
---|
54 |
|
---|
55 | const (
|
---|
56 | msgIDNone msgIDType = iota
|
---|
57 | msgIDMemory
|
---|
58 | msgIDFS
|
---|
59 | )
|
---|
60 |
|
---|
61 | const msgIDVersion uint = 0
|
---|
62 |
|
---|
63 | type msgIDHeader struct {
|
---|
64 | Version uint
|
---|
65 | Network bare.Int
|
---|
66 | Target string
|
---|
67 | Type msgIDType
|
---|
68 | }
|
---|
69 |
|
---|
70 | type msgIDBody interface {
|
---|
71 | msgIDType() msgIDType
|
---|
72 | }
|
---|
73 |
|
---|
74 | func formatMsgID(netID int64, target string, body msgIDBody) string {
|
---|
75 | var buf bytes.Buffer
|
---|
76 | w := bare.NewWriter(&buf)
|
---|
77 |
|
---|
78 | header := msgIDHeader{
|
---|
79 | Version: msgIDVersion,
|
---|
80 | Network: bare.Int(netID),
|
---|
81 | Target: target,
|
---|
82 | Type: body.msgIDType(),
|
---|
83 | }
|
---|
84 | if err := bare.MarshalWriter(w, &header); err != nil {
|
---|
85 | panic(err)
|
---|
86 | }
|
---|
87 | if err := bare.MarshalWriter(w, body); err != nil {
|
---|
88 | panic(err)
|
---|
89 | }
|
---|
90 | return base64.RawURLEncoding.EncodeToString(buf.Bytes())
|
---|
91 | }
|
---|
92 |
|
---|
93 | func parseMsgID(s string, body msgIDBody) (netID int64, target string, err error) {
|
---|
94 | b, err := base64.RawURLEncoding.DecodeString(s)
|
---|
95 | if err != nil {
|
---|
96 | return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
|
---|
97 | }
|
---|
98 |
|
---|
99 | r := bare.NewReader(bytes.NewReader(b))
|
---|
100 |
|
---|
101 | var header msgIDHeader
|
---|
102 | if err := bare.UnmarshalBareReader(r, &header); err != nil {
|
---|
103 | return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
|
---|
104 | }
|
---|
105 |
|
---|
106 | if header.Version != msgIDVersion {
|
---|
107 | return 0, "", fmt.Errorf("invalid internal message ID: got version %v, want %v", header.Version, msgIDVersion)
|
---|
108 | }
|
---|
109 |
|
---|
110 | if body != nil {
|
---|
111 | typ := body.msgIDType()
|
---|
112 | if header.Type != typ {
|
---|
113 | return 0, "", fmt.Errorf("invalid internal message ID: got type %v, want %v", header.Type, typ)
|
---|
114 | }
|
---|
115 |
|
---|
116 | if err := bare.UnmarshalBareReader(r, body); err != nil {
|
---|
117 | return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
|
---|
118 | }
|
---|
119 | }
|
---|
120 |
|
---|
121 | return int64(header.Network), header.Target, nil
|
---|
122 | }
|
---|