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.7KB

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