Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
218 lines (176 sloc) 7.45 KB
#!/usr/bin/env python
# encoding: utf-8
tootfiles - store binary data in a twitter stream
import sys
import getopt
import os
import math
import re
import zlib
import base64
import md5
import time
import urllib
from optparse import OptionParser
# External dependencies, see
import simplejson as json
from BeautifulSoup import BeautifulSoup
import twitter
class TootEncoder(object):
def __init__(self, filename):
super(TootEncoder, self).__init__()
self.filename = filename
def _encode(self):
sourcefile = open(self.filename, 'rb')
rawdata =
md5hash =
# Encode and split the data
compressed_data = zlib.compress(rawdata,9)
data = base64.b64encode(compressed_data)
tootlist = self._segment(data)
# Header Information
header = "|Tootfile:'%s' MD5:'%s' Count:'%s'|" % (self.filename, md5hash, self.tootcount)
tootlist.append(header) # Insert the header
self.tootlist = tootlist
def _segment(self, data, n=140):
''' Given the encoded string, slice it into twitter ready array elements '''
self.tootcount = int(math.ceil(len(data)/float(n)))
slices = range(self.tootcount)
return [data[i*n:(i+1)*n] for i in slices]
def publish(self, username, password):
api = twitter.Api(username, password)
print "Publishing '%s' to %s's stream with %s segments..." % (self.filename, username, self.tootcount)
for toot in self.tootlist:
# Occasionally, Twitter will either throttle us or throw an error.
# For now, we'll handle all exceptions and simply retry. This should
# be improved in the future.
for retries in range(5):
#print "Attempting to publish '%s' with length %s" % (toot, len(toot))
status = api.PostUpdate(toot)
print '.',
if retries == 4:
raise Exception('Unable to post a segment. Quitting.')
# Let's wait a bit before trying again
print "Finished."
def __str__(self):
return "\n".join([toot for toot in self.tootlist])
class TootDecoder(object):
def __init__(self, headerid):
super(TootDecoder, self).__init__()
self.headerid = headerid
self.tootlist = []
def retrieve(self):
"""Retrieve toot data from Twitter"""
if self.headerid is not None:
def write(self, filename=None, overwrite=False):
if(self.md5hash == self.headerinfo['md5']):
if filename is None:
filename = self.headerinfo['filename']
if not overwrite and os.path.exists(filename):
raise Exception("File exists. Will not overwrite. Specify permission with overwrite set to true.")
f = open(filename,'wb')
def __str__(self):
return self.rawdata
def _grabheader(self):
"""Takes the headerid and retrieves the header text"""
raw = urllib.urlopen('' % self.headerid)
js = raw.readlines()
js_object = json.loads(js[0])
self.headerdata = js_object['text']
#print self.headerdata
self.username = js_object['user']['screen_name']
def _getinfo(self):
header_re = "\|Tootfile:'(?P<filename>.*?)' MD5:'(?P<md5>.*?)' Count:'(?P<count>\d+)'\|"
results = re.match(header_re, self.headerdata)
self.headerinfo = results.groupdict()
def _walk(self):
grabbedtoots = 0
url = ''
re_status_id = re.compile(r'.*/status/([0-9]*).*')
maxpages = int(math.ceil(int(self.headerinfo['count'])/20.0))
for page in range(1,maxpages+1):
f = urllib.urlopen(url % (self.username, page))
soup = BeautifulSoup(
toots = soup.findAll('li', {'class': re.compile(r'.*\bstatus\b.*')})
if len(toots) == 0:
for toot in toots:
if grabbedtoots < int(self.headerinfo['count']):
m ='a', 'entry-date')['href'])
status_id = m.groups()[0]
if(int(status_id) < int(self.headerid)):
data = str(toot.find('span', 'entry-content').renderContents())
grabbedtoots += 1
# small delay between pages
def _decode(self):
"""Decodes the raw data from the Twitter stream"""
data = "".join(self.tootlist)
compressed_data = base64.b64decode(data)
self.rawdata = zlib.decompress(compressed_data)
self.md5hash =
def encode(filename, username=None, password=None):
if not os.path.exists(filename):
raise Exception("Specified input file does not exist")
t = TootEncoder(filename)
if username is not None and password is not None:
t.publish(username, password)
def decode(tootid):
"""Given a Twitter status ID, attempt to decode"""
t = TootDecoder(tootid)
print t
def main(argv=None):
usage = "usage: %prog [-d tootidtodecode] or [-e 'somefile' -u 'username' -p 'password']"
parser = OptionParser(usage=usage)
parser.add_option("-e", dest="filename", metavar="filename", default=None,
help="The name of the file you wish to encode")
parser.add_option("-u", dest="username", metavar="username", default=None,
help="A valid Twitter username")
parser.add_option("-p", dest="password", metavar="password", default=None,
help="The password to your twitter account")
parser.add_option("-d", dest="tootfileid", metavar="id", default=None,
help="Twitter ID of header to decode from")
(options, args) = parser.parse_args()
encoding = options.filename is not None
decoding = options.tootfileid is not None
havepass = options.password is not None
haveuser = options.password is not None
# This will catch cases where both encode and decode are selected and where neither is
if encoding is decoding:
parser.error("You must choose to either encode or decode. Use --help for more info.")
if decoding:
# Decode a file from the header with Twitter ID tootfileid
# and print to stdout
elif encoding and haveuser and havepass:
# Encode a file and publish to Twitter
encode(options.filename, options.username, options.password)
elif encoding:
# Encode a file and print to stdout
if __name__ == "__main__":