proto.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. // This file is subject to a 1-clause BSD license.
  2. // Its contents can be found in the enclosed LICENSE file.
  3. // Package proto defines convenience functions for IRC protocol requests.
  4. package proto
  5. import (
  6. "fmt"
  7. "io"
  8. "strings"
  9. "time"
  10. "notabug.org/mouz/bot/irc"
  11. )
  12. // lastMsg holds the time when the last PRIVMSG was sent.
  13. var lastMsg time.Time
  14. // ref: https://en.wikipedia.org/wiki/List_of_Internet_Relay_Chat_commands#User_commands
  15. // Raw sends the given, raw message data.
  16. //
  17. // The message being sent is reformatted to match the IRC specification.
  18. // Meaning that it can not exceed 512 bytes and must end with `\r\n`.
  19. // Any data beyond 512 bytes is simply discarded.
  20. func Raw(w io.Writer, msg string, argv ...interface{}) error {
  21. data := []byte(fmt.Sprintf(msg, argv...) + "\r\n")
  22. sz := len(data)
  23. if sz <= 2 {
  24. return nil
  25. }
  26. if sz >= 512 {
  27. data = data[:512]
  28. data[510] = '\r'
  29. data[511] = '\n'
  30. }
  31. _, err := w.Write(data)
  32. return err
  33. }
  34. // Admin instructs the server to return information about the administrator of
  35. // the server specified by <server>. If omitted, the current server is
  36. // assumed.
  37. func Admin(w io.Writer, server ...string) error {
  38. if len(server) > 0 {
  39. return Raw(w, "ADMIN %s", server[0])
  40. }
  41. return Raw(w, "ADMIN")
  42. }
  43. // Away marks us as being away, provided there is an away message.
  44. // If the away message is empty, the away status is removed.
  45. func Away(w io.Writer, message ...string) error {
  46. if len(message) > 0 {
  47. return Raw(w, "AWAY %s", message[0])
  48. }
  49. return Raw(w, "AWAY")
  50. }
  51. // CNotice sends a channel NOTICE message to <nickname> on <channel> that
  52. // bypasses flood protection limits. The target nickname must be in the same
  53. // channel as the client issuing the command, and the client must be a
  54. // channel operator.
  55. //
  56. // Normally an IRC server will limit the number of different targets a client
  57. // can send messages to within a certain time frame to prevent spammers or
  58. // bots from mass-messaging users on the network, however this command can be
  59. // used by channel operators to bypass that limit in their channel. For example,
  60. // it is often used by help operators that may be communicating with a large
  61. // number of users in a help channel at one time.
  62. //
  63. // This command is not formally defined in an RFC, but is in use by some IRC
  64. // networks. Support is indicated in a RPL_ISUPPORT reply (numeric 005) with
  65. // the CNOTICE keyword.
  66. func CNotice(w io.Writer, nickname, channel, message string) error {
  67. return Raw(w, "CNOTICE %s %s :%s", nickname, channel, message)
  68. }
  69. // CPrivMsg sends a private message to <nickname> on <channel> that bypasses
  70. // flood protection limits. The target nickname must be in the same channel as
  71. // the client issuing the command, and the client must be a channel operator.
  72. //
  73. // Normally an IRC server will limit the number of different targets a client
  74. // can send messages to within a certain time frame to prevent spammers or bots
  75. // from mass-messaging users on the network, however this command can be used
  76. // by channel operators to bypass that limit in their channel. For example, it
  77. // is often used by help operators that may be communicating with a large
  78. // number of users in a help channel at one time.
  79. //
  80. // This command is not formally defined in an RFC, but is in use by some IRC
  81. // networks. Support is indicated in a RPL_ISUPPORT reply (numeric 005) with
  82. // the CPRIVMSG keyword.
  83. func CPrivMsg(w io.Writer, nickname, channel, message string) error {
  84. return Raw(w, "CPRIVMSG %s %s :%s", nickname, channel, message)
  85. }
  86. // Connect instructs the server <remote server> (or the current server, if
  87. // <remote server> is omitted) to connect to <target server> on port <port>.
  88. //
  89. // This command should only be available to IRC Operators.
  90. func Connect(w io.Writer, targetServer string, port int, remoteServer ...string) error {
  91. if len(remoteServer) > 0 {
  92. return Raw(w, "CONNECT %s %d %s", targetServer, port, remoteServer[0])
  93. }
  94. return Raw(w, "CONNECT %s %d", targetServer, port)
  95. }
  96. // Die instructs the server to shut down and may only be issued by
  97. // IRC server operators.
  98. func Die(w io.Writer) error { return Raw(w, "DIE") }
  99. // Info requests information about the target server, or the current server if
  100. // <server> is omitted. Information returned includes the server's version,
  101. // when it was compiled, the patch level, when it was started, and any other
  102. // information which may be considered to be relevant.
  103. func Info(w io.Writer, server ...string) error {
  104. if len(server) > 0 {
  105. return Raw(w, "INFO %s", server[0])
  106. }
  107. return Raw(w, "INFO")
  108. }
  109. // Invite invites <nickname> to <channel>. <channel> does not have to exist,
  110. // but if it does, only members of the channel are allowed to invite other
  111. // clients. If the channel mode i is set, only channel operators may invite
  112. // other clients.
  113. func Invite(w io.Writer, nickname, channel string) error {
  114. return Raw(w, "INVITE %s %s", nickname, channel)
  115. }
  116. // IsOn requests the server to see if the nicknames in the given list are
  117. // currently on the network. The server returns only the nicknames which are on
  118. // the network in a space-separated list. If none of the clients are on the
  119. // network, it returns an empty list.
  120. func IsOn(w io.Writer, nicknames ...string) error {
  121. return Raw(w, "ISON %s", strings.Join(nicknames, " "))
  122. }
  123. // Join joins the given channels.
  124. func Join(w io.Writer, channels ...irc.Channel) (err error) {
  125. for _, ch := range channels {
  126. if len(ch.Key) > 0 {
  127. err = Raw(w, "JOIN %s %s", ch.Name, ch.Key)
  128. } else {
  129. err = Raw(w, "JOIN %s", ch.Name)
  130. }
  131. if err != nil {
  132. return
  133. }
  134. }
  135. return
  136. }
  137. // Kick forcibly removes <client> from <channel>. This command may only be
  138. // issued by channel operators. The optional reason tells the client why
  139. // they were kicked.
  140. func Kick(w io.Writer, channel, client string, reason ...string) error {
  141. if len(reason) > 0 {
  142. return Raw(w, "KICK %s %s :%s", channel, client, reason[0])
  143. }
  144. return Raw(w, "KICK %s %s", channel, client)
  145. }
  146. // Knock sends a NOTICE to an invitation-only <channel> with an optional
  147. // <message>, requesting an invite.
  148. //
  149. // This command is not formally defined by an RFC, but is supported by most
  150. // major IRC daemons. Support is indicated in a RPL_ISUPPORT reply (numeric 005)
  151. // with the KNOCK keyword.
  152. func Knock(w io.Writer, channel string, message ...string) error {
  153. if len(message) > 0 {
  154. return Raw(w, "KNOCK %s :%s", channel, message[0])
  155. }
  156. return Raw(w, "KNOCK %s", channel)
  157. }
  158. // List requests all channels on the server. If the comma-separated list
  159. // <channels> is given, it will return the channel topics.
  160. func List(w io.Writer, channels ...string) error {
  161. if len(channels) > 0 {
  162. return Raw(w, "LIST %s", strings.Join(channels, ","))
  163. }
  164. return Raw(w, "LIST")
  165. }
  166. // Mode changes the mode for the given user or channel.
  167. // Optionally with the given argument.
  168. func Mode(w io.Writer, target, mode string, argv ...string) error {
  169. if len(argv) > 0 {
  170. return Raw(w, "MODE %s %s %s", target, mode, argv[0])
  171. }
  172. return Raw(w, "MODE %s %s", target, mode)
  173. }
  174. // Names queries users in the given list of <channels>, If <channels> is
  175. // omitted, all users are shown, grouped by channel name with all users who are
  176. // not on a channel being shown as part of channel "*". If <server> is specified,
  177. // the command is sent to <server> for evaluation.
  178. //
  179. // The response contains all nicknames in the channel, prefixed with the highest
  180. // channel status prefix of that user, for example like this (with @ being the
  181. // highest status prefix).
  182. //
  183. // :irc.server.net 353 Phyre = #SomeChannel :@WiZ
  184. //
  185. // If a client wants to receive all the channel status prefixes of a user and
  186. // not only their current highest one, the IRCv3 multi-prefix extension can
  187. // be enabled (@ is the channel operator prefix, and + the lower voice status
  188. // prefix):
  189. //
  190. // :irc.server.net 353 Phyre = #SomeChannel :@+WiZ
  191. //
  192. func Names(w io.Writer, channels ...string) error {
  193. if len(channels) > 0 {
  194. return Raw(w, "NAMES %s", strings.Join(channels, ","))
  195. }
  196. return Raw(w, "NAMES")
  197. }
  198. // Nick allows a client to change their IRC nickname.
  199. // The optional password is used to authenticate the user with nickserv.
  200. func Nick(w io.Writer, nickname string, password ...string) error {
  201. err := Raw(w, "NICK %s", nickname)
  202. if err != nil {
  203. return err
  204. }
  205. if len(password) > 0 {
  206. return PrivMsg(w, "nickserv", "IDENTIFY %s", password[0])
  207. }
  208. return nil
  209. }
  210. // Notice works similarly to PRIVMSG, except automatic replies must never be
  211. // sent in reply to NOTICE messages.
  212. func Notice(w io.Writer, target, f string, argv ...interface{}) error {
  213. return Raw(w, "NOTICE %s :%s", target, fmt.Sprintf(f, argv...))
  214. }
  215. // Oper authenticates a user as an IRC operator on a server/network.
  216. func Oper(w io.Writer, nickname, password string) error {
  217. return Raw(w, "OPER %s %s", nickname, password)
  218. }
  219. // Part leaves the given channels.
  220. func Part(w io.Writer, channels ...irc.Channel) (err error) {
  221. for _, ch := range channels {
  222. err = Raw(w, "PART %s :", ch.Name)
  223. if err != nil {
  224. return
  225. }
  226. }
  227. return
  228. }
  229. // Pass sets a connection password. This command must be sent before the
  230. // NICK/USER registration combination. It is ignored if the given password
  231. // is empty.
  232. func Pass(w io.Writer, password string) error {
  233. if len(password) == 0 {
  234. return nil
  235. }
  236. return Raw(w, "PASS %s", password)
  237. }
  238. // Pong sends the given payload as a response to a PING message.
  239. func Pong(w io.Writer, payload string) error {
  240. return Raw(w, "PONG %s", payload)
  241. }
  242. // PrivMsg sends the specified formatted message to the given target.
  243. // The target may be a channel or nickname. If the previous message
  244. // was sent less then a second ago, sending is delayed. This is to
  245. // prevent disconnects because of excess flood.
  246. func PrivMsg(w io.Writer, target, f string, argv ...interface{}) error {
  247. if time.Since(lastMsg) < time.Second {
  248. time.Sleep(time.Second)
  249. }
  250. lastMsg = time.Now()
  251. return Raw(w, "PRIVMSG %s :%s", target, fmt.Sprintf(f, argv...))
  252. }
  253. // Quit disconnects from the server., optionally with the given message.
  254. func Quit(w io.Writer, message ...string) error {
  255. if len(message) > 0 {
  256. return Raw(w, "QUIT %s", message[0])
  257. }
  258. return Raw(w, "QUIT")
  259. }
  260. // Recover attempts to re-authenticate our username, so we can
  261. // regain the use of it. This is mostly useful after we received
  262. // a NickInUse error and is only relevant if there is a nickserv.
  263. // NOTE 10.07.2020 by mouz: This does not work. Neither does REGAIN
  264. // when connecting.
  265. func Recover(w io.Writer, nickname, password string) error {
  266. return Raw(w, "NS RECOVER %s %s", nickname, password)
  267. }
  268. // Rehash causes the server to re-read and re-process its configuration file(s).
  269. // This command can only be sent by IRC Operators
  270. func Rehash(w io.Writer, username string) error { return Raw(w, "REHASH") }
  271. // Restart restarts a server. It may only be sent by IRC Operators.
  272. func Restart(w io.Writer, username string) error { return Raw(w, "RESTART") }
  273. // SQuit causes <server> to quit the network.
  274. func SQuit(w io.Writer, server, message string) error {
  275. return Raw(w, "SQUIT %s %s", server, message)
  276. }
  277. // SetName allows a client to change the "real name" specified when registering
  278. // a connection.
  279. //
  280. // This command is not formally defined by an RFC, but is in use by some IRC
  281. // daemons. Support is indicated in a RPL_ISUPPORT reply (numeric 005) with the
  282. // SETNAME keyword
  283. func SetName(w io.Writer, name string) error {
  284. return Raw(w, "SETNAME %s", name)
  285. }
  286. // Silence adds or removes a host mask to a server-side ignore list that
  287. // prevents matching users from sending the client messages. More than one mask
  288. // may be specified. Each item prefixed with a "+" or "-" to designate whether
  289. // it is being added or removed. Sending the command with no parameters returns
  290. // the entries in the client's ignore list.
  291. //
  292. // This command is not formally defined in an RFC, but is supported by most
  293. // major IRC daemons. Support is indicated in a RPL_ISUPPORT reply (numeric 005)
  294. // with the SILENCE keyword and the maximum number of entries a client may have
  295. // in its ignore list. For example:
  296. //
  297. // :irc.server.net 005 WiZ WALLCHOPS WATCH=128 SILENCE=15 MODES=12 CHANTYPES=#
  298. //
  299. func Silence(w io.Writer, masks ...string) error {
  300. return Raw(w, "SILENCE %s", strings.Join(masks, " "))
  301. }
  302. // Summon gives users who are on the same host as <server> a message asking
  303. // them to join IRC. If server is omitted, the current server is assumed.
  304. // Channel is optional and will request them to join that specific channel.
  305. func Summon(w io.Writer, user, server, channel string) error {
  306. if len(server) > 0 {
  307. if len(channel) > 0 {
  308. return Raw(w, "SUMMON %s %s %s", user, server, channel)
  309. }
  310. return Raw(w, "SUMMON %s %s", user, server)
  311. }
  312. return Raw(w, "SUMMON %s", user)
  313. }
  314. // Time requests the local time on the current or given server.
  315. func Time(w io.Writer, server ...string) error {
  316. if len(server) > 0 {
  317. return Raw(w, "TIME %s", server[0])
  318. }
  319. return Raw(w, "TIME")
  320. }
  321. // Topic allows the client to query or set the channel topic on <channel>.
  322. // If channel mode +t is set, only a channel operator may set the topic.
  323. func Topic(w io.Writer, channel string, topic ...string) error {
  324. if len(topic) > 0 {
  325. return Raw(w, "TOPIC %s %s", channel, topic[0])
  326. }
  327. return Raw(w, "TOPIC %s", channel)
  328. }
  329. // User is used at the beginning of a connection to specify the username,
  330. // hostname, real name and initial user modes of the connecting client.
  331. // <realname> may contain spaces.
  332. //
  333. // E.g.: USER joe 8 * :joe smith
  334. //
  335. func User(w io.Writer, username, mode, realname string) error {
  336. return Raw(w, "USER %s %s * :%s", username, mode, realname)
  337. }
  338. // UserHost returns host information for the specified nicknames.
  339. func UserHost(w io.Writer, names ...string) error {
  340. return Raw(w, "USERHOST %s", strings.Join(names, " "))
  341. }
  342. // UserIP requests the direct IP address of the user with the specified nickname.
  343. //
  344. // This command is often used to obtain the IP of an abusive user to more
  345. // effectively perform a ban. It is unclear what, if any, privileges are
  346. // required to execute this command on a server.
  347. //
  348. // This command is not formally defined by an RFC, but is in use by some IRC
  349. // daemons. Support is indicated in a RPL_ISUPPORT reply (numeric 005) with
  350. // the USERIP keyword.
  351. func UserIP(w io.Writer, nickname string) error {
  352. return Raw(w, "USERIP %s", nickname)
  353. }
  354. // Users requests a list of users and information about those users in a
  355. // format similar to the UNIX commands who, rusers and finger. The command
  356. // is optionally targeted at a specific server.
  357. func Users(w io.Writer, server ...string) error {
  358. if len(server) > 0 {
  359. return Raw(w, "USERS %s", server[0])
  360. }
  361. return Raw(w, "USERS")
  362. }
  363. // Version requests version information for the current or given server.
  364. func Version(w io.Writer, server ...string) error {
  365. if len(server) > 0 {
  366. return Raw(w, "VERSION %s", server[0])
  367. }
  368. return Raw(w, "VERSION")
  369. }
  370. // Wallops sends a formatted message to all operators connected to the server
  371. // or all users with user mode 'w' set.
  372. func Wallops(w io.Writer, f string, argv ...interface{}) error {
  373. return Raw(w, "WALLOPS %s", fmt.Sprintf(f, argv...))
  374. }
  375. // Watch adds or removes a user to a client's server-side friends list.
  376. // More than one nickname may be specified. Each item prefixed with a "+" or "-"
  377. // to designate whether it is being added or removed. Sending the command
  378. // with no parameters returns the entries in the client's friends list.
  379. //
  380. // This command is not formally defined in an RFC, but is supported by most
  381. // major IRC daemons. Support is indicated in a RPL_ISUPPORT reply (numeric 005)
  382. // with the WATCH keyword and the maximum number of entries a client may have in
  383. // its friends list. For example:
  384. //
  385. // :irc.server.net 005 WiZ WALLCHOPS WATCH=128 SILENCE=15 MODES=12 CHANTYPES=#
  386. //
  387. func Watch(w io.Writer, masks ...string) error {
  388. return Raw(w, "WATCH %s", strings.Join(masks, " "))
  389. }
  390. // Who requests a list of users who match <name>. If opOnly is truen, the
  391. // server will only return information about IRC Operators.
  392. func Who(w io.Writer, name string, opOnly bool) error {
  393. if opOnly {
  394. return Raw(w, "WHO %s o", name)
  395. }
  396. return Raw(w, "WHO %s", name)
  397. }
  398. // Whois requests information about the given nickname. If <server>
  399. // is given, the command is forwarded to it for processing.
  400. func Whois(w io.Writer, nickname string, server ...string) error {
  401. if len(server) > 0 {
  402. return Raw(w, "WHOIS %s %s", server[0], nickname)
  403. }
  404. return Raw(w, "WHOIS %s", nickname)
  405. }
  406. // Whowas requests information about a nickname that is no longer in use
  407. // (due to client disconnection, or nickname changes). If <server> is given,
  408. // the command is forwarded to it for processing.
  409. func Whowas(w io.Writer, target string, server ...string) error {
  410. if len(server) > 0 {
  411. return Raw(w, "WHOWAS %s %s", server[0], target)
  412. }
  413. return Raw(w, "WHOWAS %s", target)
  414. }