source: code/trunk/bridge.go@ 783

Last change on this file since 783 was 781, checked in by delthas, 3 years ago

Add support for the wip soju.im/read capability and READ command

READ lets downstream clients share information between each other about
what messages have been read by other downstreams.

Each target/entity has an optional corresponding read receipt, which is
stored as a timestamp.

  • When a downstream sends: READ #chan timestamp=2020-01-01T01:23:45.000Z the read receipt for that target is set to that date
  • soju sends READ to downstreams:
    • on JOIN, if the client uses the soju.im/read capability
    • when the read receipt timestamp is set by any downstream

The read receipt date is clamped by the previous receipt date and the
current time.

File size: 3.0 KB
Line 
1package soju
2
3import (
4 "context"
5 "fmt"
6 "strconv"
7 "strings"
8
9 "gopkg.in/irc.v3"
10)
11
12func forwardChannel(ctx context.Context, dc *downstreamConn, ch *upstreamChannel) {
13 if !ch.complete {
14 panic("Tried to forward a partial channel")
15 }
16
17 // RPL_NOTOPIC shouldn't be sent on JOIN
18 if ch.Topic != "" {
19 sendTopic(dc, ch)
20 }
21
22 if dc.caps["soju.im/read"] {
23 channelCM := ch.conn.network.casemap(ch.Name)
24 r, err := dc.srv.db.GetReadReceipt(ctx, ch.conn.network.ID, channelCM)
25 if err != nil {
26 dc.logger.Printf("failed to get the read receipt for %q: %v", ch.Name, err)
27 } else {
28 timestampStr := "*"
29 if r != nil {
30 timestampStr = fmt.Sprintf("timestamp=%s", r.Timestamp.UTC().Format(serverTimeLayout))
31 }
32 dc.SendMessage(&irc.Message{
33 Prefix: dc.prefix(),
34 Command: "READ",
35 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name), timestampStr},
36 })
37 }
38 }
39
40 sendNames(dc, ch)
41}
42
43func sendTopic(dc *downstreamConn, ch *upstreamChannel) {
44 downstreamName := dc.marshalEntity(ch.conn.network, ch.Name)
45
46 if ch.Topic != "" {
47 dc.SendMessage(&irc.Message{
48 Prefix: dc.srv.prefix(),
49 Command: irc.RPL_TOPIC,
50 Params: []string{dc.nick, downstreamName, ch.Topic},
51 })
52 if ch.TopicWho != nil {
53 topicWho := dc.marshalUserPrefix(ch.conn.network, ch.TopicWho)
54 topicTime := strconv.FormatInt(ch.TopicTime.Unix(), 10)
55 dc.SendMessage(&irc.Message{
56 Prefix: dc.srv.prefix(),
57 Command: rpl_topicwhotime,
58 Params: []string{dc.nick, downstreamName, topicWho.String(), topicTime},
59 })
60 }
61 } else {
62 dc.SendMessage(&irc.Message{
63 Prefix: dc.srv.prefix(),
64 Command: irc.RPL_NOTOPIC,
65 Params: []string{dc.nick, downstreamName, "No topic is set"},
66 })
67 }
68}
69
70func sendNames(dc *downstreamConn, ch *upstreamChannel) {
71 downstreamName := dc.marshalEntity(ch.conn.network, ch.Name)
72
73 emptyNameReply := &irc.Message{
74 Prefix: dc.srv.prefix(),
75 Command: irc.RPL_NAMREPLY,
76 Params: []string{dc.nick, string(ch.Status), downstreamName, ""},
77 }
78 maxLength := maxMessageLength - len(emptyNameReply.String())
79
80 var buf strings.Builder
81 for _, entry := range ch.Members.innerMap {
82 nick := entry.originalKey
83 memberships := entry.value.(*memberships)
84 s := memberships.Format(dc) + dc.marshalEntity(ch.conn.network, nick)
85
86 n := buf.Len() + 1 + len(s)
87 if buf.Len() != 0 && n > maxLength {
88 // There's not enough space for the next space + nick.
89 dc.SendMessage(&irc.Message{
90 Prefix: dc.srv.prefix(),
91 Command: irc.RPL_NAMREPLY,
92 Params: []string{dc.nick, string(ch.Status), downstreamName, buf.String()},
93 })
94 buf.Reset()
95 }
96
97 if buf.Len() != 0 {
98 buf.WriteByte(' ')
99 }
100 buf.WriteString(s)
101 }
102
103 if buf.Len() != 0 {
104 dc.SendMessage(&irc.Message{
105 Prefix: dc.srv.prefix(),
106 Command: irc.RPL_NAMREPLY,
107 Params: []string{dc.nick, string(ch.Status), downstreamName, buf.String()},
108 })
109 }
110
111 dc.SendMessage(&irc.Message{
112 Prefix: dc.srv.prefix(),
113 Command: irc.RPL_ENDOFNAMES,
114 Params: []string{dc.nick, downstreamName, "End of /NAMES list"},
115 })
116}
Note: See TracBrowser for help on using the repository browser.