Browse Source

Adding some new commands.

pull/10/merge
Ben Kurtovic 8 years ago
parent
commit
4db0f9b9e8
3 changed files with 237 additions and 0 deletions
  1. +7
    -0
      README.md
  2. +72
    -0
      commands/lta_monitor.py
  3. +158
    -0
      commands/rc_monitor.py

+ 7
- 0
README.md View File

@@ -29,6 +29,8 @@ IRC Commands
[GitPython](http://packages.python.org/GitPython), which can be installed
with `pip install GitPython`.

- **lta_monitor**: monitors for LTAs. No further information is available.

- **praise**: adds a simple way for the bot respond to ad-hoc commands based on
entries in `praise`'s config (in the `"praises"` dictionary). Its original
intention was to implement silly "easter eggs" praising certain users; for
@@ -37,6 +39,11 @@ IRC Commands
`"praises"` with the key `"earwig"` and the value
`"Earwig is the bestest Python programmer ever!"`.

- **rc_monitor**: monitors the recent changes feed for certain edits and
reports them to a dedicated channel. The channel is stored under the
`"channel"` key in the command's dictionary. Reportable edits match various
heuristics, like urgent speedy taggings.

- **stars**: gets the number of stargazers for (i.e., people starring) a given
[GitHub](https://github.com/) repository.



+ 72
- 0
commands/lta_monitor.py View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import re

from earwigbot.commands import Command
from earwigbot.exceptions import APIError

class LTAMonitor(Command):
"""Monitors for LTAs. No further information is available."""
name = "lta_monitor"
hooks = ["join", "part"]

def setup(self):
try:
config = self.config.commands[self.name]
self._monitor_chan = config["monitorChannel"]
self._report_chan = config["reportChannel"]
except KeyError:
self._monitor_chan = self._report_chan = None
self.logger.warn("Cannot use without being properly configured")
self._recent = []
self._recent_max = 10

def check(self, data):
return (self._monitor_chan and self._report_chan and
data.chan == self._monitor_chan)

def process(self, data):
if not data.host.startswith("gateway/web/"):
return
match = re.search(r"/ip\.(.*?)$", data.host)
if not match:
return
ip = match.group(1)
if ip in self._recent:
return
self._recent.append(ip)
if len(self._recent) > self._recent_max:
self._recent.pop(0)

site = self.bot.wiki.get_site()
try:
result = site.api_query(action="query", list="blocks", bkip=ip, bklimit=1)
except APIError:
return
blocks = result["query"]["blocks"]
if not blocks:
return

msg = ("\x02[Alert]\x0F Joined user \x02{nick}\x0F is IP-blocked "
"on-wiki ([[User:{user}]] by {by}) because: {reason}")
self.say(self._report_chan, msg.format(nick=data.nick, **blocks[0]))

+ 158
- 0
commands/rc_monitor.py View File

@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 Ben Kurtovic <ben.kurtovic@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from collections import deque
from datetime import datetime
from threading import Thread

from earwigbot.commands import Command
from earwigbot.irc import RC

class RCMonitor(Command):
"""Monitors the recent changes feed for certain edits and reports them to a
dedicated channel."""
name = "rc_monitor"
hooks = ["msg", "rc"]

def setup(self):
try:
self._channel = self.config.commands[self.name]["channel"]
except KeyError:
self._channel = None
log = ('Cannot use without a report channel set as '
'config.commands["{0}"]["channel"]')
self.logger.warn(log.format(self.name))

self._stats = {
"start": datetime.utcnow(),
"edits": 0,
"hits": 0,
"max_backlog": 0
}
self._levels = {}
self._issues = {}
self._descriptions = {}
self._queue = deque()

self._thread = Thread(target=self._callback, name="rc_monitor")
self._thread.daemon = True
self._thread.running = True
self._prepare_reports()
self._thread.start()

def check(self, data):
if not self._channel:
return
return isinstance(data, RC) or (
data.is_command and data.command == self.name)

def process(self, data):
if isinstance(data, RC):
newlen = len(self._queue) + 1
self._queue.append(data)
if newlen > self._stats["max_backlog"]:
self._stats["max_backlog"] = newlen
return

if not self.config.irc["permissions"].is_admin(data):
self.reply(data, "You must be a bot admin to use this command.")
return

since = self._stats["start"].strftime("%H:%M:%S, %d %B %Y")
seconds = (datetime.utcnow() - self._stats["start"]).total_seconds()
rate = self._stats["edits"] / seconds
msg = ("\x02{edits:,}\x0F edits checked since {since} "
"(\x02{rate:.2f}\x0F edits/sec); \x02{hits:,}\x0F hits; "
"\x02{backlog:,}\x0F-edit backlog (\x02{max_backlog:,}\x0F "
"max).")
self.reply(data, msg.format(
since=since, rate=rate, backlog=len(self._queue), **self._stats))

def unload(self):
self._thread.running = False
self._queue.append(None)

def _prepare_reports(self):
"""Set up internal tables for storing report information."""
routine = 1
urgent = 3

self._levels = {
routine: "routine",
# ...
}
self._issues = {
"random": routine,
"random2": urgent,
# ...
}
self._descriptions = {
# ...
}

def _evaluate(self, event):
"""Return heuristic information about the given RC event."""
issues = []

# TODO
from random import random
rand = random()
if rand < 0.05:
issues.append("random")
if rand < 0.01:
issues.append("random2")
# END TODO

issues.sort(key=lambda issue: self._issues[issue], reverse=True)
return issues

def _format(self, rc, report):
"""Format a RC event for the report channel."""
level = self._levels[max(self._issues[issue] for issue in report)]
descr = ", ".join(self._descriptions[issue] for issue in report)
notify = " ".join("!rcm-" + issue for issue in report)
cmnt = rc.comment if len(rc.comment) <= 50 else rc.comment[:47] + "..."

msg = ("[\x02{level}\x0F] ({descr}) [\x02{notify}\x0F]\x0306 * "
"\x0314[[\x0307{title}\x0314]]\x0306 * \x0303{user}\x0306 * "
"\x0302{url}\x0306 * \x0310{comment}")
return msg.format(
level=level, descr=descr, notify=notify, title=rc.page,
user=rc.user, url=rc.url, comment=cmnt)

def _handle_event(self, event):
"""Process a recent change event."""
if not event.is_edit:
return
report = self._evaluate(event)
self._stats["edits"] += 1
if report:
self.say(self._channel, self._format(event, report))
self._stats["hits"] += 1

def _callback(self):
"""Internal callback for the RC monitor thread."""
while self._thread.running:
event = self._queue.popleft()
if not self._thread.running:
break
self._handle_event(event)

Loading…
Cancel
Save