|
|
@@ -0,0 +1,290 @@ |
|
|
|
# -*- 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. |
|
|
|
|
|
|
|
from earwigbot.commands import Command |
|
|
|
from earwigbot.irc import RC |
|
|
|
|
|
|
|
class Stalk(Command): |
|
|
|
"""Stalk a particular user (!stalk/!unstalk) or page (!watch/!unwatch) for |
|
|
|
edits. Applies to the current bot session only.""" |
|
|
|
name = "stalk" |
|
|
|
commands = ["stalk", "watch", "unstalk", "unwatch", "stalks", "watches", |
|
|
|
"allstalks", "allwatches", "unstalkall", "unwatchall"] |
|
|
|
hooks = ["msg", "rc"] |
|
|
|
MAX_STALKS_PER_USER = 10 |
|
|
|
|
|
|
|
def setup(self): |
|
|
|
self._users = {} |
|
|
|
self._pages = {} |
|
|
|
|
|
|
|
def check(self, data): |
|
|
|
if isinstance(data, RC): |
|
|
|
return True |
|
|
|
if data.is_command and data.command in self.commands: |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def process(self, data): |
|
|
|
if isinstance(data, RC): |
|
|
|
return self._process_rc(data) |
|
|
|
|
|
|
|
data.is_admin = self.config.irc["permissions"].is_admin(data) |
|
|
|
|
|
|
|
if data.command.startswith("all"): |
|
|
|
if data.is_admin: |
|
|
|
self.reply(data, self._all_stalks()) |
|
|
|
else: |
|
|
|
self.reply(data, "You must be a bot admin to view all stalked " |
|
|
|
"users or watched pages. View your own with " |
|
|
|
"\x0306!stalks\x0F.") |
|
|
|
return |
|
|
|
|
|
|
|
if data.command.endswith("all"): |
|
|
|
if not data.is_admin: |
|
|
|
self.reply(data, "You must be a bot admin to unstalk a user " |
|
|
|
"or unwatch a page for all users.") |
|
|
|
return |
|
|
|
if not data.args: |
|
|
|
self.reply(data, "You must give a user to unstalk or a page " |
|
|
|
"to unwatch. View all active with " |
|
|
|
"\x0306!allstalks\x0F.") |
|
|
|
return |
|
|
|
|
|
|
|
if not data.args or data.command in ["stalks", "watches"]: |
|
|
|
self.reply(data, self._current_stalks(data.nick)) |
|
|
|
return |
|
|
|
|
|
|
|
target = " ".join(data.args) |
|
|
|
if target.startswith("[[") and target.endswith("]]"): |
|
|
|
target = target[2:-2] |
|
|
|
if target.startswith("User:") and "stalk" in data.command: |
|
|
|
target = target[5:] |
|
|
|
|
|
|
|
if data.command in ["stalk", "watch"]: |
|
|
|
if data.is_private: |
|
|
|
stalkinfo = (data.nick, None) |
|
|
|
elif not data.is_admin: |
|
|
|
self.reply(data, "You must be a bot admin to stalk users or " |
|
|
|
"watch pages publicly. Retry this command in " |
|
|
|
"a private message.") |
|
|
|
return |
|
|
|
else: |
|
|
|
stalkinfo = (data.nick, data.chan) |
|
|
|
|
|
|
|
if data.command == "stalk": |
|
|
|
self._add_stalk("user", data, target, stalkinfo) |
|
|
|
elif data.command == "watch": |
|
|
|
self._add_stalk("page", data, target, stalkinfo) |
|
|
|
elif data.command == "unstalk": |
|
|
|
self._remove_stalk("user", data, target) |
|
|
|
elif data.command == "unwatch": |
|
|
|
self._remove_stalk("page", data, target) |
|
|
|
elif data.command == "unstalkall": |
|
|
|
self._remove_all_stalks("user", data, target) |
|
|
|
elif data.command == "unwatchall": |
|
|
|
self._remove_all_stalks("page", data, target) |
|
|
|
|
|
|
|
def _process_rc(self, rc): |
|
|
|
"""Process a watcher event.""" |
|
|
|
def _update_chans(items): |
|
|
|
for item in items: |
|
|
|
if item[1]: |
|
|
|
if item[1] in chans: |
|
|
|
chans[item[1]].add(item[0]) |
|
|
|
else: |
|
|
|
chans[item[1]] = {item[0]} |
|
|
|
else: |
|
|
|
chans[item[0]] = None |
|
|
|
|
|
|
|
chans = {} |
|
|
|
if rc.user in self._users: |
|
|
|
_update_chans(self._users[rc.user]) |
|
|
|
if rc.is_edit and rc.page in self._pages: |
|
|
|
_update_chans(self._pages[rc.page]) |
|
|
|
if not chans: |
|
|
|
return |
|
|
|
|
|
|
|
with self.bot.component_lock: |
|
|
|
frontend = self.bot.frontend |
|
|
|
if frontend and not frontend.is_stopped(): |
|
|
|
pretty = rc.prettify() |
|
|
|
for chan in chans: |
|
|
|
if chans[chan]: |
|
|
|
nicks = ", ".join(sorted(chans[chan])) |
|
|
|
msg = "\x02{0}\x0F: {1}".format(nicks, pretty) |
|
|
|
else: |
|
|
|
msg = pretty |
|
|
|
if len(msg) > 400: |
|
|
|
msg = msg[:397] + "..." |
|
|
|
frontend.say(chan, msg) |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def _get_stalks_by_nick(nick, table): |
|
|
|
"""Return a dictionary of stalklist entries by the given nick.""" |
|
|
|
entries = {} |
|
|
|
for target, stalks in table.iteritems(): |
|
|
|
for info in stalks: |
|
|
|
if info[0] == nick: |
|
|
|
if target in entries: |
|
|
|
entries[target].append(info[1]) |
|
|
|
else: |
|
|
|
entries[target] = [info[1]] |
|
|
|
return entries |
|
|
|
|
|
|
|
def _add_stalk(self, stalktype, data, target, stalkinfo): |
|
|
|
"""Add a stalk entry to the given table.""" |
|
|
|
if stalktype == "user": |
|
|
|
table = self._users |
|
|
|
verb = "stalk" |
|
|
|
else: |
|
|
|
table = self._pages |
|
|
|
verb = "watch" |
|
|
|
|
|
|
|
if not data.is_admin: |
|
|
|
nstalks = len(self._get_stalks_by_nick(data.nick, table)) |
|
|
|
if nstalks >= self.MAX_STALKS_PER_USER: |
|
|
|
msg = ("Already {0}ing {1} {2}s for you, which is the limit " |
|
|
|
"for non-bot admins.") |
|
|
|
self.reply(data, msg.format(verb, nstalks, stalktype)) |
|
|
|
return |
|
|
|
|
|
|
|
if target in table: |
|
|
|
if stalkinfo in table[target]: |
|
|
|
msg = "Already {0}ing that {1} in here for you." |
|
|
|
self.reply(data, msg.format(verb, stalktype)) |
|
|
|
return |
|
|
|
else: |
|
|
|
table[target].append(stalkinfo) |
|
|
|
else: |
|
|
|
table[target] = [stalkinfo] |
|
|
|
|
|
|
|
msg = "Now {0}ing {1} \x0302{2}\x0F. Remove with \x0306!un{0} {2}\x0F." |
|
|
|
self.reply(data, msg.format(verb, stalktype, target)) |
|
|
|
|
|
|
|
def _remove_stalk(self, stalktype, data, target): |
|
|
|
"""Remove a stalk entry from the given table.""" |
|
|
|
if stalktype == "user": |
|
|
|
table = self._users |
|
|
|
verb = "stalk" |
|
|
|
plural = "stalks" |
|
|
|
else: |
|
|
|
table = self._pages |
|
|
|
verb = "watch" |
|
|
|
plural = "watches" |
|
|
|
|
|
|
|
to_remove = [] |
|
|
|
if target in table: |
|
|
|
for info in table[target]: |
|
|
|
if info[0] == data.nick: |
|
|
|
to_remove.append(info) |
|
|
|
|
|
|
|
if to_remove: |
|
|
|
for info in to_remove: |
|
|
|
table[target].remove(info) |
|
|
|
if not table[target]: |
|
|
|
del table[target] |
|
|
|
msg = "No longer {0}ing {1} \x0302{2}\x0F for you." |
|
|
|
self.reply(data, msg.format(verb, stalktype, target)) |
|
|
|
return |
|
|
|
|
|
|
|
msg = ("I haven't been {0}ing that {1} for you in the first place. " |
|
|
|
"View your active {2} with \x0306!{2}\x0F.") |
|
|
|
if data.is_admin: |
|
|
|
msg += (" As a bot admin, you can clear all active {2} on that " |
|
|
|
"{1} with \x0306!un{0}all {2}\x0F.") |
|
|
|
self.reply(data, msg.format(verb, stalktype, plural)) |
|
|
|
|
|
|
|
def _remove_all_stalks(self, stalktype, data, target): |
|
|
|
"""Remove all entries for a particular target from the given table.""" |
|
|
|
if stalktype == "user": |
|
|
|
table = self._users |
|
|
|
verb = "stalk" |
|
|
|
plural = "stalks" |
|
|
|
else: |
|
|
|
table = self._pages |
|
|
|
verb = "watch" |
|
|
|
plural = "watches" |
|
|
|
|
|
|
|
try: |
|
|
|
del table[target] |
|
|
|
except KeyError: |
|
|
|
msg = ("I haven't been {0}ing that {1} for anyone in the first " |
|
|
|
"place. View all active {2} with \x0306!all{2}\x0F.") |
|
|
|
self.reply(data, msg.format(verb, stalktype, plural)) |
|
|
|
else: |
|
|
|
msg = "No longer {0}ing {1} \x0302{2}\x0F for anyone." |
|
|
|
self.reply(data, msg.format(verb, stalktype, target)) |
|
|
|
|
|
|
|
def _current_stalks(self, nick): |
|
|
|
"""Return the given user's current stalks.""" |
|
|
|
def _format_chans(chans): |
|
|
|
if None in chans: |
|
|
|
chans.remove(None) |
|
|
|
if not chans: |
|
|
|
return "privately" |
|
|
|
if len(chans) == 1: |
|
|
|
return "in {0} and privately".format(chans[0]) |
|
|
|
return "in " + ", ".join(chans) + ", and privately" |
|
|
|
return "in " + ", ".join(chans) |
|
|
|
|
|
|
|
def _format_stalks(stalks): |
|
|
|
return ", ".join( |
|
|
|
"\x0302{0}\x0F ({1})".format(target, _format_chans(chans)) |
|
|
|
for target, chans in stalks.iteritems()) |
|
|
|
|
|
|
|
users = self._get_stalks_by_nick(nick, self._users) |
|
|
|
pages = self._get_stalks_by_nick(nick, self._pages) |
|
|
|
if users: |
|
|
|
uinfo = " Users: {0}.".format(_format_stalks(users)) |
|
|
|
if pages: |
|
|
|
pinfo = " Pages: {0}.".format(_format_stalks(pages)) |
|
|
|
|
|
|
|
msg = "Currently stalking {0} user{1} and watching {2} page{3} for you.{4}{5}" |
|
|
|
return msg.format(len(users), "s" if len(users) != 1 else "", |
|
|
|
len(pages), "s" if len(pages) != 1 else "", |
|
|
|
uinfo if users else "", pinfo if pages else "") |
|
|
|
|
|
|
|
def _all_stalks(self): |
|
|
|
"""Return all existing stalks, for bot admins.""" |
|
|
|
def _format_info(info): |
|
|
|
if info[1]: |
|
|
|
return "for {0} in {1}".format(info[0], info[1]) |
|
|
|
return "for {0} privately".format(info[0]) |
|
|
|
|
|
|
|
def _format_data(data): |
|
|
|
return ", ".join(_format_info(info) for info in data) |
|
|
|
|
|
|
|
def _format_stalks(stalks): |
|
|
|
return ", ".join( |
|
|
|
"\x0302{0}\x0F ({1})".format(target, _format_data(data)) |
|
|
|
for target, data in stalks.iteritems()) |
|
|
|
|
|
|
|
users, pages = self._users, self._pages |
|
|
|
if users: |
|
|
|
uinfo = " Users: {0}.".format(_format_stalks(users)) |
|
|
|
if pages: |
|
|
|
pinfo = " Pages: {0}.".format(_format_stalks(pages)) |
|
|
|
|
|
|
|
msg = "Currently stalking {0} user{1} and watching {2} page{3}.{4}{5}" |
|
|
|
return msg.format(len(users), "s" if len(users) != 1 else "", |
|
|
|
len(pages), "s" if len(pages) != 1 else "", |
|
|
|
uinfo if users else "", pinfo if pages else "") |