Add subscribe command for automatic notifications of topics
Add a new 'subscribe' command which allows people to subscribe for automatic notifications via direct message of topics which match the subscription regex they provide. With no regex argument it shows the user's current subscription (if any). Also add a new 'unsubscribe' command for clearing the regex. Example use cases: 1. I know that nova is planning to discuss $TOPIC some time tomorrow but they don't know exactly when, and I want to spend most of the day in another room whilst ensuring I don't miss that particular discussion on $TOPIC => "/msg ptgbot subscribe $TOPIC" will give me notifications when the PTL types "#nova next $TOPIC" and "#nova now $TOPIC". 2. I'm interested in *all* discussion on Python 3. I don't know which projects are planning to discuss it, let alone when, but that doesn't matter, because I can type "/msg ptgbot subscribe python ?3" and get notified of all Python 3 discussions. As with the presence tracking commands, these commands can be used in public channels by preceding them with a '#' character. Change-Id: I3f51acc318ecf31d435768640cef6c46d8ca136c
This commit is contained in:
parent
9cb30c28a9
commit
e8f88b2ee1
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")
|
||||
|
||||
def check_in(self, reply_to, nick, words):
|
||||
if len(words) == 0:
|
||||
@ -190,6 +195,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 "
|
||||
@ -213,6 +252,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))):
|
||||
@ -239,8 +285,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':
|
||||
@ -318,6 +366,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
Block a user