summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordelthas <delthas@dille.cc>2021-12-01 13:46:42 +0100
committerdelthas <delthas@dille.cc>2022-02-10 11:21:26 +0100
commit8d2b4a928c2783657cccbfe7b9aa5a9f77be3c9e (patch)
tree6212dabe20439b901bf267dd8d1a1666376aa17b
parentdocs: update Ctrl-C behaviour (diff)
MONITOR user with whom we have an open buffer
-rw-r--r--app.go13
-rw-r--r--commands.go3
-rw-r--r--irc/events.go8
-rw-r--r--irc/rpl.go6
-rw-r--r--irc/session.go108
-rw-r--r--irc/tokens.go7
-rw-r--r--ui/ui.go16
7 files changed, 145 insertions, 16 deletions
diff --git a/app.go b/app.go
index 1144560..85836e8 100644
--- a/app.go
+++ b/app.go
@@ -95,6 +95,8 @@ type App struct {
lastNetID string
lastBuffer string
+ monitor map[string]map[string]struct{} // set of targets we want to monitor per netID, best-effort. netID->target->{}
+
lastMessageTime time.Time
lastCloseTime time.Time
}
@@ -105,6 +107,7 @@ func NewApp(cfg Config) (app *App, err error) {
events: make(chan event, eventChanSize),
cfg: cfg,
messageBounds: map[boundKey]bound{},
+ monitor: make(map[string]map[string]struct{}),
}
if cfg.Highlights != nil {
@@ -582,6 +585,9 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
s.Close()
}
app.sessions[netID] = s
+ if _, ok := app.monitor[netID]; !ok {
+ app.monitor[netID] = make(map[string]struct{})
+ }
return
}
if _, ok := ev.(irc.Typing); ok {
@@ -633,6 +639,10 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
Head: "--",
Body: ui.PlainString(body),
})
+ for target := range app.monitor[s.NetID()] {
+ // TODO: batch MONITOR +
+ s.MonitorAdd(target)
+ }
case irc.SelfNickEvent:
var body ui.StyledStringBuilder
body.WriteString(fmt.Sprintf("%s\u2192%s", ev.FormerNick, s.Nick()))
@@ -729,6 +739,8 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
buffer, line, notification := app.formatMessage(s, ev)
if buffer != "" && !s.IsChannel(buffer) {
if _, added := app.win.AddBuffer(netID, "", buffer); added {
+ app.monitor[netID][buffer] = struct{}{}
+ s.MonitorAdd(buffer)
s.NewHistoryRequest(buffer).
WithLimit(500).
Before(msg.TimeOrNow())
@@ -750,6 +762,7 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
if s.IsChannel(target) {
continue
}
+ s.MonitorAdd(target)
app.win.AddBuffer(netID, "", target)
// CHATHISTORY BEFORE excludes its bound, so add 1ms
// (precision of the time tag) to include that last message.
diff --git a/commands.go b/commands.go
index 403aec8..09582b6 100644
--- a/commands.go
+++ b/commands.go
@@ -341,6 +341,8 @@ func commandDoMsg(app *App, args []string) (err error) {
Time: time.Now(),
})
if buffer != "" && !s.IsChannel(target) {
+ app.monitor[netID][buffer] = struct{}{}
+ s.MonitorAdd(buffer)
app.win.AddBuffer(netID, "", buffer)
}
@@ -449,6 +451,7 @@ func commandDoQuery(app *App, args []string) (err error) {
if s.IsChannel(target) {
return fmt.Errorf("cannot query a channel, use JOIN instead")
}
+ s.MonitorAdd(target)
i, _ := app.win.AddBuffer(netID, "", target)
s.NewHistoryRequest(target).WithLimit(200).Before(time.Now())
app.win.JumpBufferIndex(i)
diff --git a/irc/events.go b/irc/events.go
index 0c84f2b..f8f002f 100644
--- a/irc/events.go
+++ b/irc/events.go
@@ -50,6 +50,14 @@ type UserQuitEvent struct {
Time time.Time
}
+type UserOnlineEvent struct {
+ User string
+}
+
+type UserOfflineEvent struct {
+ User string
+}
+
type TopicChangeEvent struct {
Channel string
Topic string
diff --git a/irc/rpl.go b/irc/rpl.go
index 6373972..398b5b3 100644
--- a/irc/rpl.go
+++ b/irc/rpl.go
@@ -87,6 +87,12 @@ const (
errUmodeunknownflag = "501" // :Unknown mode flag
errUsersdontmatch = "502" // :Can't change mode for other users
+ rplMononline = "730" // <nick> :target[!user@host][,target[!user@host]]*
+ rplMonoffline = "731" // <nick> :target[,target2]*
+ rplMonlist = "732" // <nick> :target[,target2]*
+ rplEndofmonlist = "733" // <nick> :End of MONITOR list
+ errMonlistisfull = "734" // <nick> <limit> <targets> :Monitor list is full.
+
rplLoggedin = "900" // <nick> <nick>!<ident>@<host> <account> :You are now logged in as <user>
rplLoggedout = "901" // <nick> <nick>!<ident>@<host> :You are now logged out
errNicklocked = "902" // :You must use a nick assigned to you
diff --git a/irc/session.go b/irc/session.go
index 3dd979c..96632bf 100644
--- a/irc/session.go
+++ b/irc/session.go
@@ -71,10 +71,11 @@ const (
TypingDone
)
-// User is a known IRC user (we share a channel with it).
+// User is a known IRC user.
type User struct {
- Name *Prefix // the nick, user and hostname of the user if known.
- Away bool // whether the user is away or not
+ Name *Prefix // the nick, user and hostname of the user if known.
+ Away bool // whether the user is away or not
+ Disconnected bool // can only be true for monitored users.
}
// Channel is a joined channel.
@@ -124,6 +125,7 @@ type Session struct {
historyLimit int
prefixSymbols string
prefixModes string
+ monitor bool
users map[string]*User // known users.
channels map[string]Channel // joined channels.
@@ -131,6 +133,7 @@ type Session struct {
chReqs map[string]struct{} // set of targets for which history is currently requested.
targetsBatchID string // ID of the channel history targets batch being processed.
targetsBatch HistoryTargetsEvent // channel history targets batch being processed.
+ monitors map[string]struct{} // set of users we want to monitor (and keep even if they are disconnected).
pendingChannels map[string]time.Time // set of join requests stamps for channels.
}
@@ -158,6 +161,7 @@ func NewSession(out chan<- Message, params SessionParams) *Session {
channels: map[string]Channel{},
chBatches: map[string]HistoryEvent{},
chReqs: map[string]struct{}{},
+ monitors: map[string]struct{}{},
pendingChannels: map[string]time.Time{},
}
@@ -235,16 +239,18 @@ func (s *Session) Names(target string) []Member {
names = make([]Member, 0, len(c.Members))
for u, pl := range c.Members {
names = append(names, Member{
- PowerLevel: pl,
- Name: u.Name.Copy(),
- Away: u.Away,
+ PowerLevel: pl,
+ Name: u.Name.Copy(),
+ Away: u.Away,
+ Disconnected: u.Disconnected,
})
}
}
} else if u, ok := s.users[s.Casemap(target)]; ok {
names = append(names, Member{
- Name: u.Name.Copy(),
- Away: u.Away,
+ Name: u.Name.Copy(),
+ Away: u.Away,
+ Disconnected: u.Disconnected,
})
names = append(names, Member{
Name: &Prefix{
@@ -278,7 +284,7 @@ func (s *Session) TypingStops() <-chan Typing {
func (s *Session) ChannelsSharedWith(name string) []string {
var user *User
- if u, ok := s.users[s.Casemap(name)]; ok {
+ if u, ok := s.users[s.Casemap(name)]; ok && !u.Disconnected {
user = u
} else {
return nil
@@ -420,6 +426,26 @@ func (s *Session) TypingStop(target string) {
s.out <- NewMessage("TAGMSG", target).WithTag("+typing", "done")
}
+func (s *Session) MonitorAdd(target string) {
+ targetCf := s.casemap(target)
+ if _, ok := s.monitors[targetCf]; !ok {
+ s.monitors[targetCf] = struct{}{}
+ if s.monitor {
+ s.out <- NewMessage("MONITOR", "+", target)
+ }
+ }
+}
+
+func (s *Session) MonitorRemove(target string) {
+ targetCf := s.casemap(target)
+ if _, ok := s.monitors[targetCf]; ok {
+ delete(s.monitors, targetCf)
+ if s.monitor {
+ s.out <- NewMessage("MONITOR", "-", target)
+ }
+ }
+}
+
type HistoryRequest struct {
s *Session
target string
@@ -603,12 +629,12 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
if s.host == "" {
s.out <- NewMessage("WHO", s.nick)
}
- return RegisteredEvent{}, nil
case rplIsupport:
if len(msg.Params) < 3 {
return nil, msg.errNotEnoughParams(3)
}
s.updateFeatures(msg.Params[1 : len(msg.Params)-1])
+ return RegisteredEvent{}, nil
case rplWhoreply:
var nick, host, flags, username string
if err := msg.ParseParams(nil, nil, &username, &host, nil, &nick, &flags, nil); err != nil {
@@ -812,6 +838,7 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
nickCf := s.Casemap(msg.Prefix.Name)
if u, ok := s.users[nickCf]; ok {
+ u.Disconnected = true
var channels []string
for channelCf, c := range s.channels {
if _, ok := c.Members[u]; ok {
@@ -827,6 +854,54 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
Time: msg.TimeOrNow(),
}, nil
}
+ case rplMononline:
+ for _, target := range strings.Split(msg.Params[1], ",") {
+ prefix := ParsePrefix(target)
+ if prefix == nil {
+ continue
+ }
+ nickCf := s.casemap(prefix.Name)
+
+ if _, ok := s.monitors[nickCf]; ok {
+ u, ok := s.users[nickCf]
+ if !ok {
+ u = &User{
+ Name: prefix,
+ }
+ s.users[nickCf] = u
+ }
+ if u.Disconnected {
+ u.Disconnected = false
+ return UserOnlineEvent{
+ User: u.Name.Name,
+ }, nil
+ }
+ }
+ }
+ case rplMonoffline:
+ for _, target := range strings.Split(msg.Params[1], ",") {
+ prefix := ParsePrefix(target)
+ if prefix == nil {
+ continue
+ }
+ nickCf := s.casemap(prefix.Name)
+
+ if _, ok := s.monitors[nickCf]; ok {
+ u, ok := s.users[nickCf]
+ if !ok {
+ u = &User{
+ Name: prefix,
+ }
+ s.users[nickCf] = u
+ }
+ if !u.Disconnected {
+ u.Disconnected = true
+ return UserOfflineEvent{
+ User: u.Name.Name,
+ }, nil
+ }
+ }
+ }
case rplNamreply:
var channel, names string
if err := msg.ParseParams(nil, nil, &channel, &names); err != nil {
@@ -1205,6 +1280,8 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
Code: code,
Message: strings.Join(msg.Params[2:], " "),
}, nil
+ case errMonlistisfull:
+ // silence monlist full error, we don't care because we do it best-effort
default:
if msg.IsReply() {
if len(msg.Params) < 2 {
@@ -1248,12 +1325,16 @@ func (s *Session) newMessageEvent(msg Message) (ev MessageEvent, err error) {
}
func (s *Session) cleanUser(parted *User) {
+ nameCf := s.Casemap(parted.Name.Name)
+ if _, ok := s.monitors[nameCf]; ok {
+ return
+ }
for _, c := range s.channels {
if _, ok := c.Members[parted]; ok {
return
}
}
- delete(s.users, s.Casemap(parted.Name.Name))
+ delete(s.users, nameCf)
}
func (s *Session) updateFeatures(features []string) {
@@ -1315,6 +1396,11 @@ func (s *Session) updateFeatures(features []string) {
if err == nil && linelen != 0 {
s.linelen = linelen
}
+ case "MONITOR":
+ monitor, err := strconv.Atoi(value)
+ if err == nil && monitor > 0 {
+ s.monitor = true
+ }
case "PREFIX":
if value == "" {
s.prefixModes = ""
diff --git a/irc/tokens.go b/irc/tokens.go
index ab7b418..aaffc08 100644
--- a/irc/tokens.go
+++ b/irc/tokens.go
@@ -460,9 +460,10 @@ func ParseCaps(caps string) (diff []Cap) {
// Member is a token in RPL_NAMREPLY's last parameter.
type Member struct {
- PowerLevel string
- Name *Prefix
- Away bool
+ PowerLevel string
+ Name *Prefix
+ Away bool
+ Disconnected bool
}
type members []Member
diff --git a/ui/ui.go b/ui/ui.go
index 7e02812..750644c 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -403,15 +403,27 @@ func drawVerticalMemberList(screen tcell.Screen, x0, y0, width, height int, memb
width--
clearArea(screen, x0, y0, width, height)
+ padding := 1
+ for _, m := range members {
+ if m.Disconnected {
+ padding = runeWidth(0x274C)
+ break
+ }
+ }
+
for i, m := range members[*offset:] {
x := x0
y := y0 + i
- if m.PowerLevel != "" {
+ if m.Disconnected {
+ disconnectedSt := tcell.StyleDefault.Foreground(tcell.ColorRed)
+ printString(screen, &x, y, Styled("\u274C", disconnectedSt))
+ } else if m.PowerLevel != "" {
+ x += padding - 1
powerLevelText := m.PowerLevel[:1]
powerLevelSt := tcell.StyleDefault.Foreground(tcell.ColorGreen)
printString(screen, &x, y, Styled(powerLevelText, powerLevelSt))
} else {
- x++
+ x += padding
}
var name StyledString