You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

monitor.go 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "errors"
  6. "strconv"
  7. "strings"
  8. "sync"
  9. "github.com/goshuirc/irc-go/ircmsg"
  10. "github.com/oragono/oragono/irc/utils"
  11. )
  12. // MonitorManager keeps track of who's monitoring which nicks.
  13. type MonitorManager struct {
  14. sync.RWMutex
  15. // client -> nicks it's watching
  16. watching map[*Client]map[string]bool
  17. // nick -> clients watching it
  18. watchedby map[string]map[*Client]bool
  19. // (all nicks must be normalized externally by casefolding)
  20. }
  21. // NewMonitorManager returns a new MonitorManager.
  22. func NewMonitorManager() *MonitorManager {
  23. mm := MonitorManager{
  24. watching: make(map[*Client]map[string]bool),
  25. watchedby: make(map[string]map[*Client]bool),
  26. }
  27. return &mm
  28. }
  29. // ErrMonitorLimitExceeded is used when the monitor list exceeds our limit.
  30. var ErrMonitorLimitExceeded = errors.New("Monitor limit exceeded")
  31. // AlertAbout alerts everyone monitoring `client`'s nick that `client` is now {on,off}line.
  32. func (manager *MonitorManager) AlertAbout(client *Client, online bool) {
  33. cfnick := client.getNickCasefolded()
  34. nick := client.getNick()
  35. var watchers []*Client
  36. // safely copy the list of clients watching our nick
  37. manager.RLock()
  38. for client := range manager.watchedby[cfnick] {
  39. watchers = append(watchers, client)
  40. }
  41. manager.RUnlock()
  42. command := RPL_MONOFFLINE
  43. if online {
  44. command = RPL_MONONLINE
  45. }
  46. // asynchronously send all the notifications
  47. go func() {
  48. for _, mClient := range watchers {
  49. mClient.Send(nil, client.server.name, command, mClient.getNick(), nick)
  50. }
  51. }()
  52. }
  53. // Add registers `client` to receive notifications about `nick`.
  54. func (manager *MonitorManager) Add(client *Client, nick string, limit int) error {
  55. manager.Lock()
  56. defer manager.Unlock()
  57. if manager.watching[client] == nil {
  58. manager.watching[client] = make(map[string]bool)
  59. }
  60. if manager.watchedby[nick] == nil {
  61. manager.watchedby[nick] = make(map[*Client]bool)
  62. }
  63. if len(manager.watching[client]) >= limit {
  64. return ErrMonitorLimitExceeded
  65. }
  66. manager.watching[client][nick] = true
  67. manager.watchedby[nick][client] = true
  68. return nil
  69. }
  70. // Remove unregisters `client` from receiving notifications about `nick`.
  71. func (manager *MonitorManager) Remove(client *Client, nick string) error {
  72. manager.Lock()
  73. defer manager.Unlock()
  74. // deleting from nil maps is fine
  75. delete(manager.watching[client], nick)
  76. delete(manager.watchedby[nick], client)
  77. return nil
  78. }
  79. // RemoveAll unregisters `client` from receiving notifications about *all* nicks.
  80. func (manager *MonitorManager) RemoveAll(client *Client) {
  81. manager.Lock()
  82. defer manager.Unlock()
  83. for nick := range manager.watching[client] {
  84. delete(manager.watchedby[nick], client)
  85. }
  86. delete(manager.watching, client)
  87. }
  88. // List lists all nicks that `client` is registered to receive notifications about.
  89. func (manager *MonitorManager) List(client *Client) (nicks []string) {
  90. manager.RLock()
  91. defer manager.RUnlock()
  92. for nick := range manager.watching[client] {
  93. nicks = append(nicks, nick)
  94. }
  95. return nicks
  96. }
  97. var (
  98. metadataSubcommands = map[string]func(server *Server, client *Client, msg ircmsg.IrcMessage) bool{
  99. "-": monitorRemoveHandler,
  100. "+": monitorAddHandler,
  101. "c": monitorClearHandler,
  102. "l": monitorListHandler,
  103. "s": monitorStatusHandler,
  104. }
  105. )
  106. func monitorHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  107. handler, exists := metadataSubcommands[strings.ToLower(msg.Params[0])]
  108. if !exists {
  109. client.Send(nil, server.name, ERR_UNKNOWNERROR, client.getNick(), "MONITOR", msg.Params[0], "Unknown subcommand")
  110. return false
  111. }
  112. return handler(server, client, msg)
  113. }
  114. func monitorRemoveHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  115. if len(msg.Params) < 2 {
  116. client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.getNick(), msg.Command, "Not enough parameters")
  117. return false
  118. }
  119. targets := strings.Split(msg.Params[1], ",")
  120. for _, target := range targets {
  121. cfnick, err := CasefoldName(target)
  122. if err != nil {
  123. continue
  124. }
  125. server.monitorManager.Remove(client, cfnick)
  126. }
  127. return false
  128. }
  129. func monitorAddHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  130. if len(msg.Params) < 2 {
  131. client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.getNick(), msg.Command, "Not enough parameters")
  132. return false
  133. }
  134. var online []string
  135. var offline []string
  136. limit := server.getLimits().MonitorEntries
  137. targets := strings.Split(msg.Params[1], ",")
  138. for _, target := range targets {
  139. // check name length
  140. if len(target) < 1 || len(targets) > server.limits.NickLen {
  141. continue
  142. }
  143. // add target
  144. casefoldedTarget, err := CasefoldName(target)
  145. if err != nil {
  146. continue
  147. }
  148. err = server.monitorManager.Add(client, casefoldedTarget, limit)
  149. if err == ErrMonitorLimitExceeded {
  150. client.Send(nil, server.name, ERR_MONLISTFULL, client.getNick(), strconv.Itoa(server.limits.MonitorEntries), strings.Join(targets, ","))
  151. break
  152. } else if err != nil {
  153. continue
  154. }
  155. // add to online / offline lists
  156. if targetClient := server.clients.Get(casefoldedTarget); targetClient == nil {
  157. offline = append(offline, target)
  158. } else {
  159. online = append(online, targetClient.getNick())
  160. }
  161. }
  162. if len(online) > 0 {
  163. client.Send(nil, server.name, RPL_MONONLINE, client.getNick(), strings.Join(online, ","))
  164. }
  165. if len(offline) > 0 {
  166. client.Send(nil, server.name, RPL_MONOFFLINE, client.getNick(), strings.Join(offline, ","))
  167. }
  168. return false
  169. }
  170. func monitorClearHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  171. server.monitorManager.RemoveAll(client)
  172. return false
  173. }
  174. func monitorListHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  175. monitorList := server.monitorManager.List(client)
  176. var nickList []string
  177. for _, cfnick := range monitorList {
  178. replynick := cfnick
  179. // report the uncasefolded nick if it's available, i.e., the client is online
  180. if mclient := server.clients.Get(cfnick); mclient != nil {
  181. replynick = mclient.getNick()
  182. }
  183. nickList = append(nickList, replynick)
  184. }
  185. for _, line := range utils.ArgsToStrings(maxLastArgLength, nickList, ",") {
  186. client.Send(nil, server.name, RPL_MONLIST, client.getNick(), line)
  187. }
  188. client.Send(nil, server.name, RPL_ENDOFMONLIST, "End of MONITOR list")
  189. return false
  190. }
  191. func monitorStatusHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  192. var online []string
  193. var offline []string
  194. monitorList := server.monitorManager.List(client)
  195. for _, name := range monitorList {
  196. target := server.clients.Get(name)
  197. if target == nil {
  198. offline = append(offline, name)
  199. } else {
  200. online = append(online, target.getNick())
  201. }
  202. }
  203. if len(online) > 0 {
  204. for _, line := range utils.ArgsToStrings(maxLastArgLength, online, ",") {
  205. client.Send(nil, server.name, RPL_MONONLINE, client.getNick(), line)
  206. }
  207. }
  208. if len(offline) > 0 {
  209. for _, line := range utils.ArgsToStrings(maxLastArgLength, offline, ",") {
  210. client.Send(nil, server.name, RPL_MONOFFLINE, client.getNick(), line)
  211. }
  212. }
  213. return false
  214. }