2012-10-02 14:09:24 -04:00
|
|
|
#!/usr/bin/python
|
2012-11-02 10:40:13 -04:00
|
|
|
# Copyright (C) 2012 Humbug, Inc. All rights reserved.
|
2012-10-02 14:09:24 -04:00
|
|
|
import sys
|
|
|
|
import traceback
|
|
|
|
import simplejson
|
|
|
|
import re
|
|
|
|
import time
|
|
|
|
import subprocess
|
|
|
|
import optparse
|
|
|
|
import os
|
|
|
|
import datetime
|
|
|
|
import textwrap
|
2012-10-12 17:19:49 -04:00
|
|
|
import signal
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-11-02 16:41:04 -04:00
|
|
|
def to_humbug_username(zephyr_username):
|
2012-11-02 16:38:00 -04:00
|
|
|
if "@" in zephyr_username:
|
|
|
|
(user, realm) = zephyr_username.split("@")
|
|
|
|
else:
|
2012-11-05 13:10:41 -05:00
|
|
|
(user, realm) = (zephyr_username, "ATHENA.MIT.EDU")
|
2012-11-02 16:38:00 -04:00
|
|
|
if realm.upper() == "ATHENA.MIT.EDU":
|
|
|
|
return user.lower() + "@mit.edu"
|
2012-11-02 20:55:07 -04:00
|
|
|
return user.lower() + "|" + realm.upper() + "@mit.edu"
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-11-02 15:46:50 -04:00
|
|
|
def to_zephyr_username(humbug_username):
|
2012-11-02 16:38:00 -04:00
|
|
|
(user, realm) = humbug_username.split("@")
|
|
|
|
if "|" not in user:
|
|
|
|
return user.lower() + "@ATHENA.MIT.EDU"
|
2012-11-02 20:55:07 -04:00
|
|
|
match_user = re.match(r'([a-zA-Z0-9_]+)\|(.+)@mit\.edu', user)
|
2012-11-02 16:38:00 -04:00
|
|
|
if not match_user:
|
|
|
|
raise Exception("Could not parse Zephyr realm for cross-realm user %s" % (humbug_username,))
|
|
|
|
return match_user.group(1).lower() + "@" + match_user.group(2).upper()
|
2012-11-02 15:46:50 -04:00
|
|
|
|
2012-11-05 15:39:09 -05:00
|
|
|
# Checks whether the pair of adjacent lines would have been
|
|
|
|
# linewrapped together, had they been intended to be parts of the same
|
|
|
|
# paragraph. Our check is whether if you move the first word on the
|
|
|
|
# 2nd line onto the first line, the resulting line is either (1)
|
|
|
|
# significantly shorter than the following line (which, if they were
|
|
|
|
# in the same paragraph, should have been wrapped in a way consistent
|
|
|
|
# with how the previous line was wrapped) or (2) shorter than 60
|
|
|
|
# characters (our assumed minimum linewrapping threshhold for Zephyr)
|
2012-11-05 15:44:08 -05:00
|
|
|
# or (3) the first word of the next line is longer than this entire
|
|
|
|
# line.
|
2012-11-05 15:39:09 -05:00
|
|
|
def different_paragraph(line, next_line):
|
2012-11-05 12:46:16 -05:00
|
|
|
words = next_line.split()
|
2012-11-05 15:39:09 -05:00
|
|
|
return (len(line + " " + words[0]) < len(next_line) * 0.8 or
|
2012-11-05 15:44:08 -05:00
|
|
|
len(line + " " + words[0]) < 60 or
|
|
|
|
len(line) < len(words[0]))
|
2012-11-05 12:46:16 -05:00
|
|
|
|
|
|
|
# Linewrapping algorithm based on:
|
|
|
|
# http://gcbenison.wordpress.com/2011/07/03/a-program-to-intelligently-remove-carriage-returns-so-you-can-paste-text-without-having-it-look-awful/
|
2012-10-23 11:22:51 -04:00
|
|
|
def unwrap_lines(body):
|
2012-11-05 12:46:16 -05:00
|
|
|
lines = body.split("\n")
|
|
|
|
result = ""
|
|
|
|
previous_line = lines[0]
|
|
|
|
for line in lines[1:]:
|
2012-11-05 15:39:27 -05:00
|
|
|
line = line.rstrip()
|
2012-11-06 10:40:08 -05:00
|
|
|
if (re.match(r'^\W', line, flags=re.UNICODE)
|
|
|
|
and re.match(r'^\W', previous_line, flags=re.UNICODE)):
|
|
|
|
result += previous_line + "\n"
|
|
|
|
elif (line == "" or
|
2012-11-05 12:46:16 -05:00
|
|
|
previous_line == "" or
|
2012-11-06 10:40:08 -05:00
|
|
|
re.match(r'^\W', line, flags=re.UNICODE) or
|
2012-11-05 15:39:09 -05:00
|
|
|
different_paragraph(previous_line, line)):
|
2012-11-05 12:46:16 -05:00
|
|
|
# Use 2 newlines to separate sections so that we
|
|
|
|
# trigger proper Markdown processing on things like
|
|
|
|
# bulleted lists
|
|
|
|
result += previous_line + "\n\n"
|
|
|
|
else:
|
|
|
|
result += previous_line + " "
|
|
|
|
previous_line = line
|
|
|
|
result += previous_line
|
|
|
|
return result
|
2012-10-23 11:22:51 -04:00
|
|
|
|
2012-10-02 14:09:24 -04:00
|
|
|
def send_humbug(zeph):
|
2012-11-02 16:58:18 -04:00
|
|
|
message = {}
|
2012-10-18 11:14:03 -04:00
|
|
|
if options.forward_class_messages:
|
2012-11-02 16:58:18 -04:00
|
|
|
message["forged"] = "yes"
|
|
|
|
message['type'] = zeph['type']
|
|
|
|
message['time'] = zeph['time']
|
|
|
|
message['sender'] = to_humbug_username(zeph['sender'])
|
|
|
|
message['fullname'] = username_to_fullname(zeph['sender'])
|
|
|
|
message['shortname'] = zeph['sender'].split('@')[0]
|
2012-10-10 18:01:39 -04:00
|
|
|
if "subject" in zeph:
|
2012-11-02 20:55:07 -04:00
|
|
|
# Truncate the subject to the current limit in Humbug. No
|
|
|
|
# need to do this for stream names, since we're only
|
|
|
|
# subscribed to valid stream names.
|
2012-11-02 16:58:18 -04:00
|
|
|
message["subject"] = zeph["subject"][:60]
|
2012-10-16 14:57:42 -04:00
|
|
|
if zeph['type'] == 'stream':
|
|
|
|
# Forward messages sent to -c foo -i bar to stream bar subject "instance"
|
|
|
|
if zeph["stream"] == "message":
|
2012-11-02 17:07:20 -04:00
|
|
|
message['stream'] = zeph['subject'].lower()
|
2012-11-01 17:31:21 -04:00
|
|
|
message['subject'] = "instance %s" % (zeph['subject'],)
|
2012-10-16 14:57:42 -04:00
|
|
|
elif zeph["stream"] == "tabbott-test5":
|
2012-11-02 17:07:20 -04:00
|
|
|
message['stream'] = zeph['subject'].lower()
|
2012-11-01 17:31:21 -04:00
|
|
|
message['subject'] = "test instance %s" % (zeph['subject'],)
|
2012-11-02 16:58:18 -04:00
|
|
|
else:
|
|
|
|
message["stream"] = zeph["stream"]
|
|
|
|
else:
|
|
|
|
message["recipient"] = zeph["recipient"]
|
|
|
|
message['content'] = unwrap_lines(zeph['content'])
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-11-02 16:58:18 -04:00
|
|
|
return humbug_client.send_message(message)
|
2012-10-02 14:09:24 -04:00
|
|
|
|
|
|
|
def fetch_fullname(username):
|
|
|
|
try:
|
|
|
|
match_user = re.match(r'([a-zA-Z0-9_]+)@mit\.edu', username)
|
|
|
|
if match_user:
|
2012-10-12 13:53:29 -04:00
|
|
|
proc = subprocess.Popen(['hesinfo', match_user.group(1), 'passwd'],
|
2012-10-12 15:46:42 -04:00
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE)
|
2012-10-02 14:09:24 -04:00
|
|
|
out, _err_unused = proc.communicate()
|
|
|
|
if proc.returncode == 0:
|
|
|
|
return out.split(':')[4].split(',')[0]
|
|
|
|
except:
|
2012-10-12 15:45:14 -04:00
|
|
|
print >>sys.stderr, '%s: zephyr=>humbug: Error getting fullname for %s' % \
|
|
|
|
(datetime.datetime.now(), username)
|
2012-10-02 14:09:24 -04:00
|
|
|
traceback.print_exc()
|
|
|
|
|
2012-11-02 16:38:00 -04:00
|
|
|
if "@" not in username:
|
|
|
|
return username
|
|
|
|
(user, realm) = username.split("@")
|
|
|
|
if realm.upper() == "MIT.EDU":
|
|
|
|
return user
|
|
|
|
return user.lower() + "@" + realm.upper()
|
2012-10-02 14:09:24 -04:00
|
|
|
|
|
|
|
fullnames = {}
|
|
|
|
def username_to_fullname(username):
|
|
|
|
if username not in fullnames:
|
|
|
|
fullnames[username] = fetch_fullname(username)
|
|
|
|
return fullnames[username]
|
|
|
|
|
2012-10-29 21:32:59 -04:00
|
|
|
current_zephyr_subs = set()
|
2012-11-07 14:32:02 -05:00
|
|
|
def zephyr_bulk_subscribe(subs):
|
2012-11-07 11:44:45 -05:00
|
|
|
try:
|
2012-11-07 14:32:02 -05:00
|
|
|
zephyr._z.subAll(subs)
|
2012-11-07 11:44:45 -05:00
|
|
|
except IOError:
|
|
|
|
# Since we haven't added the subscription to
|
|
|
|
# current_zephyr_subs yet, we can just return (so that we'll
|
|
|
|
# continue processing normal messages) and we'll end up
|
|
|
|
# retrying the next time the bot checks its subscriptions are
|
|
|
|
# up to date.
|
|
|
|
traceback.print_exc()
|
2012-11-07 14:32:02 -05:00
|
|
|
print "Error subscribing to streams; will retry later."
|
2012-11-07 11:44:45 -05:00
|
|
|
return
|
2012-11-07 14:32:02 -05:00
|
|
|
for (cls, instance, recipient) in subs:
|
|
|
|
current_zephyr_subs.add(cls)
|
2012-10-11 14:39:52 -04:00
|
|
|
|
|
|
|
def update_subscriptions_from_humbug():
|
|
|
|
try:
|
|
|
|
res = humbug_client.get_public_streams()
|
|
|
|
streams = res["streams"]
|
|
|
|
except:
|
2012-11-01 17:31:21 -04:00
|
|
|
print "%s: Error getting public streams:" % (datetime.datetime.now(),)
|
2012-10-11 14:39:52 -04:00
|
|
|
traceback.print_exc()
|
|
|
|
return
|
2012-11-07 14:32:02 -05:00
|
|
|
streams_to_subscribe = []
|
2012-10-11 14:39:52 -04:00
|
|
|
for stream in streams:
|
2012-11-07 14:32:02 -05:00
|
|
|
if stream in current_zephyr_subs:
|
|
|
|
continue
|
|
|
|
streams_to_subscribe.append((stream, "*", "*"))
|
|
|
|
zephyr_bulk_subscribe(streams_to_subscribe)
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-10-12 14:51:57 -04:00
|
|
|
def maybe_restart_mirroring_script():
|
2012-11-07 15:37:46 -05:00
|
|
|
if os.stat(os.path.join(options.root_path, "stamps", "restart_stamp")).st_mtime > start_time or \
|
2012-10-29 14:34:24 -04:00
|
|
|
((options.user == "tabbott" or options.user == "tabbott/extra") and
|
2012-11-07 15:37:46 -05:00
|
|
|
os.stat(os.path.join(options.root_path, "stamps", "tabbott_stamp")).st_mtime > start_time):
|
2012-10-12 17:47:18 -04:00
|
|
|
print
|
2012-10-12 15:45:14 -04:00
|
|
|
print "%s: zephyr mirroring script has been updated; restarting..." % \
|
2012-11-01 17:31:21 -04:00
|
|
|
(datetime.datetime.now(),)
|
2012-11-05 12:55:54 -05:00
|
|
|
os.kill(child_pid, signal.SIGTERM)
|
2012-10-12 15:13:14 -04:00
|
|
|
while True:
|
|
|
|
try:
|
2012-10-30 16:47:05 -04:00
|
|
|
if bot_name == "extra_mirror.py":
|
2012-11-07 15:37:46 -05:00
|
|
|
os.execvp(os.path.join(options.root_path, "extra_mirror.py"), sys.argv)
|
|
|
|
os.execvp(os.path.join(options.root_path, "user_root", "zephyr_mirror.py"), sys.argv)
|
2012-10-12 15:13:14 -04:00
|
|
|
except:
|
|
|
|
print "Error restarting, trying again."
|
|
|
|
traceback.print_exc()
|
2012-11-05 16:44:04 -05:00
|
|
|
time.sleep(1)
|
2012-10-12 14:51:57 -04:00
|
|
|
|
2012-10-02 14:09:24 -04:00
|
|
|
def process_loop(log):
|
2012-10-11 14:39:52 -04:00
|
|
|
sleep_count = 0
|
|
|
|
sleep_time = 0.1
|
2012-10-02 14:09:24 -04:00
|
|
|
while True:
|
2012-10-11 14:39:52 -04:00
|
|
|
notice = zephyr.receive(block=False)
|
2012-10-12 14:51:28 -04:00
|
|
|
if notice is not None:
|
|
|
|
try:
|
|
|
|
process_notice(notice, log)
|
|
|
|
except:
|
2012-10-12 15:45:14 -04:00
|
|
|
print >>sys.stderr, '%s: zephyr=>humbug: Error relaying zephyr' % \
|
2012-11-01 17:31:21 -04:00
|
|
|
(datetime.datetime.now(),)
|
2012-10-12 14:51:28 -04:00
|
|
|
traceback.print_exc()
|
|
|
|
time.sleep(2)
|
|
|
|
|
2012-10-12 14:51:57 -04:00
|
|
|
maybe_restart_mirroring_script()
|
|
|
|
|
2012-10-12 14:51:28 -04:00
|
|
|
time.sleep(sleep_time)
|
|
|
|
sleep_count += sleep_time
|
|
|
|
if sleep_count > 15:
|
|
|
|
sleep_count = 0
|
|
|
|
if options.forward_class_messages:
|
|
|
|
# Ask the Humbug server about any new classes to subscribe to
|
|
|
|
update_subscriptions_from_humbug()
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-11-02 17:16:19 -04:00
|
|
|
def parse_zephyr_body(zephyr_data):
|
2012-10-19 13:13:52 -04:00
|
|
|
try:
|
2012-11-02 17:16:19 -04:00
|
|
|
(zsig, body) = zephyr_data.split("\x00", 1)
|
2012-10-19 13:13:52 -04:00
|
|
|
except ValueError:
|
2012-11-02 17:16:19 -04:00
|
|
|
(zsig, body) = ("", zephyr_data)
|
|
|
|
return (zsig, body)
|
|
|
|
|
|
|
|
def process_notice(notice, log):
|
|
|
|
(zsig, body) = parse_zephyr_body(notice.message)
|
2012-10-12 14:39:47 -04:00
|
|
|
is_personal = False
|
|
|
|
is_huddle = False
|
|
|
|
|
|
|
|
if notice.opcode == "PING":
|
|
|
|
# skip PING messages
|
|
|
|
return
|
|
|
|
|
2012-11-02 17:16:31 -04:00
|
|
|
if zsig.endswith("@(@color(blue))"):
|
|
|
|
print "%s: zephyr=>humbug: Skipping message we got from Humbug!" % \
|
2012-11-01 17:31:21 -04:00
|
|
|
(datetime.datetime.now(),)
|
2012-11-02 17:16:31 -04:00
|
|
|
return
|
2012-10-12 14:39:47 -04:00
|
|
|
|
2012-10-16 14:39:53 -04:00
|
|
|
zephyr_class = notice.cls.lower()
|
2012-10-12 14:39:47 -04:00
|
|
|
|
2012-11-02 16:48:39 -04:00
|
|
|
if (zephyr_class == "message" and notice.recipient != ""):
|
2012-10-12 14:39:47 -04:00
|
|
|
is_personal = True
|
|
|
|
if body.startswith("CC:"):
|
|
|
|
is_huddle = True
|
|
|
|
# Map "CC: sipbtest espuser" => "starnine@mit.edu,espuser@mit.edu"
|
2012-11-02 16:41:04 -04:00
|
|
|
huddle_recipients_list = [to_humbug_username(x.strip()) for x in
|
2012-10-12 14:39:47 -04:00
|
|
|
body.split("\n")[0][4:].split()]
|
2012-11-02 16:58:18 -04:00
|
|
|
if notice.sender not in huddle_recipients_list:
|
|
|
|
huddle_recipients_list.append(to_humbug_username(notice.sender))
|
2012-10-12 14:39:47 -04:00
|
|
|
huddle_recipients = ",".join(huddle_recipients_list)
|
2012-11-02 17:06:59 -04:00
|
|
|
body = body.split("\n", 1)[1]
|
|
|
|
if (zephyr_class == "mail" and notice.instance.lower() == "inbox"):
|
2012-10-12 14:39:47 -04:00
|
|
|
is_personal = True
|
|
|
|
|
|
|
|
# Drop messages not to the listed subscriptions
|
2012-10-16 14:39:53 -04:00
|
|
|
if (zephyr_class not in current_zephyr_subs) and not \
|
2012-10-12 14:39:47 -04:00
|
|
|
(is_personal and options.forward_personals):
|
2012-10-12 15:45:14 -04:00
|
|
|
print "%s: zephyr=>humbug: Skipping ... %s/%s/%s" % \
|
2012-11-02 17:06:59 -04:00
|
|
|
(datetime.datetime.now(), zephyr_class, notice.instance, is_personal)
|
2012-10-12 14:39:47 -04:00
|
|
|
return
|
|
|
|
|
2012-11-02 17:06:59 -04:00
|
|
|
zeph = { 'time' : str(notice.time),
|
|
|
|
'sender' : notice.sender,
|
|
|
|
'zsig' : zsig, # logged here but not used by app
|
|
|
|
'content' : body }
|
2012-10-12 14:39:47 -04:00
|
|
|
if is_huddle:
|
2012-11-02 17:06:59 -04:00
|
|
|
zeph['type'] = 'personal'
|
|
|
|
zeph['recipient'] = huddle_recipients
|
2012-10-12 14:39:47 -04:00
|
|
|
elif is_personal:
|
2012-11-02 17:06:59 -04:00
|
|
|
zeph['type'] = 'personal'
|
|
|
|
zeph['recipient'] = to_humbug_username(notice.recipient)
|
2012-10-12 14:39:47 -04:00
|
|
|
else:
|
2012-11-02 17:06:59 -04:00
|
|
|
zeph['type'] = 'stream'
|
|
|
|
zeph['stream'] = zephyr_class
|
|
|
|
if notice.instance != "":
|
2012-11-02 17:07:20 -04:00
|
|
|
zeph['subject'] = notice.instance
|
2012-11-02 17:06:59 -04:00
|
|
|
else:
|
2012-10-29 18:23:36 -04:00
|
|
|
zeph["subject"] = "personal"
|
2012-10-12 14:39:47 -04:00
|
|
|
|
2012-10-17 11:20:52 -04:00
|
|
|
# Add instances in for instanced personals
|
2012-11-05 00:44:08 -05:00
|
|
|
if zeph['type'] == "personal" and notice.instance.lower() != "personal":
|
2012-11-02 17:06:59 -04:00
|
|
|
zeph["content"] = "[-i %s]" % (notice.instance,) + "\n" + zeph["content"]
|
2012-10-17 11:20:52 -04:00
|
|
|
|
2012-10-29 18:53:36 -04:00
|
|
|
zeph = decode_unicode_byte_strings(zeph)
|
2012-10-29 17:10:56 -04:00
|
|
|
|
2012-10-12 15:45:14 -04:00
|
|
|
print "%s: zephyr=>humbug: received a message on %s/%s from %s..." % \
|
2012-11-02 17:06:59 -04:00
|
|
|
(datetime.datetime.now(), zephyr_class, notice.instance, notice.sender)
|
2012-10-12 14:39:47 -04:00
|
|
|
log.write(simplejson.dumps(zeph) + '\n')
|
|
|
|
log.flush()
|
|
|
|
|
|
|
|
res = send_humbug(zeph)
|
|
|
|
if res.get("result") != "success":
|
|
|
|
print >>sys.stderr, 'Error relaying zephyr'
|
|
|
|
print zeph
|
|
|
|
print res
|
|
|
|
|
2012-10-29 18:53:36 -04:00
|
|
|
def decode_unicode_byte_strings(zeph):
|
|
|
|
for field in zeph.keys():
|
|
|
|
if isinstance(zeph[field], str):
|
|
|
|
try:
|
|
|
|
decoded = zeph[field].decode("utf-8")
|
|
|
|
except:
|
|
|
|
decoded = zeph[field].decode("iso-8859-1")
|
|
|
|
zeph[field] = decoded
|
|
|
|
return zeph
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-11-07 11:44:45 -05:00
|
|
|
def zephyr_subscribe_autoretry(sub):
|
|
|
|
while True:
|
|
|
|
try:
|
2012-11-07 14:55:30 -05:00
|
|
|
zephyr.Subscriptions().add(sub)
|
2012-11-07 11:44:45 -05:00
|
|
|
return
|
|
|
|
except IOError:
|
|
|
|
# Probably a SERVNAK from the zephyr server, but print the
|
|
|
|
# traceback just in case it's something else
|
|
|
|
traceback.print_exc()
|
|
|
|
print "Error subscribing to personals; retrying."
|
|
|
|
time.sleep(1)
|
|
|
|
|
2012-10-02 14:09:24 -04:00
|
|
|
def zephyr_to_humbug(options):
|
|
|
|
if options.forward_class_messages:
|
2012-10-15 13:13:57 -04:00
|
|
|
update_subscriptions_from_humbug()
|
2012-10-02 14:09:24 -04:00
|
|
|
if options.forward_personals:
|
2012-11-07 11:44:45 -05:00
|
|
|
# Subscribe to personals; we really can't operate without
|
|
|
|
# those subscriptions, so just retry until it works.
|
|
|
|
zephyr_subscribe_autoretry(("message", "*", "%me%"))
|
2012-10-12 13:40:57 -04:00
|
|
|
if subscribed_to_mail_messages():
|
2012-11-07 11:44:45 -05:00
|
|
|
zephyr_subscribe_autoretry(("mail", "inbox", "%me%"))
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-11-07 14:11:33 -05:00
|
|
|
if options.resend_log_path is not None:
|
|
|
|
with open(options.resend_log_path, 'r') as log:
|
2012-10-02 14:09:24 -04:00
|
|
|
for ln in log:
|
|
|
|
try:
|
|
|
|
zeph = simplejson.loads(ln)
|
2012-10-29 17:10:56 -04:00
|
|
|
# New messages added to the log shouldn't have any
|
|
|
|
# elements of type str (they should already all be
|
|
|
|
# unicode), but older messages in the log are
|
|
|
|
# still of type str, so convert them before we
|
|
|
|
# send the message
|
2012-10-29 18:53:36 -04:00
|
|
|
zeph = decode_unicode_byte_strings(zeph)
|
|
|
|
# Handle importing older zephyrs in the logs
|
|
|
|
# where it isn't called a "stream" yet
|
|
|
|
if "class" in zeph:
|
|
|
|
zeph["stream"] = zeph["class"]
|
|
|
|
if "instance" in zeph:
|
|
|
|
zeph["subject"] = zeph["instance"]
|
2012-10-12 15:45:14 -04:00
|
|
|
print "%s: zephyr=>humbug: sending saved message to %s from %s..." % \
|
2012-10-29 17:10:56 -04:00
|
|
|
(datetime.datetime.now(), zeph.get('stream', zeph.get('recipient')),
|
2012-10-12 15:45:14 -04:00
|
|
|
zeph['sender'])
|
2012-10-02 14:09:24 -04:00
|
|
|
send_humbug(zeph)
|
|
|
|
except:
|
|
|
|
print >>sys.stderr, 'Could not send saved zephyr'
|
|
|
|
traceback.print_exc()
|
|
|
|
time.sleep(2)
|
|
|
|
|
2012-10-12 15:45:14 -04:00
|
|
|
print "%s: zephyr=>humbug: Starting receive loop." % (datetime.datetime.now(),)
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-11-07 14:11:33 -05:00
|
|
|
if options.log_path is not None:
|
|
|
|
log_file = options.log_path
|
2012-10-02 14:09:24 -04:00
|
|
|
else:
|
|
|
|
log_file = "/dev/null"
|
|
|
|
|
|
|
|
with open(log_file, 'a') as log:
|
|
|
|
process_loop(log)
|
|
|
|
|
2012-10-02 15:47:59 -04:00
|
|
|
def forward_to_zephyr(message):
|
2012-11-01 17:31:21 -04:00
|
|
|
zsig = u"%s@(@color(blue))" % (username_to_fullname(message["sender_email"]),)
|
2012-10-02 14:09:24 -04:00
|
|
|
if ' dot ' in zsig:
|
2012-10-12 15:45:14 -04:00
|
|
|
print "%s: humbug=>zephyr: ERROR! Couldn't compute zsig for %s!" % \
|
|
|
|
(datetime.datetime.now(), message["sender_email"])
|
2012-10-02 14:09:24 -04:00
|
|
|
return
|
|
|
|
|
|
|
|
wrapped_content = "\n".join("\n".join(textwrap.wrap(line))
|
2012-10-11 13:34:30 -04:00
|
|
|
for line in message["content"].split("\n"))
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-10-12 15:45:14 -04:00
|
|
|
print "%s: humbug=>zephyr: Forwarding message from %s" % \
|
2012-11-02 15:46:50 -04:00
|
|
|
(datetime.datetime.now(), message["sender_email"])
|
2012-10-10 16:57:21 -04:00
|
|
|
if message['type'] == "stream":
|
2012-10-16 14:57:42 -04:00
|
|
|
zephyr_class = message["display_recipient"]
|
|
|
|
instance = message["subject"]
|
|
|
|
if (instance == "instance %s" % (zephyr_class,) or
|
|
|
|
instance == "test instance %s" % (zephyr_class,)):
|
|
|
|
# Forward messages to e.g. -c -i white-magic back from the
|
|
|
|
# place we forward them to
|
|
|
|
if instance.startswith("test"):
|
|
|
|
instance = zephyr_class
|
|
|
|
zephyr_class = "tabbott-test5"
|
|
|
|
else:
|
|
|
|
instance = zephyr_class
|
|
|
|
zephyr_class = "message"
|
2012-11-02 11:30:22 -04:00
|
|
|
zwrite_args = ["zwrite", "-s", zsig, "-c", zephyr_class, "-i", instance]
|
2012-10-02 14:09:24 -04:00
|
|
|
elif message['type'] == "personal":
|
2012-11-02 15:46:50 -04:00
|
|
|
recipient = to_zephyr_username(message["display_recipient"]["email"])
|
2012-11-02 11:30:22 -04:00
|
|
|
zwrite_args = ["zwrite", "-s", zsig, recipient]
|
2012-10-02 14:09:24 -04:00
|
|
|
elif message['type'] == "huddle":
|
2012-11-02 11:30:22 -04:00
|
|
|
zwrite_args = ["zwrite", "-s", zsig, "-C"]
|
2012-11-02 15:46:50 -04:00
|
|
|
zwrite_args.extend([to_zephyr_username(user["email"]).replace("@ATHENA.MIT.EDU", "")
|
2012-11-02 11:30:22 -04:00
|
|
|
for user in message["display_recipient"]])
|
|
|
|
|
|
|
|
p = subprocess.Popen(zwrite_args, stdin=subprocess.PIPE,
|
|
|
|
stdout=subprocess.PIPE)
|
2012-11-04 06:50:36 -05:00
|
|
|
p.communicate(input=wrapped_content.encode("utf-8"))
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-10-02 15:47:59 -04:00
|
|
|
def maybe_forward_to_zephyr(message):
|
2012-11-02 18:13:48 -04:00
|
|
|
if (message["sender_email"] == options.user + "@mit.edu"):
|
|
|
|
if not ((message["type"] == "stream") or
|
|
|
|
(message["type"] == "personal" and
|
|
|
|
message["display_recipient"]["email"].lower().endswith("mit.edu")) or
|
|
|
|
(message["type"] == "huddle" and
|
|
|
|
False not in [u["email"].lower().endswith("mit.edu") for u in
|
|
|
|
message["display_recipient"]])):
|
|
|
|
# Don't try forward personals/huddles with non-MIT users
|
|
|
|
# to MIT Zephyr.
|
|
|
|
return
|
2012-10-12 13:53:29 -04:00
|
|
|
timestamp_now = datetime.datetime.now().strftime("%s")
|
|
|
|
if float(message["timestamp"]) < float(timestamp_now) - 15:
|
2012-10-12 15:45:14 -04:00
|
|
|
print "%s humbug=>zephyr: Alert! Out of order message: %s < %s" % \
|
|
|
|
(datetime.datetime.now(), message["timestamp"], timestamp_now)
|
2012-10-02 15:47:59 -04:00
|
|
|
return
|
2012-11-02 13:53:29 -04:00
|
|
|
try:
|
|
|
|
forward_to_zephyr(message)
|
|
|
|
except:
|
|
|
|
# Don't let an exception forwarding one message crash the
|
|
|
|
# whole process
|
|
|
|
traceback.print_exc()
|
2012-10-02 15:47:59 -04:00
|
|
|
|
2012-10-02 14:09:24 -04:00
|
|
|
def humbug_to_zephyr(options):
|
|
|
|
# Sync messages from zephyr to humbug
|
2012-10-12 15:45:14 -04:00
|
|
|
print "%s: humbug=>zephyr: Starting syncing messages." % (datetime.datetime.now(),)
|
2012-10-02 15:47:59 -04:00
|
|
|
humbug_client.call_on_each_message(maybe_forward_to_zephyr,
|
2012-10-19 15:37:37 -04:00
|
|
|
options={"mirror": 'zephyr_mirror'})
|
2012-10-02 14:09:24 -04:00
|
|
|
|
2012-10-12 13:40:57 -04:00
|
|
|
def subscribed_to_mail_messages():
|
2012-11-06 13:58:22 -05:00
|
|
|
# In case we have lost our AFS tokens and those won't be able to
|
|
|
|
# parse the Zephyr subs file, first try reading in result of this
|
|
|
|
# query from the environment so we can avoid the filesystem read.
|
|
|
|
stored_result = os.environ.get("HUMBUG_FORWARD_MAIL_ZEPHYRS")
|
|
|
|
if stored_result is not None:
|
|
|
|
return stored_result == "True"
|
2012-10-12 13:40:57 -04:00
|
|
|
for (cls, instance, recipient) in parse_zephyr_subs(verbose=False):
|
|
|
|
if (cls.lower() == "mail" and instance.lower() == "inbox"):
|
2012-11-06 13:58:22 -05:00
|
|
|
os.environ["HUMBUG_FORWARD_MAIL_ZEPHYRS"] = "True"
|
2012-10-12 13:40:57 -04:00
|
|
|
return True
|
2012-11-06 13:58:22 -05:00
|
|
|
os.environ["HUMBUG_FORWARD_MAIL_ZEPHYRS"] = "False"
|
2012-10-12 13:40:57 -04:00
|
|
|
return False
|
|
|
|
|
2012-10-29 12:51:44 -04:00
|
|
|
def add_humbug_subscriptions(verbose):
|
2012-10-12 13:40:57 -04:00
|
|
|
zephyr_subscriptions = set()
|
2012-10-29 12:51:44 -04:00
|
|
|
skipped = set()
|
|
|
|
for (cls, instance, recipient) in parse_zephyr_subs(verbose=verbose):
|
2012-10-29 18:09:51 -04:00
|
|
|
if cls == "message":
|
|
|
|
if recipient != "*":
|
|
|
|
# We already have a (message, *, you) subscription, so
|
|
|
|
# these are redundant
|
|
|
|
continue
|
|
|
|
# We don't support subscribing to (message, *)
|
2012-10-16 14:57:42 -04:00
|
|
|
if instance == "*":
|
2012-10-29 18:09:51 -04:00
|
|
|
if recipient == "*":
|
|
|
|
skipped.add((cls, instance, recipient, "subscribing to all of class message is not supported."))
|
2012-10-16 14:57:42 -04:00
|
|
|
continue
|
|
|
|
# If you're on -i white-magic on zephyr, get on stream white-magic on humbug
|
2012-10-29 12:51:44 -04:00
|
|
|
# instead of subscribing to stream "message" on humbug
|
2012-10-16 14:57:42 -04:00
|
|
|
zephyr_subscriptions.add(instance)
|
|
|
|
continue
|
2012-10-29 18:09:51 -04:00
|
|
|
elif cls == "mail" and instance == "inbox":
|
2012-11-02 17:22:21 -04:00
|
|
|
# We forward mail zephyrs, so no need to print a warning.
|
|
|
|
continue
|
|
|
|
elif len(cls) > 30:
|
|
|
|
skipped.add((cls, instance, recipient, "Class longer than 30 characters"))
|
2012-10-29 18:09:51 -04:00
|
|
|
continue
|
|
|
|
elif instance != "*":
|
|
|
|
skipped.add((cls, instance, recipient, "Unsupported non-* instance"))
|
2012-10-29 12:51:44 -04:00
|
|
|
continue
|
2012-10-29 18:09:51 -04:00
|
|
|
elif recipient != "*":
|
|
|
|
skipped.add((cls, instance, recipient, "Unsupported non-* recipient."))
|
|
|
|
continue
|
2012-10-12 13:40:57 -04:00
|
|
|
zephyr_subscriptions.add(cls)
|
2012-10-29 12:51:44 -04:00
|
|
|
|
2012-10-12 13:40:57 -04:00
|
|
|
if len(zephyr_subscriptions) != 0:
|
2012-10-29 12:51:44 -04:00
|
|
|
res = humbug_client.subscribe(list(zephyr_subscriptions))
|
2012-10-29 18:09:51 -04:00
|
|
|
if res.get("result") != "success":
|
|
|
|
print "Error subscribing to streams:"
|
|
|
|
print res["msg"]
|
|
|
|
return
|
|
|
|
|
2012-10-29 12:51:44 -04:00
|
|
|
already = res.get("already_subscribed")
|
|
|
|
new = res.get("subscribed")
|
|
|
|
if verbose:
|
|
|
|
if already is not None and len(already) > 0:
|
|
|
|
print
|
|
|
|
print "Already subscribed to:", ", ".join(already)
|
|
|
|
if new is not None and len(new) > 0:
|
|
|
|
print
|
|
|
|
print "Successfully subscribed to:", ", ".join(new)
|
|
|
|
|
|
|
|
if len(skipped) > 0:
|
|
|
|
if verbose:
|
|
|
|
print
|
|
|
|
print "\n".join(textwrap.wrap("""\
|
|
|
|
You have some lines in ~/.zephyr.subs that could not be
|
|
|
|
synced to your Humbug subscriptions because they do not
|
|
|
|
use "*" as both the instance and recipient and not one of
|
|
|
|
the special cases (e.g. personals and mail zephyrs) that
|
|
|
|
Humbug has a mechanism for forwarding. Humbug does not
|
|
|
|
allow subscribing to only some subjects on a Humbug
|
|
|
|
stream, so this tool has not created a corresponding
|
|
|
|
Humbug subscription to these lines in ~/.zephyr.subs:
|
|
|
|
"""))
|
|
|
|
print
|
|
|
|
|
2012-10-29 18:09:51 -04:00
|
|
|
for (cls, instance, recipient, reason) in skipped:
|
2012-10-29 12:51:44 -04:00
|
|
|
if verbose:
|
2012-10-29 18:09:51 -04:00
|
|
|
if reason != "":
|
|
|
|
print " [%s,%s,%s] (%s)" % (cls, instance, recipient, reason)
|
|
|
|
else:
|
|
|
|
print " [%s,%s,%s]" % (cls, instance, recipient, reason)
|
2012-10-29 12:51:44 -04:00
|
|
|
if len(skipped) > 0:
|
|
|
|
if verbose:
|
|
|
|
print
|
|
|
|
print "\n".join(textwrap.wrap("""\
|
|
|
|
If you wish to be subscribed to any Humbug streams related
|
|
|
|
to these .zephyrs.subs lines, please do so via the Humbug
|
|
|
|
web interface.
|
|
|
|
"""))
|
|
|
|
print
|
2012-10-12 13:40:57 -04:00
|
|
|
|
2012-10-29 18:09:51 -04:00
|
|
|
def valid_stream_name(name):
|
2012-11-07 15:42:32 -05:00
|
|
|
return name != ""
|
2012-10-29 18:09:51 -04:00
|
|
|
|
2012-10-12 13:40:57 -04:00
|
|
|
def parse_zephyr_subs(verbose=False):
|
2012-10-11 16:20:38 -04:00
|
|
|
zephyr_subscriptions = set()
|
|
|
|
subs_file = os.path.join(os.environ["HOME"], ".zephyr.subs")
|
|
|
|
if not os.path.exists(subs_file):
|
2012-10-12 13:40:57 -04:00
|
|
|
if verbose:
|
2012-10-29 18:09:51 -04:00
|
|
|
print >>sys.stderr, "Couldn't find ~/.zephyr.subs!"
|
2012-10-18 18:09:47 -04:00
|
|
|
return []
|
2012-10-11 16:20:38 -04:00
|
|
|
|
|
|
|
for line in file(subs_file, "r").readlines():
|
|
|
|
line = line.strip()
|
|
|
|
if len(line) == 0:
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
(cls, instance, recipient) = line.split(",")
|
2012-10-29 18:09:51 -04:00
|
|
|
cls = cls.replace("%me%", options.user)
|
|
|
|
instance = instance.replace("%me%", options.user)
|
|
|
|
recipient = recipient.replace("%me%", options.user)
|
|
|
|
if not valid_stream_name(cls):
|
|
|
|
if verbose:
|
|
|
|
print >>sys.stderr, "Skipping subscription to unsupported class name: [%s]" % (line,)
|
|
|
|
continue
|
2012-10-11 16:20:38 -04:00
|
|
|
except:
|
2012-10-12 13:40:57 -04:00
|
|
|
if verbose:
|
|
|
|
print >>sys.stderr, "Couldn't parse ~/.zephyr.subs line: [%s]" % (line,)
|
|
|
|
continue
|
2012-10-16 14:57:42 -04:00
|
|
|
zephyr_subscriptions.add((cls.strip(), instance.strip(), recipient.strip()))
|
2012-10-12 13:40:57 -04:00
|
|
|
return zephyr_subscriptions
|
2012-10-11 16:20:38 -04:00
|
|
|
|
2012-11-06 10:39:37 -05:00
|
|
|
if __name__ == "__main__":
|
|
|
|
parser = optparse.OptionParser()
|
|
|
|
parser.add_option('--forward-class-messages',
|
|
|
|
dest='forward_class_messages',
|
|
|
|
default=False,
|
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
action='store_true')
|
|
|
|
parser.add_option('--resend-log',
|
2012-11-07 14:11:33 -05:00
|
|
|
dest='resend_log_path',
|
|
|
|
default=None,
|
2012-11-06 10:39:37 -05:00
|
|
|
help=optparse.SUPPRESS_HELP,
|
2012-11-07 14:11:33 -05:00
|
|
|
action='store')
|
2012-11-06 10:39:37 -05:00
|
|
|
parser.add_option('--enable-log',
|
2012-11-07 14:11:33 -05:00
|
|
|
dest='log_path',
|
|
|
|
default=None,
|
2012-11-06 10:39:37 -05:00
|
|
|
help=optparse.SUPPRESS_HELP,
|
2012-11-07 14:11:33 -05:00
|
|
|
action='store')
|
2012-11-06 10:39:37 -05:00
|
|
|
parser.add_option('--no-forward-personals',
|
|
|
|
dest='forward_personals',
|
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
default=True,
|
|
|
|
action='store_false')
|
|
|
|
parser.add_option('--forward-from-humbug',
|
|
|
|
dest='forward_from_humbug',
|
|
|
|
default=False,
|
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
action='store_true')
|
|
|
|
parser.add_option('--verbose',
|
|
|
|
dest='verbose',
|
|
|
|
default=False,
|
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
action='store_true')
|
|
|
|
parser.add_option('--sync-subscriptions',
|
|
|
|
dest='sync_subscriptions',
|
|
|
|
default=False,
|
|
|
|
action='store_true')
|
|
|
|
parser.add_option('--site',
|
|
|
|
dest='site',
|
|
|
|
default="https://humbughq.com",
|
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
action='store')
|
|
|
|
parser.add_option('--user',
|
|
|
|
dest='user',
|
|
|
|
default=os.environ["USER"],
|
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
action='store')
|
2012-11-07 14:08:04 -05:00
|
|
|
parser.add_option('--root-path',
|
|
|
|
dest='root_path',
|
|
|
|
default="/mit/tabbott/for_friends",
|
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
action='store')
|
2012-11-06 10:39:37 -05:00
|
|
|
parser.add_option('--api-key-file',
|
|
|
|
dest='api_key_file',
|
|
|
|
default=os.path.join(os.environ["HOME"], "Private", ".humbug-api-key"),
|
|
|
|
action='store')
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
|
2012-11-07 15:37:46 -05:00
|
|
|
sys.path[:0] = [options.root_path, os.path.join(options.root_path, "python-zephyr"),
|
|
|
|
os.path.join(options.root_path, "python-zephyr/build/lib.linux-x86_64-2.6/")]
|
2012-11-07 14:08:04 -05:00
|
|
|
|
2012-11-06 13:58:22 -05:00
|
|
|
# In case this is an automated restart of the mirroring script,
|
|
|
|
# and we have lost AFS tokens, first try reading the API key from
|
|
|
|
# the environment so that we can skip doing a filesystem read.
|
|
|
|
if os.environ.get("HUMBUG_API_KEY") is not None:
|
|
|
|
api_key = os.environ.get("HUMBUG_API_KEY")
|
|
|
|
else:
|
|
|
|
if not os.path.exists(options.api_key_file):
|
2012-11-07 14:21:17 -05:00
|
|
|
print "\n".join(textwrap.wrap("""\
|
|
|
|
Could not find API key file.
|
|
|
|
You need to either place your api key file at %s,
|
|
|
|
or specify the --api-key-file option.""" % (options.api_key_file,)))
|
2012-11-06 13:58:22 -05:00
|
|
|
sys.exit(1)
|
|
|
|
api_key = file(options.api_key_file).read().strip()
|
|
|
|
# Store the API key in the environment so that our children
|
|
|
|
# don't need to read it in
|
|
|
|
os.environ["HUMBUG_API_KEY"] = api_key
|
2012-11-06 10:39:37 -05:00
|
|
|
|
|
|
|
import api.common
|
|
|
|
humbug_client = api.common.HumbugAPI(email=options.user + "@mit.edu",
|
|
|
|
api_key=api_key,
|
|
|
|
verbose=True,
|
|
|
|
client="zephyr_mirror",
|
|
|
|
site=options.site)
|
|
|
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
|
|
if options.sync_subscriptions:
|
|
|
|
print "Syncing your ~/.zephyr.subs to your Humbug Subscriptions!"
|
|
|
|
add_humbug_subscriptions(True)
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
if options.forward_from_humbug:
|
|
|
|
print "This option is obsolete."
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
# First check that there are no other bots running
|
|
|
|
cmdline = " ".join(sys.argv)
|
|
|
|
if "extra_mirror" in cmdline:
|
|
|
|
bot_name = "extra_mirror.py"
|
|
|
|
else:
|
|
|
|
bot_name = "zephyr_mirror.py"
|
|
|
|
proc = subprocess.Popen(['pgrep', '-U', os.environ["USER"], "-f", bot_name],
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE)
|
|
|
|
out, _err_unused = proc.communicate()
|
|
|
|
for pid in out.split():
|
|
|
|
if int(pid.strip()) != os.getpid():
|
|
|
|
# Another copy of zephyr_mirror.py! Kill it.
|
2012-11-01 17:31:21 -04:00
|
|
|
print "Killing duplicate zephyr_mirror process %s" % (pid,)
|
2012-11-06 10:39:37 -05:00
|
|
|
os.kill(int(pid), signal.SIGKILL)
|
|
|
|
|
|
|
|
child_pid = os.fork()
|
|
|
|
if child_pid == 0:
|
|
|
|
# Run the humbug => zephyr mirror in the child
|
|
|
|
humbug_to_zephyr(options)
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
import zephyr
|
|
|
|
zephyr.init()
|
|
|
|
zephyr_to_humbug(options)
|