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.

znc.go 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  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. type zncCommandHandler func(client *Client, command string, params []string, rb *ResponseBuffer)
  12. var zncHandlers = map[string]zncCommandHandler{
  13. "*playback": zncPlaybackHandler,
  14. }
  15. func zncPrivmsgHandler(client *Client, command string, privmsg string, rb *ResponseBuffer) {
  16. zncModuleHandler(client, command, strings.Fields(privmsg), rb)
  17. }
  18. func zncModuleHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
  19. command = strings.ToLower(command)
  20. if subHandler, ok := zncHandlers[command]; ok {
  21. subHandler(client, command, params, rb)
  22. } else {
  23. nick := rb.target.Nick()
  24. rb.Add(nil, client.server.name, "NOTICE", nick, fmt.Sprintf(client.t("Oragono does not emulate the ZNC module %s"), command))
  25. rb.Add(nil, "*status!znc@znc.in", "NOTICE", nick, fmt.Sprintf(client.t("No such module [%s]"), command))
  26. }
  27. }
  28. // "number of seconds (floating point for millisecond precision) elapsed since January 1, 1970"
  29. func zncWireTimeToTime(str string) (result time.Time) {
  30. var secondsPortion, fracPortion string
  31. dot := strings.IndexByte(str, '.')
  32. if dot == -1 {
  33. secondsPortion = str
  34. } else {
  35. secondsPortion = str[:dot]
  36. fracPortion = str[dot:]
  37. }
  38. seconds, _ := strconv.ParseInt(secondsPortion, 10, 64)
  39. fraction, _ := strconv.ParseFloat(fracPortion, 64)
  40. return time.Unix(seconds, int64(fraction*1000000000))
  41. }
  42. type zncPlaybackTimes struct {
  43. after time.Time
  44. before time.Time
  45. targets StringSet // nil for "*" (everything), otherwise the channel names
  46. }
  47. // https://wiki.znc.in/Playback
  48. // PRIVMSG *playback :play <target> [lower_bound] [upper_bound]
  49. // e.g., PRIVMSG *playback :play * 1558374442
  50. func zncPlaybackHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
  51. if len(params) < 2 {
  52. return
  53. } else if strings.ToLower(params[0]) != "play" {
  54. return
  55. }
  56. targetString := params[1]
  57. var after, before time.Time
  58. if 2 < len(params) {
  59. after = zncWireTimeToTime(params[2])
  60. }
  61. if 3 < len(params) {
  62. before = zncWireTimeToTime(params[3])
  63. }
  64. var targets StringSet
  65. // three cases:
  66. // 1. the user's PMs get played back immediately upon receiving this
  67. // 2. if this is a new connection (from the server's POV), save the information
  68. // and use it to process subsequent joins. (This is the Textual behavior:
  69. // first send the playback PRIVMSG, then send the JOIN lines.)
  70. // 3. if this is a reattach (from the server's POV), immediately play back
  71. // history for channels that the client is already joined to. In this scenario,
  72. // there are three total attempts to play the history:
  73. // 3.1. During the initial reattach (no-op because the *playback privmsg
  74. // hasn't been received yet, but they negotiated the znc.in/playback
  75. // cap so we know we're going to receive it later)
  76. // 3.2 Upon receiving the *playback privmsg, i.e., now: we should play
  77. // the relevant history lines
  78. // 3.3 When the client sends a subsequent redundant JOIN line for those
  79. // channels; redundant JOIN is a complete no-op so we won't replay twice
  80. if params[1] == "*" {
  81. zncPlayPrivmsgs(client, rb, after, before)
  82. } else {
  83. targets = make(StringSet)
  84. // TODO actually handle nickname targets
  85. for _, targetName := range strings.Split(targetString, ",") {
  86. if cfTarget, err := CasefoldChannel(targetName); err == nil {
  87. targets.Add(cfTarget)
  88. }
  89. }
  90. }
  91. rb.session.zncPlaybackTimes = &zncPlaybackTimes{
  92. after: after,
  93. before: before,
  94. targets: targets,
  95. }
  96. for _, channel := range client.Channels() {
  97. if targets == nil || targets.Has(channel.NameCasefolded()) {
  98. channel.autoReplayHistory(client, rb, "")
  99. rb.Flush(true)
  100. }
  101. }
  102. }
  103. func zncPlayPrivmsgs(client *Client, rb *ResponseBuffer, after, before time.Time) {
  104. _, sequence, _ := client.server.GetHistorySequence(nil, client, "*")
  105. if sequence == nil {
  106. return
  107. }
  108. zncMax := client.server.Config().History.ZNCMax
  109. items, _, err := sequence.Between(history.Selector{Time: after}, history.Selector{Time: before}, zncMax)
  110. if err == nil {
  111. client.replayPrivmsgHistory(rb, items, "", true)
  112. }
  113. }