Additional IRC commands and bot tasks for EarwigBot https://en.wikipedia.org/wiki/User:EarwigBot
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

164 строки
5.7 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 datetime import datetime
  23. from Queue import Queue
  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 = Queue()
  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 = self._queue.qsize() + 1
  63. self._queue.put(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{qsize:,}\x0F-edit backlog (\x02{max_backlog:,}\x0F max).")
  76. self.reply(data, msg.format(
  77. since=since, rate=rate, qsize=self._queue.qsize(), **self._stats))
  78. def unload(self):
  79. self._thread.running = False
  80. self._queue.put(None)
  81. def _prepare_reports(self):
  82. """Set up internal tables for storing report information."""
  83. routine = 1
  84. alert = 2
  85. urgent = 3
  86. self._levels = {
  87. routine: "routine",
  88. alert: "alert",
  89. urgent: "URGENT"
  90. }
  91. self._issues = {
  92. "random": routine,
  93. "random2": urgent,
  94. # ...
  95. "g10": alert
  96. }
  97. self._descriptions = {
  98. "random": "common random test",
  99. "random2": "rare random test",
  100. # ...
  101. "g10": "CSD G10 nomination"
  102. }
  103. def _evaluate(self, event):
  104. """Return heuristic information about the given RC event."""
  105. issues = []
  106. # TODO
  107. from random import random
  108. rand = random()
  109. if rand < 0.05:
  110. issues.append("random")
  111. if rand < 0.01:
  112. issues.append("random2")
  113. # END TODO
  114. issues.sort(key=lambda issue: self._issues[issue], reverse=True)
  115. return issues
  116. def _format(self, rc, report):
  117. """Format a RC event for the report channel."""
  118. level = self._levels[max(self._issues[issue] for issue in report)]
  119. descr = ", ".join(self._descriptions[issue] for issue in report)
  120. notify = " ".join("!rcm-" + issue for issue in report)
  121. cmnt = rc.comment if len(rc.comment) <= 50 else rc.comment[:47] + "..."
  122. msg = ("[\x02{level}\x0F] ({descr}) [\x02{notify}\x0F]\x0306 * "
  123. "\x0314[[\x0307{title}\x0314]]\x0306 * \x0303{user}\x0306 * "
  124. "\x0302{url}\x0306 * \x0310{comment}")
  125. return msg.format(
  126. level=level, descr=descr, notify=notify, title=rc.page,
  127. user=rc.user, url=rc.url, comment=cmnt)
  128. def _handle_event(self, event):
  129. """Process a recent change event."""
  130. if not event.is_edit:
  131. return
  132. report = self._evaluate(event)
  133. self._stats["edits"] += 1
  134. if report:
  135. self.say(self._channel, self._format(event, report))
  136. self._stats["hits"] += 1
  137. def _callback(self):
  138. """Internal callback for the RC monitor thread."""
  139. while self._thread.running:
  140. event = self._queue.get()
  141. if not self._thread.running:
  142. break
  143. self._handle_event(event)