2
# Copyright 2012, Ensoft Ltd.
3
# Created by Jonathan Millican, and mangled beyond recognition by SimonC
5
import logging, re, time
6
from twisted.internet import defer, error, protocol, reactor
7
from endroid.plugins.command import CommandPlugin, command
11
class FilterProtocol(protocol.ProcessProtocol):
13
Send some data to a process via its stdin, close it, and reap the output
14
from its stdout. Twisted arguably ought to provide a utility function for
15
this - there's nothing specific to GPG or high-fiving here.
17
def __init__(self, in_data, deferred, args):
18
self.in_data = in_data
19
self.deferred = deferred
23
def connectionMade(self):
24
self.transport.write(self.in_data.encode('utf-8'))
25
self.transport.closeStdin()
27
def outReceived(self, out_data):
28
self.out_data += out_data.decode('utf-8')
30
def outConnectionLost(self):
31
d, self.deferred = self.deferred, None
32
d.callback(self.out_data)
34
def processEnded(self, reason):
35
if isinstance(reason.value, error.ProcessTerminated):
36
logging.error("Problem running filter ({}): {}".
37
format(self.args, reason.value))
38
d, self.deferred = self.deferred, None
39
d.errback(reason.value)
41
class Hi5(CommandPlugin):
43
The Hi5 plugin lets you send anonymous 'High Five!' messages to other
44
users known to Endroid. The idea is that it makes it easy to send a
45
compliment to somebody. The most basic usage is:
47
hi5 user@example.com Nice presentation dude!
49
which, if user@example.com is known to EnDroid and currently logged in,
50
sends both a unicast chat to that user with the anonymous compliment,
51
and also anonymously announces to one or more configured chatrooms that
52
user@example.com got the High Five 'Nice presentation dude!'.
54
Slightly more complicated examples: it's possible to send a compliment
55
to multiple users at once, by using comma-separation, and also omit
56
the domain part of the JID if the user is unambiguous given all the
57
users known to EnDroid. So for example:
59
hi5 bilbo@baggins.org, frodo, sam Good work with the Nazgul guys :-)
61
There is some basic anonymous logging performed by default, that includes
62
only the time/date and recipient JID. However, if you configure a GPG
63
public key, then an asymmetrically encrypted log that also includes the
64
sender and message is done. That provides a last-resort mechanism should
65
someone use the mechanism for poisonous purposes, but requires the private
66
key and passphrase. The 'spelunk_hi5' script can be used for this.
70
help = ("Send anonymous 'hi5s' to folks! Your message is sent from EnDroid"
71
" direct to the recipient, as well as being broadcast in any "
72
"configured public chat rooms.")
74
def endroid_init(self):
75
if 'gpg' in self.vars:
76
self.gpg = ('/usr/bin/gpg', '--encrypt', '--armor',
77
'--keyring', self.vars['gpg'][0],
78
'--recipient', self.vars['gpg'][1])
81
if not self.database.table_exists(HI5_TABLE):
82
self.database.create_table(HI5_TABLE, ['jids', 'date', 'encrypted'])
84
@command(helphint="{user}[,{user}] {message}")
85
def hi5(self, msg, arg):
88
jids, text = self._parse(arg)
91
msg.reply("Sorry, couldn't spot a message to send in there. Use "
93
"hi5 frodo@shire.org, sam, bilbo@rivendell Nice job!")
96
# Sanity checks, and also expand out 'user' to 'user@host'
97
# if we can do so unambiguously
101
msg.reply("Recipients can't contain '/'s".format(jid))
103
fulljid = self._get_fulljid(jid)
105
msg.reply("{0} is not a currently online valid receipient. "
106
"Sorry.".format(jid))
108
elif fulljid == msg.sender:
109
msg.reply("You really don't have to resort to complimenting "
110
"yourself. I already think you're great")
112
fulljids.append(fulljid)
115
self._do_hi5(jids, fulljids, text, msg)
117
def _do_hi5(self, jids, fulljids, text, msg):
119
Actually send the hi5
121
# jidlist is a nice human-readable representation of the lucky
122
# receipients, like 'Tom, Dick & Harry'
124
jidlist = ', '.join(jids[:-1]) + ' & ' + jids[-1]
128
msg.reply('A High Five "' + text + '" is being sent to ' + jidlist)
129
self._log_hi5(','.join(fulljids), msg.sender, text)
132
self.messagehandler.send_chat(jid, "You've been sent an anonymous "
133
"High Five: " + text)
134
for group in self.vars.get('broadcast', []):
135
groupmsg = '{} {} been sent an anonymous High Five: {}'.format(
136
jidlist, 'have' if len(jids) > 1 else 'has', text)
137
self.messagehandler.send_muc(group, groupmsg)
142
Parse something like ' bilbo@baggins.org, frodo, sam great job! ' into
143
(['bilbo@baggsins.org', 'frodo', 'sam'], 'great job!')
148
m = re.match(r'([^ ,]+)( *,? *)(.*)', msg)
149
jid, sep, rest = m.groups(1)
156
def _get_fulljid(self, jid):
158
Expand a simple 'user' JID to 'user@host' if we can do so unambiguously
160
users = self.usermanagement.get_available_users()
165
if user.startswith(jid + '@'):
166
expansions.append(user)
167
if len(expansions) == 1:
170
def _log_hi5(self, jidlist, sender, text):
172
Log the hi5. This either means spawning a GPG process to do an
173
asymmetric encryption of the whole thing, or just writing a basic
174
summary straight away.
177
def db_insert(encrypted):
178
self.database.insert(HI5_TABLE, {'jids':jidlist,
179
'date':now, 'encrypted': encrypted})
180
def db_insert_err(err):
181
logging.error("Error with hi5 database entry: {}".format(err))
185
d.addCallbacks(db_insert, db_insert_err)
186
gpg_log = '{}: {} -> {}: {}'.format(now, sender, jidlist, text)
187
fp = FilterProtocol(gpg_log, d, self.gpg)
188
reactor.spawnProcess(fp, self.gpg[0], self.gpg, {})