@@ -0,0 +1,183 @@
1 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Package chathistory provides a synchronous wrapper around the IRCv3
2 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // CHATHISTORY extension for use with girc clients.
3 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ //
4 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Usage:
5 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ //
6 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // fetcher := chathistory.New(client)
7 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // msgs, err := fetcher.Latest(ctx, "#channel", 50)
8 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ package chathistory
9 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
10 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import (
11 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "context"
12 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "fmt"
13 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "strings"
14 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "sync"
15 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "time"
16 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
17 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "github.com/lrstanley/girc"
18 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
19 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
20 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Message is a single message returned by a CHATHISTORY query.
21 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ type Message struct {
22 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ At time.Time
23 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Nick string
24 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Account string
25 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Text string
26 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ MsgID string
27 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
28 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
29 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Fetcher sends CHATHISTORY commands and collects the batched responses.
30 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ type Fetcher struct {
31 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ client *girc.Client
32 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
33 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ mu sync.Mutex
34 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ batches map[string]*batch // batchRef → accumulator
35 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ waiters map[string]chan []Message // channel → result (one waiter per channel)
36 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ handlers bool
37 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
38 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
39 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ type batch struct {
40 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ channel string
41 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ msgs []Message
42 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
43 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
44 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // New creates a Fetcher and registers the necessary BATCH handlers on the
45 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // client. The client's Config.SupportedCaps should include
46 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // "draft/chathistory" (or "chathistory") so the capability is negotiated.
47 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func New(client *girc.Client) *Fetcher {
48 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f := &Fetcher{
49 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ client: client,
50 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ batches: make(map[string]*batch),
51 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ waiters: make(map[string]chan []Message),
52 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
53 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.registerHandlers()
54 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return f
55 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
56 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
57 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func (f *Fetcher) registerHandlers() {
58 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Lock()
59 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ defer f.mu.Unlock()
60 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if f.handlers {
61 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return
62 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
63 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.handlers = true
64 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
65 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // BATCH open/close.
66 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.client.Handlers.AddBg("BATCH", func(_ *girc.Client, e girc.Event) {
67 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if len(e.Params) < 1 {
68 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return
69 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
70 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ raw := e.Params[0]
71 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if strings.HasPrefix(raw, "+") {
72 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ref := raw[1:]
73 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if len(e.Params) >= 2 && e.Params[1] == "chathistory" {
74 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ch := ""
75 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if len(e.Params) >= 3 {
76 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ch = e.Params[2]
77 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
78 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Lock()
79 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.batches[ref] = &batch{channel: ch}
80 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
81 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
82 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ } else if strings.HasPrefix(raw, "-") {
83 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ref := raw[1:]
84 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Lock()
85 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ b, ok := f.batches[ref]
86 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if ok {
87 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ delete(f.batches, ref)
88 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if w, wok := f.waiters[b.channel]; wok {
89 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ delete(f.waiters, b.channel)
90 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
91 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ w <- b.msgs
92 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return
93 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
94 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
95 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
96 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
97 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ })
98 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
99 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Collect PRIVMSGs tagged with a tracked batch ref.
100 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.client.Handlers.AddBg(girc.PRIVMSG, func(_ *girc.Client, e girc.Event) {
101 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ batchRef, ok := e.Tags.Get("batch")
102 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if !ok || batchRef == "" {
103 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return
104 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
105 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
106 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Lock()
107 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ b, tracked := f.batches[batchRef]
108 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if !tracked {
109 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
110 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return
111 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
112 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
113 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ nick := ""
114 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if e.Source != nil {
115 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ nick = e.Source.Name
116 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
117 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ acct, _ := e.Tags.Get("account")
118 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ msgID, _ := e.Tags.Get("msgid")
119 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
120 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ b.msgs = append(b.msgs, Message{
121 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ At: e.Timestamp,
122 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Nick: nick,
123 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Account: acct,
124 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Text: e.Last(),
125 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ MsgID: msgID,
126 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ })
127 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
128 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ })
129 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
130 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
131 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Latest fetches the N most recent messages from a channel using
132 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // CHATHISTORY LATEST. Blocks until the server responds or ctx expires.
133 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func (f *Fetcher) Latest(ctx context.Context, channel string, count int) ([]Message, error) {
134 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ result := make(chan []Message, 1)
135 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
136 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Lock()
137 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.waiters[channel] = result
138 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
139 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
140 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err := f.client.Cmd.SendRawf("CHATHISTORY LATEST %s * %d", channel, count); err != nil {
141 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Lock()
142 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ delete(f.waiters, channel)
143 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
144 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return nil, fmt.Errorf("chathistory: send: %w", err)
145 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
146 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
147 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ select {
148 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ case msgs := <-result:
149 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return msgs, nil
150 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ case <-ctx.Done():
151 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Lock()
152 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ delete(f.waiters, channel)
153 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
154 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return nil, ctx.Err()
155 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
156 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
157 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
158 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Before fetches up to count messages before the given timestamp.
159 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func (f *Fetcher) Before(ctx context.Context, channel string, before time.Time, count int) ([]Message, error) {
160 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ result := make(chan []Message, 1)
161 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
162 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Lock()
163 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.waiters[channel] = result
164 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
165 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
166 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ts := before.UTC().Format("2006-01-02T15:04:05.000Z")
167 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err := f.client.Cmd.SendRawf("CHATHISTORY BEFORE %s timestamp=%s %d", channel, ts, count); err != nil {
168 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Lock()
169 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ delete(f.waiters, channel)
170 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
171 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return nil, fmt.Errorf("chathistory: send: %w", err)
172 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
173 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
174 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ select {
175 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ case msgs := <-result:
176 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return msgs, nil
177 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ case <-ctx.Done():
178 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Lock()
179 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ delete(f.waiters, channel)
180 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f.mu.Unlock()
181 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return nil, ctx.Err()
182 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
183 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }