commit 42b47ec

sewn  ·  2026-07-23 17:33:49 +0000 UTC
parent 55c3548
Revert "Support opening irc:// links in senpai"

This reverts commit 8a399a916e2b1277dd792df446a0dde37d0d3c17.
13 files changed,  +34, -402
M app.go
M go.mod
M go.sum
M app.go
+8, -155
  1@@ -27,7 +27,6 @@ import (
  2 	"unicode"
  3 	"unicode/utf8"
  4 
  5-	"git.sr.ht/~delthas/senpai/varlinkservice"
  6 	"git.sr.ht/~rockorager/vaxis"
  7 	"golang.org/x/net/proxy"
  8 
  9@@ -134,8 +133,8 @@ type App struct {
 10 
 11 	monitor map[string]map[string]struct{} // set of targets we want to monitor per netID, best-effort. netID->target->{}
 12 
 13-	networkLock sync.RWMutex                 // locks networks
 14-	networks    map[string]map[string]string // set of network IDs to attributes we want to connect to; to be locked with networkLock
 15+	networkLock sync.RWMutex        // locks networks
 16+	networks    map[string]struct{} // set of network IDs we want to connect to; to be locked with networkLock
 17 
 18 	pendingCompletions    map[string][]pendingCompletion
 19 	pendingCompletionsOff int
 20@@ -171,7 +170,7 @@ func NewApp(cfg Config) (app *App, err error) {
 21 	}
 22 
 23 	app = &App{
 24-		networks: map[string]map[string]string{
 25+		networks: map[string]struct{}{
 26 			"": {}, // add the master network by default
 27 		},
 28 		pendingCompletions: make(map[string][]pendingCompletion),
 29@@ -386,41 +385,6 @@ func (app *App) wantsNetwork(netID string) bool {
 30 	return ok
 31 }
 32 
 33-func (app *App) findNetworkByLink(link string) (host string, target string, netID string) {
 34-	// links often do not contain encoded #
 35-	link = strings.ReplaceAll(link, "#", "%23")
 36-
 37-	u, err := url.Parse(link)
 38-	if err != nil {
 39-		return "", "", ""
 40-	}
 41-	host, port, err := net.SplitHostPort(u.Host)
 42-	if err != nil {
 43-		host = u.Host
 44-		port = "6697"
 45-	}
 46-	target = strings.TrimLeft(u.Path, "/")
 47-	if i := strings.IndexAny(target, "/,"); i >= 0 {
 48-		target = target[:i]
 49-	}
 50-	app.networkLock.Lock()
 51-	defer app.networkLock.Unlock()
 52-	for netID, attrs := range app.networks {
 53-		if attrs["host"] != host {
 54-			continue
 55-		}
 56-		p := attrs["port"]
 57-		if p == "" {
 58-			p = "6697"
 59-		}
 60-		if p != port {
 61-			continue
 62-		}
 63-		return u.Host, target, netID
 64-	}
 65-	return u.Host, target, ""
 66-}
 67-
 68 // ircLoop maintains a connection to the IRC server by connecting and then
 69 // forwarding IRC events to app.events repeatedly.
 70 func (app *App) ircLoop(netID string) {
 71@@ -684,38 +648,6 @@ func (app *App) handleUIEvent(ev interface{}) bool {
 72 		} else {
 73 			app.uploadingProgress = &ev.Progress
 74 		}
 75-	case *events.EventOpenLink:
 76-		host, target, netID := app.findNetworkByLink(ev.Link)
 77-		if netID != "" {
 78-			if cNetID, _ := app.win.CurrentBuffer(); cNetID != netID {
 79-				app.win.JumpBufferNetwork(netID, "")
 80-			}
 81-			if target == "" {
 82-				break
 83-			}
 84-			if !app.win.JumpBufferNetwork(netID, target) {
 85-				if s := app.sessions[netID]; s != nil && s.IsChannel(target) {
 86-					cNetID, cTarget := app.win.CurrentBuffer()
 87-					app.win.AddLine(cNetID, cTarget, ui.Line{
 88-						At:   time.Now(),
 89-						Head: ui.PlainString("--"),
 90-						Body: ui.PlainString("An IRC link of a new channel was opened. Enter to add and join that channel."),
 91-					})
 92-					app.win.InputSet(fmt.Sprintf("/join %v", target))
 93-				} else {
 94-					i, _ := app.addUserBuffer(netID, target, time.Time{})
 95-					app.win.JumpBufferIndex(i)
 96-				}
 97-			}
 98-		} else {
 99-			netID, target := app.win.CurrentBuffer()
100-			app.win.AddLine(netID, target, ui.Line{
101-				At:   time.Now(),
102-				Head: ui.PlainString("--"),
103-				Body: ui.PlainString("An IRC link of a new network was opened. Enter to add and join that network."),
104-			})
105-			app.win.InputSet(fmt.Sprintf("/bouncer network create -addr %q", host))
106-		}
107 	default:
108 		// TODO: missing event types
109 	}
110@@ -1573,37 +1505,6 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
111 			// TODO: batch MONITOR +
112 			s.MonitorAdd(target)
113 		}
114-
115-		if netID == "" || app.cfg.OpenLink == "" {
116-			break
117-		}
118-		_, target, foundNetID := app.findNetworkByLink(app.cfg.OpenLink)
119-		if netID != foundNetID {
120-			break
121-		}
122-		app.cfg.OpenLink = ""
123-		app.lastNetID = ""
124-		app.lastBuffer = ""
125-		if cNetID, _ := app.win.CurrentBuffer(); cNetID != netID {
126-			app.win.JumpBufferNetwork(netID, "")
127-		}
128-		if target == "" {
129-			break
130-		}
131-		if !app.win.JumpBufferNetwork(netID, target) {
132-			if s.IsChannel(target) {
133-				cNetID, cTarget := app.win.CurrentBuffer()
134-				app.win.AddLine(cNetID, cTarget, ui.Line{
135-					At:   time.Now(),
136-					Head: ui.PlainString("--"),
137-					Body: ui.PlainString("An IRC link of a new channel was opened. Enter to add and join that channel."),
138-				})
139-				app.win.InputSet(fmt.Sprintf("/join %v", target))
140-			} else {
141-				i, _ := app.addUserBuffer(netID, target, time.Time{})
142-				app.win.JumpBufferIndex(i)
143-			}
144-		}
145 	case irc.SelfNickEvent:
146 		if !app.cfg.StatusEnabled {
147 			break
148@@ -1856,17 +1757,11 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
149 		}
150 	case irc.BouncerNetworkEvent:
151 		if !ev.Delete {
152-			app.networkLock.Lock()
153-			if _, ok := app.networks[ev.ID]; !ok {
154-				app.networks[ev.ID] = ev.Attrs
155-			} else {
156-				for k, v := range ev.Attrs {
157-					app.networks[ev.ID][k] = v
158-				}
159-			}
160-			app.networkLock.Unlock()
161-			_, added := app.win.AddBuffer(ev.ID, ev.Attrs["name"], "")
162+			_, added := app.win.AddBuffer(ev.ID, ev.Name, "")
163 			if added {
164+				app.networkLock.Lock()
165+				app.networks[ev.ID] = struct{}{}
166+				app.networkLock.Unlock()
167 				go app.ircLoop(ev.ID)
168 			}
169 		} else {
170@@ -1882,39 +1777,6 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
171 			}
172 			app.win.RemoveNetworkBuffers(ev.ID)
173 		}
174-	case irc.BouncerNetworkListEvent:
175-		for _, ev := range ev {
176-			app.networkLock.Lock()
177-			if _, ok := app.networks[ev.ID]; !ok {
178-				app.networks[ev.ID] = ev.Attrs
179-			} else {
180-				for k, v := range ev.Attrs {
181-					app.networks[ev.ID][k] = v
182-				}
183-			}
184-			app.networkLock.Unlock()
185-			_, added := app.win.AddBuffer(ev.ID, ev.Attrs["name"], "")
186-			if added {
187-				go app.ircLoop(ev.ID)
188-			}
189-		}
190-
191-		if app.cfg.OpenLink == "" {
192-			break
193-		}
194-		host, _, foundNetID := app.findNetworkByLink(app.cfg.OpenLink)
195-		if foundNetID != "" {
196-			// We will handle the link in the session for that network.
197-			break
198-		}
199-		app.cfg.OpenLink = ""
200-		netID, target := app.win.CurrentBuffer()
201-		app.win.AddLine(netID, target, ui.Line{
202-			At:   time.Now(),
203-			Head: ui.PlainString("--"),
204-			Body: ui.PlainString("An IRC link of a new network was opened. Enter to add and join that network."),
205-		})
206-		app.win.InputSet(fmt.Sprintf("/bouncer network create -addr %q", host))
207 	case irc.ListEvent:
208 		for _, item := range ev {
209 			text := fmt.Sprintf("There are %4s users on channel %s", item.Count, item.Channel)
210@@ -1943,6 +1805,7 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
211 				Foreground: app.cfg.Colors.Status,
212 			}),
213 		})
214+		return
215 	case irc.ErrorEvent:
216 		var head string
217 		var body string
218@@ -2627,16 +2490,6 @@ func (app *App) addUserBuffer(netID, buffer string, t time.Time) (i int, added b
219 	return
220 }
221 
222-func (app *App) OpenLink(in *varlinkservice.OpenLinkIn) (*varlinkservice.OpenLinkOut, error) {
223-	app.postEvent(event{
224-		src: "*",
225-		content: &events.EventOpenLink{
226-			Link: in.Link,
227-		},
228-	})
229-	return nil, nil
230-}
231-
232 func keyNameMatch(name string) *keyMatch {
233 	parts := strings.Split(name, "+")
234 	mods := parts[:len(parts)-1]
+0, -82
  1@@ -7,8 +7,6 @@ import (
  2 	"fmt"
  3 	"hash/fnv"
  4 	"io"
  5-	"net"
  6-	"net/url"
  7 	"os"
  8 	"os/signal"
  9 	"os/user"
 10@@ -20,8 +18,6 @@ import (
 11 	"time"
 12 
 13 	"git.sr.ht/~delthas/senpai"
 14-	"git.sr.ht/~delthas/senpai/varlinkservice"
 15-	"github.com/emersion/go-varlink"
 16 )
 17 
 18 func main() {
 19@@ -44,26 +40,6 @@ func main() {
 20 		return
 21 	}
 22 
 23-	socketDir := os.Getenv("XDG_RUNTIME_DIR")
 24-	if socketDir == "" {
 25-		socketDir = os.TempDir()
 26-	}
 27-	socketDir = filepath.Join(socketDir, "senpai")
 28-
 29-	var link string
 30-	if _, err := url.Parse(flag.Arg(0)); err == nil {
 31-		link = flag.Arg(0)
 32-	}
 33-	if link != "" {
 34-		ok, err := sendOpenLink(socketDir, link)
 35-		if err != nil {
 36-			fmt.Fprintf(os.Stderr, "failed to send open link to another instance: %v\n", err)
 37-		} else if ok {
 38-			// Sent link open command to another instance, exit.
 39-			return
 40-		}
 41-	}
 42-
 43 	var previousConfigPath string
 44 	if configPath == "" {
 45 		configDir, err := os.UserConfigDir()
 46@@ -195,7 +171,6 @@ func main() {
 47 		fmt.Fprintf(os.Stderr, "\n")
 48 	}
 49 
 50-	cfg.OpenLink = link
 51 	cfg.Debug = cfg.Debug || debug
 52 	if nickname != "" {
 53 		cfg.Nick = nickname
 54@@ -223,14 +198,6 @@ func main() {
 55 		app.Close()
 56 	}()
 57 
 58-	if cfg.LocalIntegrations {
 59-		socketPath := filepath.Join(socketDir, fmt.Sprintf("%v.sock", os.Getpid()))
 60-		if err := listenVarlink(socketPath, app); err != nil {
 61-			fmt.Fprintf(os.Stderr, "failed to send open link to another instance: %v\n", err)
 62-		}
 63-		defer os.Remove(socketPath)
 64-	}
 65-
 66 	app.Run()
 67 	app.Close()
 68 	if !cfg.Transient {
 69@@ -373,52 +340,3 @@ func writeLastStamp(app *senpai.App, hash string) {
 70 		fmt.Fprintf(os.Stderr, "failed to write last stamp at %q: %s\n", p, err)
 71 	}
 72 }
 73-
 74-func sendOpenLink(socketDir string, link string) (ok bool, err error) {
 75-	es, err := os.ReadDir(socketDir)
 76-	if os.IsNotExist(err) {
 77-		return false, nil
 78-	}
 79-	if err != nil {
 80-		return false, err
 81-	}
 82-	for _, e := range es {
 83-		if filepath.Ext(e.Name()) != ".sock" {
 84-			continue
 85-		}
 86-		conn, err := net.Dial("unix", filepath.Join(socketDir, e.Name()))
 87-		if err != nil {
 88-			return false, err
 89-		}
 90-		c := varlinkservice.Client{Client: varlink.NewClient(conn)}
 91-		defer c.Close()
 92-		if _, err := c.OpenLink(&varlinkservice.OpenLinkIn{Link: link}); err != nil {
 93-			return false, err
 94-		}
 95-		return true, nil
 96-	}
 97-	return false, nil
 98-}
 99-
100-func listenVarlink(socketPath string, backend varlinkservice.Backend) error {
101-	if err := os.MkdirAll(filepath.Dir(socketPath), 0700); err != nil {
102-		return err
103-	}
104-	if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) {
105-		return err
106-	}
107-	l, err := net.Listen("unix", socketPath)
108-	if err != nil {
109-		return err
110-	}
111-	server := varlink.NewServer()
112-	server.Handler = varlinkservice.Handler{
113-		Backend: backend,
114-	}
115-	go func() {
116-		if err := server.Serve(l); err != nil {
117-			fmt.Fprintf(os.Stderr, "failed to serve varlink server: %v\n", err)
118-		}
119-	}()
120-	return nil
121-}
+0, -1
1@@ -115,7 +115,6 @@ type Config struct {
2 
3 	Colors ui.ConfigColors
4 
5-	OpenLink          string
6 	Debug             bool
7 	Transient         bool
8 	LocalIntegrations bool
+1, -2
1@@ -8,7 +8,6 @@ Keywords=Chat;IRC;IM;Messaging
2 Categories=Network;InstantMessaging;Chat;IRCClient;ConsoleOnly
3 
4 Type=Application
5-MimeType=x-scheme-handler/irc;x-scheme-handler/ircs
6 Icon=senpai
7 Terminal=true
8-Exec=senpai %u
9+Exec=senpai
+1, -4
 1@@ -6,7 +6,7 @@ senpai - your everyday IRC student
 2 
 3 # SYNOPSIS
 4 
 5-*senpai* [options...] [link]
 6+*senpai* [options...]
 7 
 8 # OPTIONS
 9 
10@@ -24,9 +24,6 @@ senpai - your everyday IRC student
11 *-version*
12 	Display version info.
13 
14-senpai can also be opened with an _irc://_ link to a server or target. In that
15-case, the link is opened in an existing instance if available.
16-
17 # DESCRIPTION
18 
19 senpai is an IRC client made for bouncers.  It supports the newest IRC
+0, -4
1@@ -45,7 +45,3 @@ type EventFileUpload struct {
2 	Location string
3 	Error    string
4 }
5-
6-type EventOpenLink struct {
7-	Link string
8-}
M go.mod
+1, -3
 1@@ -9,20 +9,18 @@ require (
 2 	github.com/delthas/go-libnp v0.2.0
 3 	github.com/delthas/go-localeinfo v0.2.0
 4 	github.com/disintegration/imaging v1.6.2
 5-	github.com/emersion/go-varlink v0.0.0-20251006073247-e7ad9f7950c7
 6 	github.com/godbus/dbus/v5 v5.1.0
 7 	github.com/rivo/uniseg v0.4.7
 8-	golang.org/x/image v0.24.0
 9 	golang.org/x/net v0.35.0
10 	golang.org/x/time v0.10.0
11 	mvdan.cc/xurls/v2 v2.5.0
12 )
13 
14 require (
15-	github.com/dave/jennifer v1.7.1 // indirect
16 	github.com/go-ole/go-ole v1.3.0 // indirect
17 	github.com/mattn/go-runewidth v0.0.16 // indirect
18 	github.com/mattn/go-sixel v0.0.5 // indirect
19 	github.com/soniakeys/quant v1.0.0 // indirect
20+	golang.org/x/image v0.24.0 // indirect
21 	golang.org/x/sys v0.30.0 // indirect
22 )
M go.sum
+0, -4
 1@@ -4,8 +4,6 @@ git.sr.ht/~rockorager/vaxis v0.15.1-0.20260305170555-c3371e09d426 h1:UPaRLWZqH6s
 2 git.sr.ht/~rockorager/vaxis v0.15.1-0.20260305170555-c3371e09d426/go.mod h1:ptCAzb19xAhqhfAmCsN9Xnxdx01vlXkwZcuMYPnyqzE=
 3 github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
 4 github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
 5-github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
 6-github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
 7 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 8 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 9 github.com/delthas/go-libnp v0.2.0 h1:OVll7Z9CER0rYpgiglXN1QvuNtkyY5X9uzLqm+xDstA=
10@@ -14,8 +12,6 @@ github.com/delthas/go-localeinfo v0.2.0 h1:0K5F5GQ5p4ealZjuBs4eubHLOIL21/YWSxs55
11 github.com/delthas/go-localeinfo v0.2.0/go.mod h1:sG54BxlyQgIskYURLrg7mvhoGBe0Qq12DNtYRALwNa4=
12 github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
13 github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
14-github.com/emersion/go-varlink v0.0.0-20251006073247-e7ad9f7950c7 h1:GtfymsmxgcEdOuYLm5CR6VS5pTEXfUKkQDp/wRvvyA8=
15-github.com/emersion/go-varlink v0.0.0-20251006073247-e7ad9f7950c7/go.mod h1:0/V0Ta8VUzKRLXUtyZS49soMI93Taqlm63wX5nx6YEo=
16 github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
17 github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
18 github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
+1, -3
 1@@ -128,8 +128,6 @@ type MetadataChangeEvent struct {
 2 
 3 type BouncerNetworkEvent struct {
 4 	ID     string
 5-	Attrs  map[string]string
 6+	Name   string
 7 	Delete bool
 8 }
 9-
10-type BouncerNetworkListEvent []BouncerNetworkEvent
+22, -40
  1@@ -129,15 +129,14 @@ type Session struct {
  2 	typings      *Typings               // incoming typing notifications.
  3 	typingStamps map[string]typingStamp // user typing instants.
  4 
  5-	nick     string
  6-	nickCf   string // casemapped nickname.
  7-	user     string
  8-	real     string
  9-	acct     string
 10-	host     string
 11-	netID    string
 12-	netAttrs map[string]string
 13-	auth     SASLClient
 14+	nick   string
 15+	nickCf string // casemapped nickname.
 16+	user   string
 17+	real   string
 18+	acct   string
 19+	host   string
 20+	netID  string
 21+	auth   SASLClient
 22 
 23 	availableCaps map[string]string
 24 	enabledCaps   map[string]struct{}
 25@@ -163,21 +162,18 @@ type Session struct {
 26 	clientTagListIsAllow bool // whether clientTagList is an allowlist (true) or a blocklist (false)
 27 	clientTagList        map[string]struct{}
 28 
 29-	users                  map[string]*User        // known users.
 30-	channels               map[string]Channel      // joined channels.
 31-	metadata               map[string]Metadata     // known target metadata.
 32-	chBatches              map[string]HistoryEvent // channel history batches being processed.
 33-	chReqs                 map[string]struct{}     // set of targets for which history is currently requested.
 34-	targetsBatchID         string                  // ID of the channel history targets batch being processed.
 35-	targetsBatch           HistoryTargetsEvent     // channel history targets batch being processed.
 36-	searchBatchID          string                  // ID of the search targets batch being processed.
 37-	searchBatch            SearchEvent             // search batch being processed.
 38-	bouncerNetworksBatchID string                  // ID of the bouncer network batch being processed.
 39-	bouncerNetworksBatch   BouncerNetworkListEvent // bouncer network batch being processed.
 40-	monitors               map[string]struct{}     // set of users we want to monitor (and keep even if they are disconnected).
 41-	pendingList            ListEvent               // current list response being received (flushed on list end).
 42-
 43-	pendingChannels map[string]time.Time // set of join requests stamps for channels.
 44+	users           map[string]*User        // known users.
 45+	channels        map[string]Channel      // joined channels.
 46+	metadata        map[string]Metadata     // known target metadata.
 47+	chBatches       map[string]HistoryEvent // channel history batches being processed.
 48+	chReqs          map[string]struct{}     // set of targets for which history is currently requested.
 49+	targetsBatchID  string                  // ID of the channel history targets batch being processed.
 50+	targetsBatch    HistoryTargetsEvent     // channel history targets batch being processed.
 51+	searchBatchID   string                  // ID of the search targets batch being processed.
 52+	searchBatch     SearchEvent             // search batch being processed.
 53+	monitors        map[string]struct{}     // set of users we want to monitor (and keep even if they are disconnected).
 54+	pendingList     ListEvent               // current list response being received (flushed on list end).
 55+	pendingChannels map[string]time.Time    // set of join requests stamps for channels.
 56 
 57 	receivedISupport bool
 58 	receivedUserMode bool
 59@@ -797,15 +793,6 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
 60 				s.searchBatch.Messages = append(s.searchBatch.Messages, ev)
 61 				return nil, nil
 62 			}
 63-		} else if id == s.bouncerNetworksBatchID {
 64-			ev, err := s.handleMessageRegistered(msg, true)
 65-			if err != nil {
 66-				return nil, err
 67-			}
 68-			if ev, ok := ev.(BouncerNetworkEvent); ok {
 69-				s.bouncerNetworksBatch = append(s.bouncerNetworksBatch, ev)
 70-				return nil, nil
 71-			}
 72 		} else if b, ok := s.chBatches[id]; ok {
 73 			ev, err := s.handleMessageRegistered(msg, true)
 74 			if err != nil {
 75@@ -1468,9 +1455,6 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
 76 			case "soju.im/search":
 77 				s.searchBatchID = id
 78 				s.searchBatch = SearchEvent{}
 79-			case "soju.im/bouncer-networks":
 80-				s.bouncerNetworksBatchID = id
 81-				s.bouncerNetworksBatch = BouncerNetworkListEvent{}
 82 			}
 83 		} else {
 84 			if b, ok := s.chBatches[id]; ok {
 85@@ -1484,9 +1468,6 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
 86 			} else if s.searchBatchID == id {
 87 				s.searchBatchID = ""
 88 				return s.searchBatch, nil
 89-			} else if s.bouncerNetworksBatchID == id {
 90-				s.bouncerNetworksBatchID = ""
 91-				return s.bouncerNetworksBatch, nil
 92 			}
 93 		}
 94 	case "NICK":
 95@@ -1594,7 +1575,8 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
 96 			ID: id,
 97 		}
 98 		if msg.Params[2] != "*" {
 99-			event.Attrs = parseTags(msg.Params[2])
100+			attrs := parseTags(msg.Params[2])
101+			event.Name = attrs["name"]
102 		} else {
103 			event.Delete = true
104 		}
+0, -92
 1@@ -1,92 +0,0 @@
 2-// Code generated by go-varlink/varlinkgen. DO NOT EDIT.
 3-
 4-package varlinkservice
 5-
 6-import (
 7-	"encoding/json"
 8-
 9-	govarlink "github.com/emersion/go-varlink"
10-)
11-
12-type OpenLinkIn struct {
13-	Link string `json:"link"`
14-}
15-type OpenLinkOut struct{}
16-
17-type Client struct {
18-	*govarlink.Client
19-}
20-
21-func unmarshalError(err error) error {
22-	verr, ok := err.(*govarlink.ClientError)
23-	if !ok {
24-		return err
25-	}
26-	var v error
27-	switch verr.Name {
28-	default:
29-		return err
30-	}
31-	if err := json.Unmarshal(verr.Parameters, v); err != nil {
32-		return err
33-	}
34-	return v
35-}
36-func (c Client) OpenLink(in *OpenLinkIn) (*OpenLinkOut, error) {
37-	if in == nil {
38-		in = new(OpenLinkIn)
39-	}
40-	out := new(OpenLinkOut)
41-	err := c.Client.Do("fr.delthas.senpai.OpenLink", in, out)
42-	return out, unmarshalError(err)
43-}
44-
45-type Backend interface {
46-	OpenLink(*OpenLinkIn) (*OpenLinkOut, error)
47-}
48-
49-type Handler struct {
50-	Backend Backend
51-}
52-
53-func marshalError(err error) error {
54-	var name string
55-	switch err.(type) {
56-	default:
57-		return err
58-	}
59-	return &govarlink.ServerError{
60-		Name:       name,
61-		Parameters: err,
62-	}
63-}
64-func (h Handler) HandleVarlink(call *govarlink.ServerCall, req *govarlink.ServerRequest) error {
65-	var (
66-		out interface{}
67-		err error
68-	)
69-	switch req.Method {
70-	case "fr.delthas.senpai.OpenLink":
71-		in := new(OpenLinkIn)
72-		if err := json.Unmarshal(req.Parameters, in); err != nil {
73-			return err
74-		}
75-		out, err = h.Backend.OpenLink(in)
76-	default:
77-		err = &govarlink.ServerError{
78-			Name:       "org.varlink.service.MethodNotFound",
79-			Parameters: map[string]string{"method": req.Method},
80-		}
81-	}
82-	if err != nil {
83-		return marshalError(err)
84-	}
85-	return call.CloseWithReply(out)
86-}
87-
88-func (h Handler) Register(reg *govarlink.Registry) {
89-	reg.Add(&govarlink.RegistryInterface{
90-		Definition: "interface fr.delthas.senpai\n\nmethod OpenLink(link: string) -> ()\n",
91-		Name:       "fr.delthas.senpai",
92-	}, h)
93-}
1@@ -1,3 +0,0 @@
2-interface fr.delthas.senpai
3-
4-method OpenLink(link: string) -> ()
+0, -9
 1@@ -1,9 +0,0 @@
 2-//go:build generate
 3-
 4-package varlinkservice
 5-
 6-import (
 7-	_ "github.com/emersion/go-varlink/cmd/varlinkgen"
 8-)
 9-
10-//go:generate go run github.com/emersion/go-varlink/cmd/varlinkgen -i fr.delthas.senpai.varlink