Additional IRC commands and bot tasks for EarwigBot https://en.wikipedia.org/wiki/User:EarwigBot
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.

152 lines
5.4 KiB

  1. # Copyright (C) 2021 Ben Kurtovic <ben.kurtovic@gmail.com>
  2. #
  3. # Permission is hereby granted, free of charge, to any person obtaining a copy
  4. # of this software and associated documentation files (the "Software"), to deal
  5. # in the Software without restriction, including without limitation the rights
  6. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. # copies of the Software, and to permit persons to whom the Software is
  8. # furnished to do so, subject to the following conditions:
  9. #
  10. # The above copyright notice and this permission notice shall be included in
  11. # all copies or substantial portions of the Software.
  12. #
  13. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19. # SOFTWARE.
  20. import base64
  21. import pickle
  22. import re
  23. from earwigbot.commands import Command
  24. from earwigbot.config.permissions import User
  25. class PartWhen(Command):
  26. """Ask the bot to part the channel when a condition is met."""
  27. name = "partwhen"
  28. commands = ["partwhen", "unpartwhen"]
  29. hooks = ["join", "msg"]
  30. def setup(self):
  31. self._conds = self._load_conditions()
  32. def check(self, data):
  33. if data.is_command and data.command in self.commands:
  34. return True
  35. try:
  36. if data.line[1] == "JOIN":
  37. if data.chan in self._conds and "join" in self._conds[data.chan]:
  38. return True
  39. except IndexError:
  40. pass
  41. return False
  42. def process(self, data):
  43. if data.line[1] == "JOIN":
  44. self._handle_join(data)
  45. return
  46. if not self.config.irc["permissions"].is_admin(data):
  47. self.reply(data, "You must be a bot admin to use this command.")
  48. return
  49. channel = data.chan
  50. args = data.args
  51. if args and args[0].startswith("#"):
  52. # "!partwhen #channel <event> <args>..."
  53. channel = args[0]
  54. args = args[1:]
  55. if data.command == "unpartwhen":
  56. if self._conds.get(channel):
  57. del self._conds[channel]
  58. self._save_conditions()
  59. self.reply(
  60. data,
  61. "Cleared part conditions for {}.".format(
  62. "this channel" if channel == data.chan else channel
  63. ),
  64. )
  65. else:
  66. self.reply(data, "No part conditions set.")
  67. return
  68. if not args:
  69. conds = self._conds.get(channel, {})
  70. existing = "; ".join(
  71. "{} {}".format(cond, ", ".join(str(user) for user in users))
  72. for cond, users in conds.iteritems()
  73. )
  74. if existing:
  75. status = f"Current part conditions: {existing}."
  76. else:
  77. status = "No part conditions set for {}.".format(
  78. "this channel" if channel == data.chan else channel
  79. )
  80. self.reply(
  81. data,
  82. f"{status} Usage: !{data.command} [<channel>] <event> <args>...",
  83. )
  84. return
  85. event = args[0]
  86. args = args[1:]
  87. if event == "join":
  88. if not args:
  89. self.reply(
  90. data,
  91. "Join event requires an argument for the user joining, "
  92. "in nick!ident@host syntax.",
  93. )
  94. return
  95. cond = args[0]
  96. match = re.match(r"(.*?)!(.*?)@(.*?)$", cond)
  97. if not match:
  98. self.reply(
  99. data,
  100. "User join pattern is invalid; should use "
  101. "nick!ident@host syntax.",
  102. )
  103. return
  104. conds = self._conds.setdefault(channel, {}).setdefault("join", [])
  105. conds.append(User(match.group(1), match.group(2), match.group(3)))
  106. self._save_conditions()
  107. self.reply(
  108. data,
  109. "Okay, I will leave {} when {} joins.".format(
  110. "the channel" if channel == data.chan else channel, cond
  111. ),
  112. )
  113. else:
  114. self.reply(data, f"Unknown event: {event} (valid events: join).")
  115. def _handle_join(self, data):
  116. user = User(data.nick, data.ident, data.host)
  117. conds = self._conds.get(data.chan, {}).get("join", {})
  118. for cond in conds:
  119. if user in cond:
  120. self.logger.info(
  121. f"Parting {data.chan} because {str(user)} met join condition {str(cond)}"
  122. )
  123. self.part(data.chan, f"Requested to leave when {data.nick} joined")
  124. break
  125. def _load_conditions(self):
  126. permdb = self.config.irc["permissions"]
  127. try:
  128. raw = permdb.get_attr("command:partwhen", "data")
  129. except KeyError:
  130. return {}
  131. return pickle.loads(base64.b64decode(raw))
  132. def _save_conditions(self):
  133. permdb = self.config.irc["permissions"]
  134. raw = base64.b64encode(pickle.dumps(self._conds, pickle.HIGHEST_PROTOCOL))
  135. permdb.set_attr("command:partwhen", "data", raw)