Merge "Add subscribe command for automatic notifications of topics"
This commit is contained in:
commit
4fbaf41725
18
README.rst
18
README.rst
|
@ -37,11 +37,25 @@ Anyone can privately message the bot with the following commands:
|
|||
* ``seen NICK`` - asks the bot where the user with the given IRC nick
|
||||
was last seen (if anywhere). The nick is case-insensitive.
|
||||
|
||||
* ``subscribe REGEXP`` - subscribes for a direct message notification
|
||||
from the bot whenever a topic with a substring matching ``REGEXP``
|
||||
is set via the ``now`` or ``next`` commands (see below). The exact
|
||||
string the (case-insensitive) regular expression will be matched
|
||||
against is of the form ``#track now topic`` (i.e. the same as the
|
||||
full commands issued by track moderators). So for example
|
||||
``subscribe #nova.*test|python *3`` would match any testing topics
|
||||
in the nova track, and any Python 3 topics in any track.
|
||||
|
||||
* ``subscribe`` - shows your current subscription regular expression
|
||||
(if any)
|
||||
|
||||
* ``unsubscribe`` - cancels your current subscription (if any)
|
||||
|
||||
The above commands also work in the channel when prefixed with ``#``,
|
||||
for example ``#in the pub``. You can use the ``#`` prefix with
|
||||
private messages to the bot too, in case you don't want to memorise
|
||||
different syntax for these presence-tracking commands depending on
|
||||
whether you are messaging the bot privately or in a channel.
|
||||
different syntax for these commands depending on whether you are
|
||||
messaging the bot privately or in a channel.
|
||||
|
||||
|
||||
Track moderators commands
|
||||
|
|
|
@ -113,6 +113,15 @@
|
|||
<a href="https://opendev.org/openstack/ptgbot/src/branch/master/README.rst">(more help)</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h3 class="panel-title">Worried about missing discussions on your favourite topic?</h3></div>
|
||||
<div class="bot-help">
|
||||
Message the bot with <code>subscribe REGEXP</code> to get a
|
||||
notification message when any topic matching that REGEXP is being
|
||||
discussed or up next.
|
||||
<a href="https://opendev.org/openstack/ptgbot/src/branch/master/README.rst">(more help)</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h3 class="panel-title">Looking for someone, or want to be easy to find?</h3></div>
|
||||
<div class="bot-help">
|
||||
|
|
|
@ -21,6 +21,7 @@ from ib3.connection import SSL
|
|||
import irc.bot
|
||||
import json
|
||||
import logging.config
|
||||
import re
|
||||
import os
|
||||
import time
|
||||
import textwrap
|
||||
|
@ -107,9 +108,13 @@ class PTGBot(SASL, SSL, irc.bot.SingleServerIRCBot):
|
|||
self.check_out(nick, nick, words)
|
||||
elif cmd == 'seen':
|
||||
self.last_seen(nick, nick, words)
|
||||
elif cmd == 'subscribe':
|
||||
self.subscribe(nick, nick, msg.lstrip('#' + cmd).strip())
|
||||
elif cmd == 'unsubscribe':
|
||||
self.unsubscribe(nick, nick)
|
||||
else:
|
||||
self.send_priv_or_pub(nick, None,
|
||||
"Recognised commands: in, out, seen")
|
||||
self.send_priv_or_pub(
|
||||
nick, None, "Recognised commands: in, out, seen, subscribe")
|
||||
|
||||
# Checks location against known tracks. If prefixed with # then
|
||||
# insists it must match a known track. If not #-prefixed but
|
||||
|
@ -205,6 +210,40 @@ class PTGBot(SASL, SSL, irc.bot.SingleServerIRCBot):
|
|||
(last_check_in['nick'], last_check_in['location'],
|
||||
last_check_in['out']))
|
||||
|
||||
def subscribe(self, reply_to, nick, new_re):
|
||||
existing_re = self.data.get_subscription(nick)
|
||||
if new_re == "":
|
||||
if existing_re is None:
|
||||
self.send_priv_or_pub(
|
||||
reply_to, nick,
|
||||
"You don't have a subscription regex set yet"
|
||||
)
|
||||
else:
|
||||
self.send_priv_or_pub(
|
||||
reply_to, nick,
|
||||
"Your current subscription regex is: " + existing_re)
|
||||
else:
|
||||
self.data.set_subscription(nick, new_re)
|
||||
self.send_priv_or_pub(
|
||||
reply_to, nick,
|
||||
"Subscription set to " + new_re +
|
||||
(" (was %s)" % existing_re if existing_re else "")
|
||||
)
|
||||
|
||||
def unsubscribe(self, reply_to, nick):
|
||||
existing_re = self.data.get_subscription(nick)
|
||||
if existing_re is None:
|
||||
self.send_priv_or_pub(
|
||||
reply_to, nick,
|
||||
"You don't have a subscription regex set yet"
|
||||
)
|
||||
else:
|
||||
self.data.set_subscription(nick, None)
|
||||
self.send_priv_or_pub(
|
||||
reply_to, nick,
|
||||
"Cancelled subscription %s" % existing_re
|
||||
)
|
||||
|
||||
def on_pubmsg(self, c, e):
|
||||
if not self.identify_msg_cap:
|
||||
self.log.debug("Ignoring message because identify-msg "
|
||||
|
@ -228,6 +267,13 @@ class PTGBot(SASL, SSL, irc.bot.SingleServerIRCBot):
|
|||
self.last_seen(chan, nick, words[1:])
|
||||
return
|
||||
|
||||
elif cmd == '#subscribe':
|
||||
self.subscribe(chan, nick, msg.lstrip('#' + cmd).strip())
|
||||
return
|
||||
elif cmd == '#unsubscribe':
|
||||
self.unsubscribe(chan, nick)
|
||||
return
|
||||
|
||||
if (self.data.is_voice_required() and not
|
||||
(self.channels[chan].is_voiced(nick) or
|
||||
self.channels[chan].is_oper(nick))):
|
||||
|
@ -254,8 +300,10 @@ class PTGBot(SASL, SSL, irc.bot.SingleServerIRCBot):
|
|||
params = str.join(' ', words[2:])
|
||||
if adverb == 'now':
|
||||
self.data.add_now(track, params)
|
||||
self.notify(track, adverb, params)
|
||||
elif adverb == 'next':
|
||||
self.data.add_next(track, params)
|
||||
self.notify(track, adverb, params)
|
||||
elif adverb == 'clean':
|
||||
self.data.clean_tracks([track])
|
||||
elif adverb == 'color':
|
||||
|
@ -333,6 +381,24 @@ class PTGBot(SASL, SSL, irc.bot.SingleServerIRCBot):
|
|||
self.send(chan, "%s: unknown command '%s'" % (nick, command))
|
||||
return
|
||||
|
||||
def notify(self, track, adverb, params):
|
||||
location = self.data.get_location(track)
|
||||
track = '#' + track
|
||||
trackloc = track
|
||||
if location is not None:
|
||||
trackloc = "%s (%s)" % (track, location)
|
||||
|
||||
for nick, regexp in self.data.get_subscriptions().items():
|
||||
event_text = " ".join([track, adverb, params])
|
||||
if re.search(regexp, event_text, re.IGNORECASE):
|
||||
message = "%s in %s: %s" % (adverb, trackloc, params)
|
||||
# Note: there is no guarantee that nick will be online
|
||||
# at this point. However if not, the bot will receive
|
||||
# a 401 :No such nick/channel message which it will
|
||||
# ignore due to the lack of a nosuchnick handler.
|
||||
# Fortunately this is the behaviour we want.
|
||||
self.send(nick, message)
|
||||
|
||||
def send_priv_or_pub(self, target, nick, msg):
|
||||
if target.startswith('#') and nick is not None:
|
||||
self.send(target, "%s: %s" % (nick, msg))
|
||||
|
|
22
ptgbot/db.py
22
ptgbot/db.py
|
@ -36,7 +36,8 @@ class PTGDataBase():
|
|||
'links': OrderedDict(),
|
||||
# Keys for last_check_in are lower-cased nicks;
|
||||
# values are in the same format as BASE_CHECK_IN
|
||||
'last_check_in': OrderedDict()}
|
||||
'last_check_in': OrderedDict(),
|
||||
'subscriptions': OrderedDict()}
|
||||
|
||||
BASE_CHECK_IN = {
|
||||
'nick': None, # original case for use in output
|
||||
|
@ -117,6 +118,9 @@ class PTGDataBase():
|
|||
self.data['location'][track] = location
|
||||
self.save()
|
||||
|
||||
def get_location(self, track):
|
||||
return self.data['location'].get(track)
|
||||
|
||||
def add_next(self, track, session):
|
||||
if track not in self.data['next']:
|
||||
self.data['next'][track] = []
|
||||
|
@ -234,6 +238,22 @@ class PTGDataBase():
|
|||
self.save()
|
||||
return self.data['last_check_in'][nick]['location']
|
||||
|
||||
def get_subscription(self, nick):
|
||||
if 'subscriptions' not in self.data:
|
||||
return None
|
||||
return self.data['subscriptions'].get(nick)
|
||||
|
||||
def get_subscriptions(self):
|
||||
if 'subscriptions' not in self.data:
|
||||
return {}
|
||||
return self.data['subscriptions']
|
||||
|
||||
def set_subscription(self, nick, regexp):
|
||||
if 'subscriptions' not in self.data:
|
||||
self.data['subscriptions'] = OrderedDict()
|
||||
self.data['subscriptions'][nick] = regexp
|
||||
self.save()
|
||||
|
||||
def save(self):
|
||||
timestamp = datetime.datetime.now()
|
||||
self.data['timestamp'] = self.serialise_timestamp(timestamp)
|
||||
|
|
Loading…
Reference in New Issue