選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

znc.go 5.2KB

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