2012-09-06 20:45:48 +02:00
|
|
|
#!/usr/bin/python
|
2012-09-13 02:29:26 +02:00
|
|
|
import mechanize
|
|
|
|
import urllib
|
|
|
|
import sys
|
|
|
|
import logging
|
|
|
|
import traceback
|
|
|
|
import simplejson
|
|
|
|
import re
|
|
|
|
import time
|
2012-09-13 03:41:52 +02:00
|
|
|
import subprocess
|
2012-09-21 21:17:25 +02:00
|
|
|
import optparse
|
2012-09-26 16:44:42 +02:00
|
|
|
import os
|
2012-09-27 22:14:14 +02:00
|
|
|
import datetime
|
2012-09-28 20:29:23 +02:00
|
|
|
import textwrap
|
2012-09-28 22:12:51 +02:00
|
|
|
from urllib2 import HTTPError
|
2012-09-26 22:46:23 +02:00
|
|
|
|
2012-09-28 20:26:56 +02:00
|
|
|
sys.path.append("/mit/tabbott/Public/python-zephyr/")
|
|
|
|
sys.path.append("/mit/tabbott/Public/python-zephyr/build/lib.linux-x86_64-2.6/")
|
|
|
|
import zephyr
|
|
|
|
|
2012-09-26 16:44:42 +02:00
|
|
|
zephyr.init()
|
2012-09-13 02:29:26 +02:00
|
|
|
|
2012-09-21 21:17:25 +02:00
|
|
|
parser = optparse.OptionParser()
|
|
|
|
parser.add_option('--forward-class-messages',
|
|
|
|
dest='forward_class_messages',
|
|
|
|
default=False,
|
|
|
|
action='store_true')
|
|
|
|
parser.add_option('--resend-log',
|
|
|
|
dest='resend_log',
|
|
|
|
default=False,
|
|
|
|
action='store_true')
|
2012-09-28 23:37:10 +02:00
|
|
|
parser.add_option('--enable-log',
|
|
|
|
dest='enable_log',
|
|
|
|
default=False,
|
|
|
|
action='store_true')
|
2012-09-21 21:17:25 +02:00
|
|
|
parser.add_option('--no-forward-personals',
|
|
|
|
dest='forward_personals',
|
|
|
|
default=True,
|
|
|
|
action='store_false')
|
2012-09-24 20:29:33 +02:00
|
|
|
parser.add_option('--forward-from-humbug',
|
|
|
|
dest='forward_to_humbug',
|
|
|
|
default=True,
|
|
|
|
action='store_false')
|
2012-09-21 21:17:25 +02:00
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
|
2012-09-07 17:45:09 +02:00
|
|
|
browser = None
|
2012-09-07 16:54:28 +02:00
|
|
|
csrf_token = None
|
|
|
|
|
|
|
|
def browser_login():
|
2012-09-07 17:45:09 +02:00
|
|
|
logger = logging.getLogger("mechanize")
|
|
|
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
|
|
|
logger.setLevel(logging.INFO)
|
|
|
|
|
|
|
|
global browser
|
|
|
|
browser = mechanize.Browser()
|
2012-09-07 16:54:28 +02:00
|
|
|
browser.set_handle_robots(False)
|
2012-09-06 20:45:48 +02:00
|
|
|
## debugging code to consider
|
2012-09-07 16:54:28 +02:00
|
|
|
# browser.set_debug_http(True)
|
|
|
|
# browser.set_debug_responses(True)
|
|
|
|
# browser.set_debug_redirects(True)
|
|
|
|
# browser.set_handle_refresh(False)
|
2012-09-07 17:14:26 +02:00
|
|
|
|
2012-09-07 16:54:28 +02:00
|
|
|
browser.add_password("https://app.humbughq.com/", "tabbott", "xxxxxxxxxxxxxxxxx", "wiki")
|
|
|
|
browser.open("https://app.humbughq.com/")
|
|
|
|
browser.follow_link(text_regex="\s*Log in\s*")
|
|
|
|
browser.select_form(nr=0)
|
2012-09-29 00:08:15 +02:00
|
|
|
browser["username"] = os.environ["USER"] + "@mit.edu"
|
|
|
|
browser["password"] = os.environ["USER"]
|
2012-09-07 16:54:28 +02:00
|
|
|
|
|
|
|
global csrf_token
|
2012-09-18 01:52:04 +02:00
|
|
|
csrf_token = browser["csrfmiddlewaretoken"]
|
|
|
|
|
|
|
|
browser.submit()
|
2012-09-06 20:45:48 +02:00
|
|
|
|
2012-10-01 20:36:08 +02:00
|
|
|
def compute_humbug_username(zephyr_username):
|
|
|
|
return zephyr_username.lower().split("@")[0] + "@mit.edu"
|
|
|
|
|
2012-09-26 16:43:53 +02:00
|
|
|
def send_humbug(zeph):
|
2012-10-01 20:36:08 +02:00
|
|
|
zeph["sender"] = compute_humbug_username(zeph["sender"])
|
2012-09-26 21:23:52 +02:00
|
|
|
if "recipient" in zeph:
|
2012-10-01 20:36:08 +02:00
|
|
|
zeph["recipient"] = compute_humbug_username(zeph["recipient"])
|
2012-09-17 21:39:01 +02:00
|
|
|
zeph['fullname'] = username_to_fullname(zeph['sender'])
|
|
|
|
zeph['shortname'] = zeph['sender'].split('@')[0]
|
2012-09-27 21:17:53 +02:00
|
|
|
if "instance" in zeph:
|
|
|
|
zeph["instance"] = zeph["instance"][:30]
|
2012-09-07 16:54:28 +02:00
|
|
|
browser.addheaders.append(('X-CSRFToken', csrf_token))
|
2012-09-26 21:23:52 +02:00
|
|
|
|
|
|
|
humbug_data = []
|
|
|
|
for key in zeph.keys():
|
2012-09-29 16:11:24 +02:00
|
|
|
if key == "zsig":
|
|
|
|
# Don't send zsigs to the Humbug server
|
|
|
|
continue
|
2012-09-26 22:46:23 +02:00
|
|
|
if isinstance(zeph[key], unicode):
|
|
|
|
val = zeph[key].encode("utf-8")
|
|
|
|
elif isinstance(zeph[key], str):
|
|
|
|
val = zeph[key].decode("utf-8")
|
2012-09-26 21:23:52 +02:00
|
|
|
humbug_data.append((key, val))
|
2012-09-28 22:12:51 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
browser.open("https://app.humbughq.com/forge_zephyr/", urllib.urlencode(humbug_data))
|
|
|
|
except HTTPError, e:
|
|
|
|
if e.code == 401:
|
|
|
|
# Digest auth failed; server was probably restarted; login in again
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
browser_login()
|
|
|
|
except HTTPError, e:
|
|
|
|
print "Failed logging in; trying again in 10 seconds."
|
|
|
|
time.sleep(10)
|
|
|
|
continue
|
|
|
|
break
|
|
|
|
|
|
|
|
print "Auth failure; trying again after logging in a second time!"
|
|
|
|
browser.open("https://app.humbughq.com/forge_zephyr/", urllib.urlencode(humbug_data))
|
|
|
|
else:
|
|
|
|
raise
|
|
|
|
|
2012-09-06 22:10:48 +02:00
|
|
|
|
2012-09-13 03:41:52 +02:00
|
|
|
def fetch_fullname(username):
|
|
|
|
try:
|
2012-09-21 21:17:25 +02:00
|
|
|
match_user = re.match(r'([a-zA-Z0-9_]+)@mit\.edu', username)
|
2012-09-13 03:41:52 +02:00
|
|
|
if match_user:
|
|
|
|
proc = subprocess.Popen(['hesinfo', match_user.group(1), 'passwd'], stdout=subprocess.PIPE)
|
|
|
|
out, _err_unused = proc.communicate()
|
|
|
|
if proc.returncode == 0:
|
|
|
|
return out.split(':')[4].split(',')[0]
|
|
|
|
except:
|
|
|
|
print >>sys.stderr, 'Error getting fullname for', username
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
return username.title().replace('@', ' at ').replace('.', ' dot ')
|
|
|
|
|
|
|
|
fullnames = {}
|
|
|
|
def username_to_fullname(username):
|
|
|
|
if username not in fullnames:
|
|
|
|
fullnames[username] = fetch_fullname(username)
|
|
|
|
return fullnames[username]
|
|
|
|
|
2012-09-07 17:45:09 +02:00
|
|
|
|
2012-09-24 20:29:33 +02:00
|
|
|
def process_loop(log):
|
2012-09-26 17:53:41 +02:00
|
|
|
import mit_subs_list
|
2012-09-13 02:29:26 +02:00
|
|
|
while True:
|
2012-09-07 19:17:56 +02:00
|
|
|
try:
|
2012-09-13 02:29:26 +02:00
|
|
|
notice = zephyr.receive(block=True)
|
|
|
|
zsig, body = notice.message.split("\x00", 1)
|
2012-09-21 22:24:16 +02:00
|
|
|
is_personal = False
|
|
|
|
is_huddle = False
|
2012-09-28 21:45:11 +02:00
|
|
|
if isinstance(zsig, str):
|
|
|
|
# Check for width unicode character u'\u200B'.encode("utf-8")
|
|
|
|
if u'\u200B'.encode("utf-8") in zsig:
|
|
|
|
print "Skipping message from Humbug!"
|
|
|
|
continue
|
2012-09-24 20:29:33 +02:00
|
|
|
|
2012-09-21 22:24:16 +02:00
|
|
|
sender = notice.sender.lower().replace("athena.mit.edu", "mit.edu")
|
|
|
|
recipient = notice.recipient.lower().replace("athena.mit.edu", "mit.edu")
|
2012-09-13 02:29:26 +02:00
|
|
|
|
2012-09-28 22:15:58 +02:00
|
|
|
if (notice.cls.lower() == "message" and
|
|
|
|
notice.instance.lower() == "personal"):
|
2012-09-21 21:17:25 +02:00
|
|
|
is_personal = True
|
2012-09-21 22:24:16 +02:00
|
|
|
if body.startswith("CC:"):
|
|
|
|
is_huddle = True
|
|
|
|
# Map "CC: sipbtest espuser" => "starnine@mit.edu,espuser@mit.edu"
|
|
|
|
huddle_recipients_list = [x + "@mit.edu" for x in
|
2012-09-24 20:29:33 +02:00
|
|
|
body.split("\n")[0][4:].split()]
|
2012-09-21 22:24:16 +02:00
|
|
|
if sender not in huddle_recipients_list:
|
|
|
|
huddle_recipients_list.append(sender)
|
|
|
|
huddle_recipients = ",".join(huddle_recipients_list)
|
2012-09-21 21:17:25 +02:00
|
|
|
|
|
|
|
if notice.opcode != "":
|
|
|
|
# skip PING messages
|
2012-09-13 02:29:26 +02:00
|
|
|
continue
|
2012-09-21 21:17:25 +02:00
|
|
|
|
|
|
|
# Drop messages not to the listed subscriptions
|
2012-09-28 22:15:58 +02:00
|
|
|
if (notice.cls.lower() not in mit_subs_list.all_subs) and not \
|
|
|
|
(is_personal and options.forward_personals):
|
2012-09-21 21:17:25 +02:00
|
|
|
print "Skipping ...", notice.cls, notice.instance, is_personal
|
|
|
|
continue
|
|
|
|
|
2012-09-21 22:24:16 +02:00
|
|
|
if is_huddle:
|
|
|
|
zeph = { 'type' : 'personal',
|
|
|
|
'time' : str(notice.time),
|
|
|
|
'sender' : sender,
|
|
|
|
'recipient' : huddle_recipients,
|
|
|
|
'zsig' : zsig, # logged here but not used by app
|
|
|
|
'new_zephyr': body.split("\n", 1)[1] }
|
|
|
|
elif is_personal:
|
2012-09-21 21:17:25 +02:00
|
|
|
zeph = { 'type' : 'personal',
|
|
|
|
'time' : str(notice.time),
|
|
|
|
'sender' : sender,
|
|
|
|
'recipient' : recipient,
|
|
|
|
'zsig' : zsig, # logged here but not used by app
|
|
|
|
'new_zephyr': body }
|
|
|
|
else:
|
|
|
|
zeph = { 'type' : 'class',
|
|
|
|
'time' : str(notice.time),
|
|
|
|
'sender' : sender,
|
2012-09-28 22:15:58 +02:00
|
|
|
'class' : notice.cls.lower(),
|
2012-09-21 21:17:25 +02:00
|
|
|
'instance' : notice.instance,
|
|
|
|
'zsig' : zsig, # logged here but not used by app
|
|
|
|
'new_zephyr': body }
|
2012-09-13 02:29:26 +02:00
|
|
|
|
|
|
|
log.write(simplejson.dumps(zeph) + '\n')
|
|
|
|
log.flush()
|
|
|
|
|
2012-10-01 17:57:35 +02:00
|
|
|
print "%s: received a message on %s/%s from %s..." % \
|
|
|
|
(datetime.datetime.now(), notice.cls, notice.instance, notice.sender)
|
2012-09-26 16:43:53 +02:00
|
|
|
send_humbug(zeph)
|
2012-09-07 19:17:56 +02:00
|
|
|
except:
|
2012-09-13 02:29:26 +02:00
|
|
|
print >>sys.stderr, 'Error relaying zephyr'
|
2012-09-07 19:17:56 +02:00
|
|
|
traceback.print_exc()
|
2012-09-13 02:29:26 +02:00
|
|
|
time.sleep(2)
|
2012-09-24 20:29:33 +02:00
|
|
|
|
|
|
|
|
|
|
|
def zephyr_to_humbug(options):
|
|
|
|
browser_login()
|
|
|
|
|
|
|
|
import mit_subs_list
|
|
|
|
subs = zephyr.Subscriptions()
|
|
|
|
if options.forward_class_messages:
|
|
|
|
for sub in mit_subs_list.all_subs:
|
|
|
|
subs.add((sub, '*', '*'))
|
|
|
|
if options.forward_personals:
|
|
|
|
subs.add(("message", "personal", "*"))
|
|
|
|
|
|
|
|
if options.resend_log:
|
|
|
|
with open('zephyrs', 'r') as log:
|
2012-09-26 21:23:52 +02:00
|
|
|
for ln in log:
|
|
|
|
try:
|
2012-09-24 20:29:33 +02:00
|
|
|
zeph = simplejson.loads(ln)
|
|
|
|
print "sending saved message to %s from %s..." % \
|
|
|
|
(zeph.get('class', zeph.get('recipient')), zeph['sender'])
|
2012-09-26 16:43:53 +02:00
|
|
|
send_humbug(zeph)
|
2012-09-26 21:23:52 +02:00
|
|
|
except:
|
|
|
|
print >>sys.stderr, 'Could not send saved zephyr'
|
|
|
|
traceback.print_exc()
|
|
|
|
time.sleep(2)
|
2012-09-24 20:29:33 +02:00
|
|
|
|
|
|
|
print "Starting receive loop"
|
|
|
|
|
2012-09-28 23:37:10 +02:00
|
|
|
if options.enable_log:
|
|
|
|
log_file = "zephyrs"
|
|
|
|
else:
|
|
|
|
log_file = "/dev/null"
|
|
|
|
|
|
|
|
with open(log_file, 'a') as log:
|
2012-09-24 20:29:33 +02:00
|
|
|
process_loop(log)
|
|
|
|
|
2012-10-01 20:42:34 +02:00
|
|
|
def get_new_humbugs(max_humbug_id):
|
2012-09-24 20:29:33 +02:00
|
|
|
browser.addheaders.append(('X-CSRFToken', csrf_token))
|
2012-09-27 22:14:14 +02:00
|
|
|
submit_hash = {"mit_sync_bot": 'yes'}
|
2012-10-01 20:42:34 +02:00
|
|
|
if max_humbug_id is not None:
|
|
|
|
submit_hash["first"] = str(0)
|
|
|
|
submit_hash["last"] = str(max_humbug_id)
|
2012-09-24 20:29:33 +02:00
|
|
|
submit_data = urllib.urlencode([(k, v.encode('utf-8')) for k,v in submit_hash.items()])
|
2012-09-27 22:14:14 +02:00
|
|
|
res = browser.open("https://app.humbughq.com/api/get_updates", submit_data)
|
2012-09-24 20:29:33 +02:00
|
|
|
return simplejson.loads(res.read())['zephyrs']
|
|
|
|
|
2012-09-26 16:44:42 +02:00
|
|
|
def send_zephyr(message):
|
2012-09-28 21:45:11 +02:00
|
|
|
zsig = u"%s\u200B" % (username_to_fullname(message["sender_email"]))
|
2012-09-28 21:29:44 +02:00
|
|
|
if ' dot ' in zsig:
|
|
|
|
print "ERROR! Couldn't compute zsig for %s!" % (message["sender_email"])
|
|
|
|
return
|
2012-10-02 20:18:53 +02:00
|
|
|
wrapped_content = "\n".join("\n".join(textwrap.wrap(line))
|
|
|
|
for line in message["content"].split("\n"))
|
2012-10-02 21:11:55 +02:00
|
|
|
wrapped_content = wrapped_content.replace('<','<').replace('>','>').replace('&', '&')
|
2012-09-28 20:49:59 +02:00
|
|
|
print "Sending message from %s humbug=>zephyr at %s" % (message["sender_email"], datetime.datetime.now())
|
2012-09-26 16:44:42 +02:00
|
|
|
if message['type'] == "class":
|
|
|
|
zeph = zephyr.ZNotice(sender=message["sender_email"].replace("mit.edu", "ATHENA.MIT.EDU"),
|
2012-09-27 23:53:40 +02:00
|
|
|
auth=True, cls=message["display_recipient"],
|
|
|
|
instance=message["instance"])
|
2012-09-28 20:29:23 +02:00
|
|
|
body = "%s\0%s" % (zsig, wrapped_content)
|
2012-09-27 22:14:14 +02:00
|
|
|
zeph.setmessage(body)
|
|
|
|
zeph.send()
|
2012-09-26 16:44:42 +02:00
|
|
|
elif message['type'] == "personal":
|
|
|
|
zeph = zephyr.ZNotice(sender=message["sender_email"].replace("mit.edu", "ATHENA.MIT.EDU"),
|
2012-09-27 22:14:14 +02:00
|
|
|
auth=True, recipient=message["display_recipient"].replace("mit.edu", "ATHENA.MIT.EDU"),
|
|
|
|
cls="message", instance="personal")
|
2012-09-28 20:29:23 +02:00
|
|
|
body = "%s\0%s" % (zsig, wrapped_content)
|
2012-09-27 22:14:14 +02:00
|
|
|
zeph.setmessage(body)
|
|
|
|
zeph.send()
|
2012-09-26 16:44:42 +02:00
|
|
|
elif message['type'] == "huddle":
|
|
|
|
cc_list = ["CC:"]
|
|
|
|
cc_list.extend([user["email"].replace("@mit.edu", "")
|
|
|
|
for user in message["display_recipient"]])
|
2012-09-28 20:29:23 +02:00
|
|
|
body = "%s\0%s\n%s" % (zsig, " ".join(cc_list), wrapped_content)
|
2012-09-27 22:14:14 +02:00
|
|
|
for r in message["display_recipient"]:
|
|
|
|
zeph = zephyr.ZNotice(sender=message["sender_email"].replace("mit.edu", "ATHENA.MIT.EDU"),
|
|
|
|
auth=True, recipient=r["email"].replace("mit.edu", "ATHENA.MIT.EDU"),
|
|
|
|
cls="message", instance="personal")
|
|
|
|
zeph.setmessage(body)
|
|
|
|
zeph.send()
|
2012-09-26 16:44:42 +02:00
|
|
|
|
2012-09-24 20:29:33 +02:00
|
|
|
def humbug_to_zephyr(options):
|
|
|
|
# Sync messages from zephyr to humbug
|
|
|
|
browser_login()
|
2012-09-27 22:14:14 +02:00
|
|
|
print "Starting syncing messages."
|
2012-10-01 20:42:34 +02:00
|
|
|
max_humbug_id = None
|
2012-09-24 20:29:33 +02:00
|
|
|
while True:
|
2012-09-28 22:12:51 +02:00
|
|
|
try:
|
2012-10-01 20:42:34 +02:00
|
|
|
humbugs = get_new_humbugs(max_humbug_id)
|
2012-09-28 22:12:51 +02:00
|
|
|
except HTTPError, e:
|
|
|
|
# 502/503 typically means the server was restarted; sleep
|
|
|
|
# a bit, then try again
|
2012-09-29 03:13:16 +02:00
|
|
|
print "Failed getting zephyrs; trying again in 5 seconds."
|
2012-10-02 18:19:11 +02:00
|
|
|
time.sleep(2)
|
2012-09-28 22:12:51 +02:00
|
|
|
if e.code == 401:
|
|
|
|
# 401 means digest auth failed -- we need to login again
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
browser_login()
|
|
|
|
except HTTPError, e:
|
|
|
|
print "Failed logging in; trying again in 10 seconds."
|
|
|
|
time.sleep(10)
|
|
|
|
continue
|
|
|
|
break
|
|
|
|
continue
|
2012-10-02 18:19:11 +02:00
|
|
|
except:
|
|
|
|
# For other errors, just try again
|
|
|
|
time.sleep(2)
|
|
|
|
continue
|
2012-10-01 20:42:34 +02:00
|
|
|
for humbug in humbugs:
|
|
|
|
max_humbug_id = max(max_humbug_id, humbug["id"])
|
|
|
|
if humbug["sender_email"] == os.environ["USER"] + "@mit.edu":
|
|
|
|
if float(humbug["timestamp"]) < float(datetime.datetime.now().strftime("%s")) - 5:
|
|
|
|
print "Alert! Out of order message!", humbug["timestamp"], datetime.datetime.now().strftime("%s")
|
2012-09-27 22:14:14 +02:00
|
|
|
continue
|
2012-10-01 20:42:34 +02:00
|
|
|
send_zephyr(humbug)
|
2012-09-24 20:29:33 +02:00
|
|
|
|
|
|
|
if options.forward_to_humbug:
|
|
|
|
zephyr_to_humbug(options)
|
|
|
|
else:
|
|
|
|
humbug_to_zephyr(options)
|