Additional IRC commands and bot tasks for EarwigBot https://en.wikipedia.org/wiki/User:EarwigBot
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

165 Zeilen
5.8 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2016 Ben Kurtovic <ben.kurtovic@gmail.com>
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a copy
  6. # of this software and associated documentation files (the "Software"), to deal
  7. # in the Software without restriction, including without limitation the rights
  8. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. # copies of the Software, and to permit persons to whom the Software is
  10. # furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. # SOFTWARE.
  22. from collections import deque
  23. from datetime import datetime
  24. from threading import Thread
  25. from earwigbot.commands import Command
  26. from earwigbot.irc import RC
  27. class RCMonitor(Command):
  28. """Monitors the recent changes feed for certain edits and reports them to a
  29. dedicated channel."""
  30. name = "rc_monitor"
  31. hooks = ["msg", "rc"]
  32. def setup(self):
  33. try:
  34. self._channel = self.config.commands[self.name]["channel"]
  35. except KeyError:
  36. self._channel = None
  37. log = ('Cannot use without a report channel set as '
  38. 'config.commands["{0}"]["channel"]')
  39. self.logger.warn(log.format(self.name))
  40. self._stats = {
  41. "start": datetime.utcnow(),
  42. "edits": 0,
  43. "hits": 0,
  44. "max_backlog": 0
  45. }
  46. self._levels = {}
  47. self._issues = {}
  48. self._descriptions = {}
  49. self._queue = deque()
  50. self._thread = Thread(target=self._callback, name="rc_monitor")
  51. self._thread.daemon = True
  52. self._thread.running = True
  53. self._prepare_reports()
  54. self._thread.start()
  55. def check(self, data):
  56. if not self._channel:
  57. return
  58. return isinstance(data, RC) or (
  59. data.is_command and data.command == self.name)
  60. def process(self, data):
  61. if isinstance(data, RC):
  62. newlen = len(self._queue) + 1
  63. self._queue.append(data)
  64. if newlen > self._stats["max_backlog"]:
  65. self._stats["max_backlog"] = newlen
  66. return
  67. if not self.config.irc["permissions"].is_admin(data):
  68. self.reply(data, "You must be a bot admin to use this command.")
  69. return
  70. since = self._stats["start"].strftime("%H:%M:%S, %d %B %Y")
  71. seconds = (datetime.utcnow() - self._stats["start"]).total_seconds()
  72. rate = self._stats["edits"] / seconds
  73. msg = ("\x02{edits:,}\x0F edits checked since {since} "
  74. "(\x02{rate:.2f}\x0F edits/sec); \x02{hits:,}\x0F hits; "
  75. "\x02{backlog:,}\x0F-edit backlog (\x02{max_backlog:,}\x0F "
  76. "max).")
  77. self.reply(data, msg.format(
  78. since=since, rate=rate, backlog=len(self._queue), **self._stats))
  79. def unload(self):
  80. self._thread.running = False
  81. self._queue.append(None)
  82. def _prepare_reports(self):
  83. """Set up internal tables for storing report information."""
  84. routine = 1
  85. alert = 2
  86. urgent = 3
  87. self._levels = {
  88. routine: "routine",
  89. alert: "alert",
  90. urgent: "URGENT"
  91. }
  92. self._issues = {
  93. "random": routine,
  94. "random2": urgent,
  95. # ...
  96. "g10": alert
  97. }
  98. self._descriptions = {
  99. "random": "common random test",
  100. "random2": "rare random test",
  101. # ...
  102. "g10": "CSD G10 nomination"
  103. }
  104. def _evaluate(self, event):
  105. """Return heuristic information about the given RC event."""
  106. issues = []
  107. # TODO
  108. from random import random
  109. rand = random()
  110. if rand < 0.05:
  111. issues.append("random")
  112. if rand < 0.01:
  113. issues.append("random2")
  114. # END TODO
  115. issues.sort(key=lambda issue: self._issues[issue], reverse=True)
  116. return issues
  117. def _format(self, rc, report):
  118. """Format a RC event for the report channel."""
  119. level = self._levels[max(self._issues[issue] for issue in report)]
  120. descr = ", ".join(self._descriptions[issue] for issue in report)
  121. notify = " ".join("!rcm-" + issue for issue in report)
  122. cmnt = rc.comment if len(rc.comment) <= 50 else rc.comment[:47] + "..."
  123. msg = ("[\x02{level}\x0F] ({descr}) [\x02{notify}\x0F]\x0306 * "
  124. "\x0314[[\x0307{title}\x0314]]\x0306 * \x0303{user}\x0306 * "
  125. "\x0302{url}\x0306 * \x0310{comment}")
  126. return msg.format(
  127. level=level, descr=descr, notify=notify, title=rc.page,
  128. user=rc.user, url=rc.url, comment=cmnt)
  129. def _handle_event(self, event):
  130. """Process a recent change event."""
  131. if not event.is_edit:
  132. return
  133. report = self._evaluate(event)
  134. self._stats["edits"] += 1
  135. if report:
  136. self.say(self._channel, self._format(event, report))
  137. self._stats["hits"] += 1
  138. def _callback(self):
  139. """Internal callback for the RC monitor thread."""
  140. while self._thread.running:
  141. event = self._queue.popleft()
  142. if not self._thread.running:
  143. break
  144. self._handle_event(event)