summaryrefslogtreecommitdiff
path: root/irc/session.go
diff options
context:
space:
mode:
Diffstat (limited to 'irc/session.go')
-rw-r--r--irc/session.go108
1 files changed, 97 insertions, 11 deletions
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 = ""