Sfoglia il codice sorgente

support znc.in/playback

tags/v1.1.0-rc1
Shivaram Lingamneni 5 anni fa
parent
commit
b96fdb2293
9 ha cambiato i file con 184 aggiunte e 21 eliminazioni
  1. 6
    0
      gencapdefs.py
  2. 6
    1
      irc/caps/defs.go
  3. 32
    16
      irc/channel.go
  4. 2
    0
      irc/client.go
  5. 4
    0
      irc/commands.go
  6. 14
    4
      irc/handlers.go
  7. 7
    0
      irc/help.go
  8. 17
    0
      irc/misc_test.go
  9. 96
    0
      irc/znc.go

+ 6
- 0
gencapdefs.py Vedi File

@@ -165,6 +165,12 @@ CAPDEFS = [
165 165
         url="https://github.com/ircv3/ircv3-specifications/pull/362",
166 166
         standard="Proposed IRCv3",
167 167
     ),
168
+    CapDef(
169
+        identifier="ZNCPlayback",
170
+        name="znc.in/playback",
171
+        url="https://wiki.znc.in/Playback",
172
+        standard="ZNC vendor",
173
+    ),
168 174
 ]
169 175
 
170 176
 def validate_defs():

+ 6
- 1
irc/caps/defs.go Vedi File

@@ -7,7 +7,7 @@ package caps
7 7
 
8 8
 const (
9 9
 	// number of recognized capabilities:
10
-	numCapabs = 25
10
+	numCapabs = 26
11 11
 	// length of the uint64 array that represents the bitset:
12 12
 	bitsetLen = 1
13 13
 )
@@ -112,6 +112,10 @@ const (
112 112
 	// EventPlayback is the Proposed IRCv3 capability named "draft/event-playback":
113 113
 	// https://github.com/ircv3/ircv3-specifications/pull/362
114 114
 	EventPlayback Capability = iota
115
+
116
+	// ZNCPlayback is the ZNC vendor capability named "znc.in/playback":
117
+	// https://wiki.znc.in/Playback
118
+	ZNCPlayback Capability = iota
115 119
 )
116 120
 
117 121
 // `capabilityNames[capab]` is the string name of the capability `capab`
@@ -142,5 +146,6 @@ var (
142 146
 		"oragono.io/bnc",
143 147
 		"znc.in/self-message",
144 148
 		"draft/event-playback",
149
+		"znc.in/playback",
145 150
 	}
146 151
 )

+ 32
- 16
irc/channel.go Vedi File

@@ -620,25 +620,41 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
620 620
 	// TODO #259 can be implemented as Flush(false) (i.e., nonblocking) while holding joinPartMutex
621 621
 	rb.Flush(true)
622 622
 
623
-	var replayLimit int
624
-	customReplayLimit := client.AccountSettings().AutoreplayLines
625
-	if customReplayLimit != nil {
626
-		replayLimit = *customReplayLimit
627
-		maxLimit := channel.server.Config().History.ChathistoryMax
628
-		if maxLimit < replayLimit {
629
-			replayLimit = maxLimit
630
-		}
623
+	// autoreplay any messages as necessary
624
+	config := channel.server.Config()
625
+	var items []history.Item
626
+	if rb.session.zncPlaybackTimes != nil && (rb.session.zncPlaybackTimes.targets == nil || rb.session.zncPlaybackTimes.targets[chcfname]) {
627
+		items, _ = channel.history.Between(rb.session.zncPlaybackTimes.after, rb.session.zncPlaybackTimes.before, false, config.History.ChathistoryMax)
631 628
 	} else {
632
-		replayLimit = channel.server.Config().History.AutoreplayOnJoin
633
-	}
634
-	if 0 < replayLimit {
635
-		// TODO don't replay the client's own JOIN line?
636
-		items := channel.history.Latest(replayLimit)
637
-		if 0 < len(items) {
638
-			channel.replayHistoryItems(rb, items, true)
639
-			rb.Flush(true)
629
+		var replayLimit int
630
+		customReplayLimit := client.AccountSettings().AutoreplayLines
631
+		if customReplayLimit != nil {
632
+			replayLimit = *customReplayLimit
633
+			maxLimit := channel.server.Config().History.ChathistoryMax
634
+			if maxLimit < replayLimit {
635
+				replayLimit = maxLimit
636
+			}
637
+		} else {
638
+			replayLimit = channel.server.Config().History.AutoreplayOnJoin
639
+		}
640
+		if 0 < replayLimit {
641
+			items = channel.history.Latest(replayLimit)
640 642
 		}
641 643
 	}
644
+	// remove the client's own JOIN line from the replay
645
+	numItems := len(items)
646
+	for i := len(items) - 1; 0 <= i; i-- {
647
+		if items[i].Message.Msgid == message.Msgid {
648
+			// zero'ed items will not be replayed because their `Type` field is not recognized
649
+			items[i] = history.Item{}
650
+			numItems--
651
+			break
652
+		}
653
+	}
654
+	if 0 < numItems {
655
+		channel.replayHistoryItems(rb, items, true)
656
+		rb.Flush(true)
657
+	}
642 658
 }
643 659
 
644 660
 // plays channel join messages (the JOIN line, topic, and names) to a session.

+ 2
- 0
irc/client.go Vedi File

@@ -113,6 +113,8 @@ type Session struct {
113 113
 	maxlenRest   uint32
114 114
 	capState     caps.State
115 115
 	capVersion   caps.Version
116
+
117
+	zncPlaybackTimes *zncPlaybackTimes
116 118
 }
117 119
 
118 120
 // sets the session quit message, if there isn't one already

+ 4
- 0
irc/commands.go Vedi File

@@ -321,6 +321,10 @@ func init() {
321 321
 			handler:   whowasHandler,
322 322
 			minParams: 1,
323 323
 		},
324
+		"ZNC": {
325
+			handler:   zncHandler,
326
+			minParams: 1,
327
+		},
324 328
 	}
325 329
 
326 330
 	initializeServices()

+ 14
- 4
irc/handlers.go Vedi File

@@ -2001,12 +2001,16 @@ func messageHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
2001 2001
 			}
2002 2002
 			channel.SendSplitMessage(msg.Command, lowestPrefix, clientOnlyTags, client, splitMsg, rb)
2003 2003
 		} else {
2004
-			if service, isService := OragonoServices[strings.ToLower(targetString)]; isService {
2005
-				// NOTICE and TAGMSG to services are ignored
2006
-				if histType == history.Privmsg {
2004
+			// NOTICE and TAGMSG to services are ignored
2005
+			if histType == history.Privmsg {
2006
+				lowercaseTarget := strings.ToLower(targetString)
2007
+				if service, isService := OragonoServices[lowercaseTarget]; isService {
2007 2008
 					servicePrivmsgHandler(service, server, client, message, rb)
2009
+					continue
2010
+				} else if _, isZNC := zncHandlers[lowercaseTarget]; isZNC {
2011
+					zncPrivmsgHandler(client, lowercaseTarget, message, rb)
2012
+					continue
2008 2013
 				}
2009
-				continue
2010 2014
 			}
2011 2015
 
2012 2016
 			user := server.clients.Get(targetString)
@@ -2746,3 +2750,9 @@ func whowasHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
2746 2750
 	}
2747 2751
 	return false
2748 2752
 }
2753
+
2754
+// ZNC <module> [params]
2755
+func zncHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
2756
+	zncModuleHandler(client, msg.Params[0], msg.Params[1:], rb)
2757
+	return false
2758
+}

+ 7
- 0
irc/help.go Vedi File

@@ -540,6 +540,13 @@ Returns information for the given user(s).`,
540 540
 
541 541
 Returns historical information on the last user with the given nickname.`,
542 542
 	},
543
+	"znc": {
544
+		text: `ZNC <module> [params]
545
+
546
+Used to emulate features of the ZNC bouncer. This command is not intended
547
+for direct use by end users.`,
548
+		duplicate: true,
549
+	},
543 550
 
544 551
 	// Informational
545 552
 	"modes": {

+ 17
- 0
irc/misc_test.go Vedi File

@@ -0,0 +1,17 @@
1
+// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"testing"
8
+	"time"
9
+)
10
+
11
+func TestZncTimestampParser(t *testing.T) {
12
+	assertEqual(zncWireTimeToTime("1558338348.988"), time.Unix(1558338348, 988000000), t)
13
+	assertEqual(zncWireTimeToTime("1558338348.9"), time.Unix(1558338348, 900000000), t)
14
+	assertEqual(zncWireTimeToTime("1558338348"), time.Unix(1558338348, 0), t)
15
+	assertEqual(zncWireTimeToTime(".988"), time.Unix(0, 988000000), t)
16
+	assertEqual(zncWireTimeToTime("garbage"), time.Unix(0, 0), t)
17
+}

+ 96
- 0
irc/znc.go Vedi File

@@ -0,0 +1,96 @@
1
+// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"fmt"
8
+	"strconv"
9
+	"strings"
10
+	"time"
11
+)
12
+
13
+type zncCommandHandler func(client *Client, command string, params []string, rb *ResponseBuffer)
14
+
15
+var zncHandlers = map[string]zncCommandHandler{
16
+	"*playback": zncPlaybackHandler,
17
+}
18
+
19
+func zncPrivmsgHandler(client *Client, command string, privmsg string, rb *ResponseBuffer) {
20
+	zncModuleHandler(client, command, strings.Fields(privmsg), rb)
21
+}
22
+
23
+func zncModuleHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
24
+	command = strings.ToLower(command)
25
+	if subHandler, ok := zncHandlers[command]; ok {
26
+		subHandler(client, command, params, rb)
27
+	} else {
28
+		rb.Add(nil, "*status!znc@znc.in", "NOTICE", rb.target.Nick(), fmt.Sprintf(client.t("No such module [%s]"), command))
29
+	}
30
+}
31
+
32
+// "number of seconds (floating point for millisecond precision) elapsed since January 1, 1970"
33
+func zncWireTimeToTime(str string) (result time.Time) {
34
+	var secondsPortion, fracPortion string
35
+	dot := strings.IndexByte(str, '.')
36
+	if dot == -1 {
37
+		secondsPortion = str
38
+	} else {
39
+		secondsPortion = str[:dot]
40
+		fracPortion = str[dot:]
41
+	}
42
+	seconds, _ := strconv.ParseInt(secondsPortion, 10, 64)
43
+	fraction, _ := strconv.ParseFloat(fracPortion, 64)
44
+	return time.Unix(seconds, int64(fraction*1000000000))
45
+}
46
+
47
+type zncPlaybackTimes struct {
48
+	after   time.Time
49
+	before  time.Time
50
+	targets map[string]bool // nil for "*" (everything), otherwise the channel names
51
+}
52
+
53
+// https://wiki.znc.in/Playback
54
+// PRIVMSG *playback :play <target> [lower_bound] [upper_bound]
55
+// e.g., PRIVMSG *playback :play * 1558374442
56
+func zncPlaybackHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
57
+	if len(params) < 2 {
58
+		return
59
+	} else if strings.ToLower(params[0]) != "play" {
60
+		return
61
+	}
62
+	targetString := params[1]
63
+
64
+	var after, before time.Time
65
+	if 2 < len(params) {
66
+		after = zncWireTimeToTime(params[2])
67
+	}
68
+	if 3 < len(params) {
69
+		before = zncWireTimeToTime(params[3])
70
+	}
71
+
72
+	var targets map[string]bool
73
+
74
+	// OK: the user's PMs get played back immediately on receiving this,
75
+	// then we save the timestamps in the session to handle replay on future channel joins
76
+	config := client.server.Config()
77
+	if params[1] == "*" {
78
+		items, _ := client.history.Between(after, before, false, config.History.ChathistoryMax)
79
+		client.replayPrivmsgHistory(rb, items, true)
80
+	} else {
81
+		for _, targetName := range strings.Split(targetString, ",") {
82
+			if cfTarget, err := CasefoldChannel(targetName); err == nil {
83
+				if targets == nil {
84
+					targets = make(map[string]bool)
85
+				}
86
+				targets[cfTarget] = true
87
+			}
88
+		}
89
+	}
90
+
91
+	rb.session.zncPlaybackTimes = &zncPlaybackTimes{
92
+		after:   after,
93
+		before:  before,
94
+		targets: targets,
95
+	}
96
+}

Loading…
Annulla
Salva