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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "fmt"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/oragono/oragono/irc/history"
  10. "github.com/oragono/oragono/irc/utils"
  11. )
  12. const (
  13. // #829, also see "Case 2" in the "three cases" below:
  14. zncPlaybackCommandExpiration = time.Second * 30
  15. )
  16. type zncCommandHandler func(client *Client, command string, params []string, rb *ResponseBuffer)
  17. var zncHandlers = map[string]zncCommandHandler{
  18. "*playback": zncPlaybackHandler,
  19. }
  20. func zncPrivmsgHandler(client *Client, command string, privmsg string, rb *ResponseBuffer) {
  21. zncModuleHandler(client, command, strings.Fields(privmsg), rb)
  22. }
  23. func zncModuleHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
  24. command = strings.ToLower(command)
  25. if subHandler, ok := zncHandlers[command]; ok {
  26. subHandler(client, command, params, rb)
  27. } else {
  28. nick := rb.target.Nick()
  29. rb.Add(nil, client.server.name, "NOTICE", nick, fmt.Sprintf(client.t("Oragono does not emulate the ZNC module %s"), command))
  30. rb.Add(nil, "*status!znc@znc.in", "NOTICE", nick, fmt.Sprintf(client.t("No such module [%s]"), command))
  31. }
  32. }
  33. // "number of seconds (floating point for millisecond precision) elapsed since January 1, 1970"
  34. func zncWireTimeToTime(str string) (result time.Time) {
  35. var secondsPortion, fracPortion string
  36. dot := strings.IndexByte(str, '.')
  37. if dot == -1 {
  38. secondsPortion = str
  39. } else {
  40. secondsPortion = str[:dot]
  41. fracPortion = str[dot:]
  42. }
  43. seconds, _ := strconv.ParseInt(secondsPortion, 10, 64)
  44. fraction, _ := strconv.ParseFloat(fracPortion, 64)
  45. return time.Unix(seconds, int64(fraction*1000000000)).UTC()
  46. }
  47. func timeToZncWireTime(t time.Time) (result string) {
  48. secs := t.Unix()
  49. nano := t.UnixNano() - (secs * 1000000000)
  50. return fmt.Sprintf("%d.%d", secs, nano)
  51. }
  52. type zncPlaybackTimes struct {
  53. start time.Time
  54. end time.Time
  55. targets utils.StringSet // nil for "*" (everything), otherwise the channel names
  56. setAt time.Time
  57. }
  58. func (z *zncPlaybackTimes) ValidFor(target string) bool {
  59. if z == nil {
  60. return false
  61. }
  62. if time.Now().Sub(z.setAt) > zncPlaybackCommandExpiration {
  63. return false
  64. }
  65. if z.targets == nil {
  66. return true
  67. }
  68. return z.targets.Has(target)
  69. }
  70. // https://wiki.znc.in/Playback
  71. func zncPlaybackHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
  72. if len(params) == 0 {
  73. return
  74. }
  75. switch strings.ToLower(params[0]) {
  76. case "play":
  77. zncPlaybackPlayHandler(client, command, params, rb)
  78. case "list":
  79. zncPlaybackListHandler(client, command, params, rb)
  80. default:
  81. return
  82. }
  83. }
  84. // PRIVMSG *playback :play <target> [lower_bound] [upper_bound]
  85. // e.g., PRIVMSG *playback :play * 1558374442
  86. func zncPlaybackPlayHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
  87. if len(params) < 2 || len(params) > 4 {
  88. return
  89. }
  90. targetString := params[1]
  91. now := time.Now().UTC()
  92. var start, end time.Time
  93. switch len(params) {
  94. case 2:
  95. // #1205: this should have the same semantics as `LATEST *`
  96. case 3:
  97. // #831: this should have the same semantics as `LATEST timestamp=qux`,
  98. // or equivalently `BETWEEN timestamp=$now timestamp=qux`, as opposed to
  99. // `AFTER timestamp=qux` (this matters in the case where there are
  100. // more than znc-maxmessages available)
  101. start = now
  102. end = zncWireTimeToTime(params[2])
  103. case 4:
  104. start = zncWireTimeToTime(params[2])
  105. end = zncWireTimeToTime(params[3])
  106. }
  107. var targets utils.StringSet
  108. var nickTargets []string
  109. // three cases:
  110. // 1. the user's PMs get played back immediately upon receiving this
  111. // 2. if this is a new connection (from the server's POV), save the information
  112. // and use it to process subsequent joins. (This is the Textual behavior:
  113. // first send the playback PRIVMSG, then send the JOIN lines.)
  114. // 3. if this is a reattach (from the server's POV), immediately play back
  115. // history for channels that the client is already joined to. In this scenario,
  116. // there are three total attempts to play the history:
  117. // 3.1. During the initial reattach (no-op because the *playback privmsg
  118. // hasn't been received yet, but they negotiated the znc.in/playback
  119. // cap so we know we're going to receive it later)
  120. // 3.2 Upon receiving the *playback privmsg, i.e., now: we should play
  121. // the relevant history lines
  122. // 3.3 When the client sends a subsequent redundant JOIN line for those
  123. // channels; redundant JOIN is a complete no-op so we won't replay twice
  124. playPrivmsgs := false
  125. if params[1] == "*" {
  126. playPrivmsgs = true // XXX nil `targets` means "every channel"
  127. } else {
  128. targets = make(utils.StringSet)
  129. for _, targetName := range strings.Split(targetString, ",") {
  130. if targetName == "*self" {
  131. playPrivmsgs = true
  132. } else if strings.HasPrefix(targetName, "#") {
  133. if cfTarget, err := CasefoldChannel(targetName); err == nil {
  134. targets.Add(cfTarget)
  135. }
  136. } else {
  137. if cfNick, err := CasefoldName(targetName); err == nil {
  138. nickTargets = append(nickTargets, cfNick)
  139. }
  140. }
  141. }
  142. }
  143. if playPrivmsgs {
  144. zncPlayPrivmsgs(client, rb, "*", start, end)
  145. }
  146. rb.session.zncPlaybackTimes = &zncPlaybackTimes{
  147. start: start,
  148. end: end,
  149. targets: targets,
  150. setAt: time.Now().UTC(),
  151. }
  152. for _, channel := range client.Channels() {
  153. if targets == nil || targets.Has(channel.NameCasefolded()) {
  154. channel.autoReplayHistory(client, rb, "")
  155. rb.Flush(true)
  156. }
  157. }
  158. for _, cfNick := range nickTargets {
  159. zncPlayPrivmsgs(client, rb, cfNick, start, end)
  160. rb.Flush(true)
  161. }
  162. }
  163. func zncPlayPrivmsgs(client *Client, rb *ResponseBuffer, target string, after, before time.Time) {
  164. _, sequence, _ := client.server.GetHistorySequence(nil, client, target)
  165. if sequence == nil {
  166. return
  167. }
  168. zncMax := client.server.Config().History.ZNCMax
  169. items, _, err := sequence.Between(history.Selector{Time: after}, history.Selector{Time: before}, zncMax)
  170. if err == nil && len(items) != 0 {
  171. client.replayPrivmsgHistory(rb, items, "", true)
  172. }
  173. }
  174. // PRIVMSG *playback :list
  175. func zncPlaybackListHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
  176. nick := client.Nick()
  177. for _, channel := range client.Channels() {
  178. _, sequence, err := client.server.GetHistorySequence(channel, client, "")
  179. if err != nil {
  180. client.server.logger.Error("internal", "couldn't get history sequence for ZNC list", err.Error())
  181. continue
  182. }
  183. items, _, err := sequence.Between(history.Selector{}, history.Selector{}, 1) // i.e., LATEST * 1
  184. if err != nil {
  185. client.server.logger.Error("internal", "couldn't query history for ZNC list", err.Error())
  186. } else if len(items) != 0 {
  187. stamp := timeToZncWireTime(items[0].Message.Time)
  188. rb.Add(nil, "*playback!znc@znc.in", "PRIVMSG", nick, fmt.Sprintf("%s 0 %s", channel.Name(), stamp))
  189. }
  190. }
  191. }