#!/usr/bin/env python
# encoding: utf-8
import codecs
from datetime import datetime
from optparse import OptionParser
import re
import time
import urllib, urllib2
from BeautifulSoup import BeautifulSoup, NavigableString
class Message:
def __init__(self, thread_url, sender, recipient, timestamp, subject, content):
self.thread_url = thread_url
self.sender = sender
self.recipient = recipient
self.timestamp = timestamp
self.subject = subject
self.content = content
def __str__(self):
return """
URL: %s
From: %s
To: %s
Date: %s
Subject: %s
Content-Length: %d
%s
""" % ( self.thread_url,
self.sender,
self.recipient,
self.timestamp,
self.subject.strip(),
len(self.content),
self.content
)
class ArrowFetcher:
base_url = 'http://w...content-available-to-author-only...d.com'
sleep_duration = 3.0 # time to wait after each HTTP request
def __init__(self, username, password):
self.username = username
self.thread_urls = []
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
urllib2.install_opener(opener)
params = urllib.urlencode(dict(username=username, password=password))
f = opener.open(self.base_url + '/login', params)
f.close()
def _safely_soupify(self, f):
f = f.partition("function autocoreError")[0] + '</body></html>' # wtf okc with the weirdly encoded "</scr' + 'ipt>'"-type statements in your javascript
return(BeautifulSoup(f))
def _request_read_sleep(self, url):
f = urllib2.urlopen(url).read()
time.sleep(self.sleep_duration)
return f
def queue_threads(self):
self.thread_urls = []
for folder in range(1,4): # Inbox, Sent, Smiles
page = 0;
while (True):
print "queuing folder %s, page %s" % (folder, page)
f = self._request_read_sleep(self.base_url + '/messages?folder=' + str(folder) + '&low=' + str((page * 30) + 1))
soup = self._safely_soupify(f)
end_pattern = re.compile('&folder=\d\';')
threads = [
re.sub(end_pattern, '', li.find('p')['onclick'].partition("window.location='")[2])
for li in soup.find('ul', {'id': 'messages'}).findAll('li')
]
if len(threads) == 0: # break out of the infinite loop when we reach the end and there are no threads on the page
break
else:
self.thread_urls.extend(threads)
page = page + 1
def dedupe_threads(self):
print "removing duplicate URLs"
self.thread_urls = list(set(self.thread_urls))
def fetch_threads(self):
self.messages = []
for thread_url in self.thread_urls:
self.messages.extend(self._fetch_thread(thread_url))
def strptime(self, string, format='%b %d, %Y – %I:%M%p'):
return datetime.strptime(string.strip(), format)
def write_messages(self, file_name):
self.messages.sort(key = lambda message: message.timestamp) # sort by time
f = codecs.open(file_name, encoding='utf-8', mode='w') # ugh, otherwise i think it will try to write ascii
for message in self.messages:
print "writing message for thread: " + message.thread_url
f.write(unicode(message))
f.close()
def _fetch_thread(self, thread_url):
message_list = []
print "fetching thread: " + self.base_url + thread_url
f = self._request_read_sleep(self.base_url + thread_url)
soup = self._safely_soupify(f)
try:
subject = soup.find('strong', {'id': 'message_heading'}).contents[0]
except AttributeError:
subject = ''
try:
other_user = soup.find('a', {'class': 'buddyname'}).contents[0]
except AttributeError:
try:
# messages from OkCupid itself are a special case
other_user = soup.find('ul', {'id': 'thread'}).find('p', 'signature').contents[0].partition('Message from ')[2]
except AttributeError:
other_user = ''
for message in soup.find('ul', {'id': 'thread'}).findAll('li'):
body_contents = message.find('div', 'message_body')
if body_contents:
body = self._strip_tags(body_contents.renderContents()).renderContents().strip()
for pair in [ ('<br />', '\n'),
('&', '&'),
('<', '<'),
('>', '>'),
('"', '"'),
(''', "'"),
('—', "—")]:
body = body.replace(pair[0], pair[1])
timestamp = message.find('span','timestamp')
if timestamp.decodeContents and timestamp.decodeContents():
timestamp = self.strptime(timestamp.decodeContents().strip())
else:
timestamp = self.strptime(timestamp.text.strip())
sender = other_user
recipient = self.username
if message['class'].replace('preview', '').strip() == 'from_me':
recipient = other_user
sender = self.username
message_list.append(Message(self.base_url + thread_url,
unicode(sender),
unicode(recipient),
timestamp,
unicode(subject),
body.decode('utf-8')))
else:
continue # control elements are also <li>'s in their html, so non-messages
return message_list
# http://stackoverflow.com/questions/1765848/remove-a-tag-using-beautifulsoup-but-keep-its-contents/1766002#1766002
def _strip_tags(self, html, invalid_tags=['a', 'span', 'strong', 'div']):
soup = BeautifulSoup(html)
for tag in soup.findAll(True):
if tag.name in invalid_tags:
s = ""
for c in tag.contents:
if type(c) != NavigableString:
c = self._strip_tags(unicode(c), invalid_tags)
s += unicode(c).strip()
else:
s += unicode(c)
tag.replaceWith(s)
return soup
def main():
parser = OptionParser()
parser.add_option("-u", "--username", dest="username",
help="your OkCupid username")
parser.add_option("-p", "--password", dest="password",
help="your OkCupid password")
parser.add_option("-f", "--filename", dest="filename",
help="the file to which you want to write the data")
(options, args) = parser.parse_args()
if not options.username:
print "Please specify your OkCupid username with either '-u' or '--username'"
if not options.password:
print "Please specify your OkCupid password with either '-p' or '--password'"
if not options.filename:
print "Please specify the destination file with either '-f' or '--filename'"
if options.username and options.password and options.filename:
arrow_fetcher = ArrowFetcher(options.username, options.password)
arrow_fetcher.queue_threads()
arrow_fetcher.dedupe_threads()
arrow_fetcher.fetch_threads()
arrow_fetcher.write_messages(options.filename)
if __name__ == '__main__':
main()
IyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgZW5jb2Rpbmc6IHV0Zi04CgppbXBvcnQgY29kZWNzCmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lCmZyb20gb3B0cGFyc2UgaW1wb3J0IE9wdGlvblBhcnNlcgppbXBvcnQgcmUKaW1wb3J0IHRpbWUKaW1wb3J0IHVybGxpYiwgdXJsbGliMgoKZnJvbSBCZWF1dGlmdWxTb3VwIGltcG9ydCBCZWF1dGlmdWxTb3VwLCBOYXZpZ2FibGVTdHJpbmcKCgpjbGFzcyBNZXNzYWdlOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHRocmVhZF91cmwsIHNlbmRlciwgcmVjaXBpZW50LCB0aW1lc3RhbXAsIHN1YmplY3QsIGNvbnRlbnQpOgogICAgICAgIHNlbGYudGhyZWFkX3VybCA9IHRocmVhZF91cmwKICAgICAgICBzZWxmLnNlbmRlciA9IHNlbmRlcgogICAgICAgIHNlbGYucmVjaXBpZW50ID0gcmVjaXBpZW50CiAgICAgICAgc2VsZi50aW1lc3RhbXAgPSB0aW1lc3RhbXAKICAgICAgICBzZWxmLnN1YmplY3QgPSBzdWJqZWN0CiAgICAgICAgc2VsZi5jb250ZW50ID0gY29udGVudAogICAgZGVmIF9fc3RyX18oc2VsZik6CiAgICAgICAgcmV0dXJuICIiIgpVUkw6ICVzCkZyb206ICVzClRvOiAlcwpEYXRlOiAlcwpTdWJqZWN0OiAlcwpDb250ZW50LUxlbmd0aDogJWQKCiVzCgoiIiIgICAgICAgICAgICAlICggIHNlbGYudGhyZWFkX3VybCwgCiAgICAgICAgICAgICAgICAgICAgc2VsZi5zZW5kZXIsIAogICAgICAgICAgICAgICAgICAgIHNlbGYucmVjaXBpZW50LCAKICAgICAgICAgICAgICAgICAgICBzZWxmLnRpbWVzdGFtcCwgCiAgICAgICAgICAgICAgICAgICAgc2VsZi5zdWJqZWN0LnN0cmlwKCksCiAgICAgICAgICAgICAgICAgICAgbGVuKHNlbGYuY29udGVudCksCiAgICAgICAgICAgICAgICAgICAgc2VsZi5jb250ZW50CiAgICAgICAgICAgICAgICAgICApCgoKY2xhc3MgQXJyb3dGZXRjaGVyOgogICAgYmFzZV91cmwgPSAnaHR0cDovL3cuLi5jb250ZW50LWF2YWlsYWJsZS10by1hdXRob3Itb25seS4uLmQuY29tJwogICAgc2xlZXBfZHVyYXRpb24gPSAzLjAgICMgdGltZSB0byB3YWl0IGFmdGVyIGVhY2ggSFRUUCByZXF1ZXN0CgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHVzZXJuYW1lLCBwYXNzd29yZCk6CiAgICAgICAgc2VsZi51c2VybmFtZSA9IHVzZXJuYW1lCiAgICAgICAgc2VsZi50aHJlYWRfdXJscyA9IFtdCiAgICAgICAgb3BlbmVyID0gdXJsbGliMi5idWlsZF9vcGVuZXIodXJsbGliMi5IVFRQQ29va2llUHJvY2Vzc29yKCkpCiAgICAgICAgdXJsbGliMi5pbnN0YWxsX29wZW5lcihvcGVuZXIpCiAgICAgICAgcGFyYW1zID0gdXJsbGliLnVybGVuY29kZShkaWN0KHVzZXJuYW1lPXVzZXJuYW1lLCBwYXNzd29yZD1wYXNzd29yZCkpCiAgICAgICAgZiA9IG9wZW5lci5vcGVuKHNlbGYuYmFzZV91cmwgKyAnL2xvZ2luJywgcGFyYW1zKQogICAgICAgIGYuY2xvc2UoKQogICAgCiAgICBkZWYgX3NhZmVseV9zb3VwaWZ5KHNlbGYsIGYpOgogICAgICAgIGYgPSBmLnBhcnRpdGlvbigiZnVuY3Rpb24gYXV0b2NvcmVFcnJvciIpWzBdICsgJzwvYm9keT48L2h0bWw+JyAjIHd0ZiBva2Mgd2l0aCB0aGUgd2VpcmRseSBlbmNvZGVkICI8L3NjcicgKyAnaXB0PiciLXR5cGUgc3RhdGVtZW50cyBpbiB5b3VyIGphdmFzY3JpcHQKICAgICAgICByZXR1cm4oQmVhdXRpZnVsU291cChmKSkKICAgIAogICAgZGVmIF9yZXF1ZXN0X3JlYWRfc2xlZXAoc2VsZiwgdXJsKToKICAgICAgICBmID0gdXJsbGliMi51cmxvcGVuKHVybCkucmVhZCgpCiAgICAgICAgdGltZS5zbGVlcChzZWxmLnNsZWVwX2R1cmF0aW9uKQogICAgICAgIHJldHVybiBmCiAgICAKICAgIGRlZiBxdWV1ZV90aHJlYWRzKHNlbGYpOgogICAgICAgIHNlbGYudGhyZWFkX3VybHMgPSBbXQogICAgICAgIGZvciBmb2xkZXIgaW4gcmFuZ2UoMSw0KTogIyBJbmJveCwgU2VudCwgU21pbGVzCiAgICAgICAgICAgIHBhZ2UgPSAwOwogICAgICAgICAgICB3aGlsZSAoVHJ1ZSk6CiAgICAgICAgICAgICAgICBwcmludCAicXVldWluZyBmb2xkZXIgJXMsIHBhZ2UgJXMiICUgKGZvbGRlciwgcGFnZSkKICAgICAgICAgICAgICAgIGYgPSBzZWxmLl9yZXF1ZXN0X3JlYWRfc2xlZXAoc2VsZi5iYXNlX3VybCArICcvbWVzc2FnZXM/Zm9sZGVyPScgKyBzdHIoZm9sZGVyKSArICcmbG93PScgKyBzdHIoKHBhZ2UgKiAzMCkgKyAxKSkKICAgICAgICAgICAgICAgIHNvdXAgPSBzZWxmLl9zYWZlbHlfc291cGlmeShmKQogICAgICAgICAgICAgICAgZW5kX3BhdHRlcm4gPSByZS5jb21waWxlKCcmZm9sZGVyPVxkXCc7JykKICAgICAgICAgICAgICAgIHRocmVhZHMgPSBbCiAgICAgICAgICAgICAgICAgICAgcmUuc3ViKGVuZF9wYXR0ZXJuLCAnJywgbGkuZmluZCgncCcpWydvbmNsaWNrJ10ucGFydGl0aW9uKCJ3aW5kb3cubG9jYXRpb249JyIpWzJdKQogICAgICAgICAgICAgICAgICAgIGZvciBsaSBpbiBzb3VwLmZpbmQoJ3VsJywgeydpZCc6ICdtZXNzYWdlcyd9KS5maW5kQWxsKCdsaScpCiAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICBpZiBsZW4odGhyZWFkcykgPT0gMDogICMgYnJlYWsgb3V0IG9mIHRoZSBpbmZpbml0ZSBsb29wIHdoZW4gd2UgcmVhY2ggdGhlIGVuZCBhbmQgdGhlcmUgYXJlIG5vIHRocmVhZHMgb24gdGhlIHBhZ2UKICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICBzZWxmLnRocmVhZF91cmxzLmV4dGVuZCh0aHJlYWRzKQogICAgICAgICAgICAgICAgICAgIHBhZ2UgPSBwYWdlICsgMQogICAgCiAgICBkZWYgZGVkdXBlX3RocmVhZHMoc2VsZik6CiAgICAgICAgcHJpbnQgInJlbW92aW5nIGR1cGxpY2F0ZSBVUkxzIgogICAgICAgIHNlbGYudGhyZWFkX3VybHMgPSBsaXN0KHNldChzZWxmLnRocmVhZF91cmxzKSkKICAgIAogICAgZGVmIGZldGNoX3RocmVhZHMoc2VsZik6CiAgICAgICAgc2VsZi5tZXNzYWdlcyA9IFtdCiAgICAgICAgZm9yIHRocmVhZF91cmwgaW4gc2VsZi50aHJlYWRfdXJsczoKICAgICAgICAgICAgc2VsZi5tZXNzYWdlcy5leHRlbmQoc2VsZi5fZmV0Y2hfdGhyZWFkKHRocmVhZF91cmwpKQoKICAgIGRlZiBzdHJwdGltZShzZWxmLCBzdHJpbmcsIGZvcm1hdD0nJWIgJWQsICVZICZuZGFzaDsgJUk6JU0lcCcpOgogICAgICAgIHJldHVybiBkYXRldGltZS5zdHJwdGltZShzdHJpbmcuc3RyaXAoKSwgZm9ybWF0KQogICAgICAgICAgICAgICAgCiAgICBkZWYgd3JpdGVfbWVzc2FnZXMoc2VsZiwgZmlsZV9uYW1lKToKICAgICAgICBzZWxmLm1lc3NhZ2VzLnNvcnQoa2V5ID0gbGFtYmRhIG1lc3NhZ2U6IG1lc3NhZ2UudGltZXN0YW1wKSAgIyBzb3J0IGJ5IHRpbWUKICAgICAgICBmID0gY29kZWNzLm9wZW4oZmlsZV9uYW1lLCBlbmNvZGluZz0ndXRmLTgnLCBtb2RlPSd3JykgICMgdWdoLCBvdGhlcndpc2UgaSB0aGluayBpdCB3aWxsIHRyeSB0byB3cml0ZSBhc2NpaQogICAgICAgIGZvciBtZXNzYWdlIGluIHNlbGYubWVzc2FnZXM6CiAgICAgICAgICAgIHByaW50ICJ3cml0aW5nIG1lc3NhZ2UgZm9yIHRocmVhZDogIiArIG1lc3NhZ2UudGhyZWFkX3VybAogICAgICAgICAgICBmLndyaXRlKHVuaWNvZGUobWVzc2FnZSkpCiAgICAgICAgZi5jbG9zZSgpCiAgICAKICAgIGRlZiBfZmV0Y2hfdGhyZWFkKHNlbGYsIHRocmVhZF91cmwpOgogICAgICAgIG1lc3NhZ2VfbGlzdCA9IFtdCiAgICAgICAgcHJpbnQgImZldGNoaW5nIHRocmVhZDogIiArIHNlbGYuYmFzZV91cmwgKyB0aHJlYWRfdXJsCiAgICAgICAgZiA9IHNlbGYuX3JlcXVlc3RfcmVhZF9zbGVlcChzZWxmLmJhc2VfdXJsICsgdGhyZWFkX3VybCkKICAgICAgICBzb3VwID0gc2VsZi5fc2FmZWx5X3NvdXBpZnkoZikKICAgICAgICB0cnk6CiAgICAgICAgICAgIHN1YmplY3QgPSBzb3VwLmZpbmQoJ3N0cm9uZycsIHsnaWQnOiAnbWVzc2FnZV9oZWFkaW5nJ30pLmNvbnRlbnRzWzBdCiAgICAgICAgZXhjZXB0IEF0dHJpYnV0ZUVycm9yOgogICAgICAgICAgICBzdWJqZWN0ID0gJycKICAgICAgICB0cnk6CiAgICAgICAgICAgIG90aGVyX3VzZXIgPSBzb3VwLmZpbmQoJ2EnLCB7J2NsYXNzJzogJ2J1ZGR5bmFtZSd9KS5jb250ZW50c1swXQogICAgICAgIGV4Y2VwdCBBdHRyaWJ1dGVFcnJvcjoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgIyBtZXNzYWdlcyBmcm9tIE9rQ3VwaWQgaXRzZWxmIGFyZSBhIHNwZWNpYWwgY2FzZQogICAgICAgICAgICAgICAgb3RoZXJfdXNlciA9IHNvdXAuZmluZCgndWwnLCB7J2lkJzogJ3RocmVhZCd9KS5maW5kKCdwJywgJ3NpZ25hdHVyZScpLmNvbnRlbnRzWzBdLnBhcnRpdGlvbignTWVzc2FnZSBmcm9tICcpWzJdCiAgICAgICAgICAgIGV4Y2VwdCBBdHRyaWJ1dGVFcnJvcjoKICAgICAgICAgICAgICAgIG90aGVyX3VzZXIgPSAnJwogICAgICAgIGZvciBtZXNzYWdlIGluIHNvdXAuZmluZCgndWwnLCB7J2lkJzogJ3RocmVhZCd9KS5maW5kQWxsKCdsaScpOgogICAgICAgICAgICBib2R5X2NvbnRlbnRzID0gbWVzc2FnZS5maW5kKCdkaXYnLCAnbWVzc2FnZV9ib2R5JykKICAgICAgICAgICAgaWYgYm9keV9jb250ZW50czoKICAgICAgICAgICAgICAgIGJvZHkgPSBzZWxmLl9zdHJpcF90YWdzKGJvZHlfY29udGVudHMucmVuZGVyQ29udGVudHMoKSkucmVuZGVyQ29udGVudHMoKS5zdHJpcCgpCiAgICAgICAgICAgICAgICBmb3IgcGFpciBpbiBbICAgKCc8YnIgLz4nLCAnXG4nKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKCcmYW1wOycsICcmJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKCcmbHQ7JywgJzwnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoJyZndDsnLCAnPicpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICgnJnF1b3Q7JywgJyInKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoJyYjMzk7JywgIiciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoJyZtZGFzaDsnLCAi4oCUIildOgogICAgICAgICAgICAgICAgICAgIGJvZHkgPSBib2R5LnJlcGxhY2UocGFpclswXSwgcGFpclsxXSkKICAgICAgICAgICAgICAgIHRpbWVzdGFtcCA9IG1lc3NhZ2UuZmluZCgnc3BhbicsJ3RpbWVzdGFtcCcpCiAgICAgICAgICAgICAgICBpZiB0aW1lc3RhbXAuZGVjb2RlQ29udGVudHMgYW5kIHRpbWVzdGFtcC5kZWNvZGVDb250ZW50cygpOgogICAgICAgICAgICAgICAgICAgIHRpbWVzdGFtcCA9IHNlbGYuc3RycHRpbWUodGltZXN0YW1wLmRlY29kZUNvbnRlbnRzKCkuc3RyaXAoKSkKICAgICAgICAgICAgICAgIGVsc2U6IAogICAgICAgICAgICAgICAgICAgIHRpbWVzdGFtcCA9IHNlbGYuc3RycHRpbWUodGltZXN0YW1wLnRleHQuc3RyaXAoKSkKICAgICAgICAgICAgICAgIHNlbmRlciA9IG90aGVyX3VzZXIKICAgICAgICAgICAgICAgIHJlY2lwaWVudCA9IHNlbGYudXNlcm5hbWUKICAgICAgICAgICAgICAgIGlmIG1lc3NhZ2VbJ2NsYXNzJ10ucmVwbGFjZSgncHJldmlldycsICcnKS5zdHJpcCgpID09ICdmcm9tX21lJzoKICAgICAgICAgICAgICAgICAgICByZWNpcGllbnQgPSBvdGhlcl91c2VyCiAgICAgICAgICAgICAgICAgICAgc2VuZGVyID0gc2VsZi51c2VybmFtZQogICAgICAgICAgICAgICAgbWVzc2FnZV9saXN0LmFwcGVuZChNZXNzYWdlKHNlbGYuYmFzZV91cmwgKyB0aHJlYWRfdXJsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bmljb2RlKHNlbmRlciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5pY29kZShyZWNpcGllbnQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVzdGFtcCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bmljb2RlKHN1YmplY3QpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvZHkuZGVjb2RlKCd1dGYtOCcpKSkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGNvbnRpbnVlICAjIGNvbnRyb2wgZWxlbWVudHMgYXJlIGFsc28gPGxpPidzIGluIHRoZWlyIGh0bWwsIHNvIG5vbi1tZXNzYWdlcwogICAgICAgIHJldHVybiBtZXNzYWdlX2xpc3QKICAgIAogICAgIyBodHRwOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzE3NjU4NDgvcmVtb3ZlLWEtdGFnLXVzaW5nLWJlYXV0aWZ1bHNvdXAtYnV0LWtlZXAtaXRzLWNvbnRlbnRzLzE3NjYwMDIjMTc2NjAwMgogICAgZGVmIF9zdHJpcF90YWdzKHNlbGYsIGh0bWwsIGludmFsaWRfdGFncz1bJ2EnLCAnc3BhbicsICdzdHJvbmcnLCAnZGl2J10pOgogICAgICAgIHNvdXAgPSBCZWF1dGlmdWxTb3VwKGh0bWwpCiAgICAgICAgZm9yIHRhZyBpbiBzb3VwLmZpbmRBbGwoVHJ1ZSk6CiAgICAgICAgICAgIGlmIHRhZy5uYW1lIGluIGludmFsaWRfdGFnczoKICAgICAgICAgICAgICAgIHMgPSAiIgogICAgICAgICAgICAgICAgZm9yIGMgaW4gdGFnLmNvbnRlbnRzOgogICAgICAgICAgICAgICAgICAgIGlmIHR5cGUoYykgIT0gTmF2aWdhYmxlU3RyaW5nOgogICAgICAgICAgICAgICAgICAgICAgICBjID0gc2VsZi5fc3RyaXBfdGFncyh1bmljb2RlKGMpLCBpbnZhbGlkX3RhZ3MpCiAgICAgICAgICAgICAgICAgICAgICAgIHMgKz0gdW5pY29kZShjKS5zdHJpcCgpCiAgICAgICAgICAgICAgICAgICAgZWxzZTogICAgIAogICAgICAgICAgICAgICAgICAgICAgICBzICs9IHVuaWNvZGUoYykKICAgICAgICAgICAgICAgIHRhZy5yZXBsYWNlV2l0aChzKQogICAgICAgIHJldHVybiBzb3VwCgoKZGVmIG1haW4oKToKICAgIHBhcnNlciA9IE9wdGlvblBhcnNlcigpCiAgICBwYXJzZXIuYWRkX29wdGlvbigiLXUiLCAiLS11c2VybmFtZSIsIGRlc3Q9InVzZXJuYW1lIiwKICAgICAgICAgICAgICAgICAgICAgIGhlbHA9InlvdXIgT2tDdXBpZCB1c2VybmFtZSIpCiAgICBwYXJzZXIuYWRkX29wdGlvbigiLXAiLCAiLS1wYXNzd29yZCIsIGRlc3Q9InBhc3N3b3JkIiwKICAgICAgICAgICAgICAgICAgICAgIGhlbHA9InlvdXIgT2tDdXBpZCBwYXNzd29yZCIpCiAgICBwYXJzZXIuYWRkX29wdGlvbigiLWYiLCAiLS1maWxlbmFtZSIsIGRlc3Q9ImZpbGVuYW1lIiwKICAgICAgICAgICAgICAgICAgICBoZWxwPSJ0aGUgZmlsZSB0byB3aGljaCB5b3Ugd2FudCB0byB3cml0ZSB0aGUgZGF0YSIpCiAgICAob3B0aW9ucywgYXJncykgPSBwYXJzZXIucGFyc2VfYXJncygpCiAgICBpZiBub3Qgb3B0aW9ucy51c2VybmFtZToKICAgICAgICBwcmludCAiUGxlYXNlIHNwZWNpZnkgeW91ciBPa0N1cGlkIHVzZXJuYW1lIHdpdGggZWl0aGVyICctdScgb3IgJy0tdXNlcm5hbWUnIgogICAgaWYgbm90IG9wdGlvbnMucGFzc3dvcmQ6CiAgICAgICAgcHJpbnQgIlBsZWFzZSBzcGVjaWZ5IHlvdXIgT2tDdXBpZCBwYXNzd29yZCB3aXRoIGVpdGhlciAnLXAnIG9yICctLXBhc3N3b3JkJyIKICAgIGlmIG5vdCBvcHRpb25zLmZpbGVuYW1lOgogICAgICAgIHByaW50ICJQbGVhc2Ugc3BlY2lmeSB0aGUgZGVzdGluYXRpb24gZmlsZSB3aXRoIGVpdGhlciAnLWYnIG9yICctLWZpbGVuYW1lJyIKICAgIGlmIG9wdGlvbnMudXNlcm5hbWUgYW5kIG9wdGlvbnMucGFzc3dvcmQgYW5kIG9wdGlvbnMuZmlsZW5hbWU6CiAgICAgICAgYXJyb3dfZmV0Y2hlciA9IEFycm93RmV0Y2hlcihvcHRpb25zLnVzZXJuYW1lLCBvcHRpb25zLnBhc3N3b3JkKQogICAgICAgIGFycm93X2ZldGNoZXIucXVldWVfdGhyZWFkcygpCiAgICAgICAgYXJyb3dfZmV0Y2hlci5kZWR1cGVfdGhyZWFkcygpCiAgICAgICAgYXJyb3dfZmV0Y2hlci5mZXRjaF90aHJlYWRzKCkKICAgICAgICBhcnJvd19mZXRjaGVyLndyaXRlX21lc3NhZ2VzKG9wdGlvbnMuZmlsZW5hbWUpCgppZiBfX25hbWVfXyA9PSAnX19tYWluX18nOgogICAgbWFpbigpCiAgICA=