80 columns, spacing and line continuations.
[pyTivo/wgw.git] / mind.py
blobd478ab1dee3ad6dae847b154d84a24168c4df32f
1 import cookielib
2 import urllib2
3 import urllib
4 import time
5 import warnings
6 import config
7 import logging
9 try:
10 import xml.etree.ElementTree as ElementTree
11 except ImportError:
12 try:
13 import elementtree.ElementTree as ElementTree
14 except ImportError:
15 warnings.warn('Python 2.5 or higher or elementtree is ' +
16 'needed to use the TivoPush')
18 if 'ElementTree' not in locals():
20 class Mind:
21 def __init__(self, *arg, **karg):
22 raise Exception('Python 2.5 or higher or elementtree is ' +
23 'needed to use the TivoPush')
25 else:
27 class Mind:
28 def __init__(self, username, password):
29 self.__logger = logging.getLogger('pyTivo.mind')
30 self.__username = username
31 self.__password = password
33 cj = cookielib.CookieJar()
34 cp = urllib2.HTTPCookieProcessor(cj)
35 self.__opener = urllib2.build_opener(cp)
37 self.__login()
39 if not self.__pcBodySearch():
40 self.__pcBodyStore('pyTivo', True)
42 def pushVideo(self, tsn, url, description, duration, size,
43 title, subtitle):
44 # It looks like tivo only supports one pc per house
45 pc_body_id = self.__pcBodySearch()[0]
47 data = {
48 'bodyId': 'tsn:' + tsn,
49 'description': description,
50 'duration': duration,
51 'encodingType': 'mpeg2ProgramStream',
52 'partnerId': 'tivo:pt.3187',
53 'pcBodyId': pc_body_id,
54 'publishDate': time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()),
55 'size': size,
56 'source': 'file:/C%3A%2FDocuments%20and%20Settings%2F' +
57 'Stephanie%2FDesktop%2FVideo',
58 'state': 'complete',
59 'subtitle': subtitle,
60 'title': title,
61 'url': url
64 offer_id, content_id = self.__bodyOfferModify(data)
65 self.__subscribe(offer_id, content_id, tsn)
67 def getDownloadRequests(self):
68 NEEDED_VALUES = [
69 'bodyId',
70 'bodyOfferId',
71 'description',
72 'partnerId',
73 'pcBodyId',
74 'publishDate',
75 'source',
76 'state',
77 'subscriptionId',
78 'subtitle',
79 'title',
80 'url'
83 # It looks like tivo only supports one pc per house
84 pc_body_id = self.__pcBodySearch()[0]
86 requests = []
87 offer_list = self.__bodyOfferSchedule(pc_body_id)
89 for offer in offer_list.findall('bodyOffer'):
90 d = {}
91 if offer.findtext('state') != 'scheduled':
92 continue
94 for n in NEEDED_VALUES:
95 d[n] = offer.findtext(n)
96 requests.append(d)
98 return requests
100 def completeDownloadRequest(self, request):
101 request['encodingType'] = 'mpeg2ProgramStream'
102 request['state'] = 'complete'
103 request['type'] = 'bodyOfferModify'
104 request['updateDate'] = time.strftime('%Y-%m-%d %H:%M%S',
105 time.gmtime())
107 offer_id, content_id = self.__bodyOfferModify(request)
108 self.__subscribe(offer_id, content_id, request['bodyId'][4:])
111 def getXMPPLoginInfo(self):
112 # It looks like tivo only supports one pc per house
113 pc_body_id = self.__pcBodySearch()[0]
115 xml = self.__bodyXmppInfoGet(pc_body_id)
117 results = {}
118 results['server'] = xml.findtext('server')
119 results['port'] = int(xml.findtext('port'))
120 results['username'] = xml.findtext('xmppId')
122 for sendPresence in xml.findall('sendPresence'):
123 results.setdefault('presence_list',[]).append(sendPresence.text)
125 return results
127 def __login(self):
129 data = {
130 'cams_security_domain': 'tivocom',
131 'cams_login_config': 'http',
132 'cams_cb_username': self.__username,
133 'cams_cb_password': self.__password,
134 'cams_original_url': '/mind/mind7?type=infoGet'
137 r = urllib2.Request(
138 'https://mind.tivo.com:8181/mind/login',
139 urllib.urlencode(data)
141 try:
142 result = self.__opener.open(r)
143 except:
144 pass
146 self.__logger.debug('__login\n%s' % (data))
148 def __dict_request(self, data, req):
149 r = urllib2.Request(
150 'https://mind.tivo.com:8181/mind/mind7?type=' + req,
151 dictcode(data),
152 {'Content-Type': 'x-tivo/dict-binary'}
154 result = self.__opener.open(r)
156 xml = ElementTree.parse(result).find('.')
158 self.__logger.debug('%s\n%s\n\n%sg' % (req, data,
159 ElementTree.tostring(xml)))
160 return xml
162 def __bodyOfferModify(self, data):
163 """Create an offer"""
165 xml = self.__dict_request(data, 'bodyOfferModify&bodyId=' +
166 data['bodyId'])
168 if xml.findtext('state') != 'complete':
169 raise Exception(ElementTree.tostring(xml))
171 offer_id = xml.findtext('offerId')
172 content_id = offer_id.replace('of','ct')
174 return offer_id, content_id
177 def __subscribe(self, offer_id, content_id, tsn):
178 """Push the offer to the tivo"""
179 data = {
180 'bodyId': 'tsn:' + tsn,
181 'idSetSource': {
182 'contentId': content_id,
183 'offerId': offer_id,
184 'type': 'singleOfferSource'
186 'title': 'pcBodySubscription',
187 'uiType': 'cds'
190 return self.__dict_request(data, 'subscribe&bodyId=tsn:' + tsn)
192 def __bodyOfferSchedule(self, pc_body_id):
193 """Get pending stuff for this pc"""
195 data = {'pcBodyId': pc_body_id}
196 return self.__dict_request(data, 'bodyOfferSchedule')
198 def __pcBodySearch(self):
199 """Find PCS"""
201 xml = self.__dict_request({}, 'pcBodySearch')
203 return [id.text for id in xml.findall('pcBody/pcBodyId')]
205 def __collectionIdSearch(self, url):
206 """Find collection ids"""
208 xml = self.__dict_request({'url': url}, 'collectionIdSearch')
209 return xml.findtext('collectionId')
211 def __pcBodyStore(self, name, replace=False):
212 """Setup a new PC"""
214 data = {
215 'name': name,
216 'replaceExisting': str(replace).lower()
219 return self.__dict_request(data, 'pcBodyStore')
221 def __bodyXmppInfoGet(self, body_id):
223 return self.__dict_request({'bodyId': body_id},
224 'bodyXmppInfoGet&bodyId=' + body_id)
227 def dictcode(d):
228 """Helper to create x-tivo/dict-binary"""
229 output = []
231 keys = [str(k) for k in d]
232 keys.sort()
234 for k in keys:
235 v = d[k]
237 output.append( varint( len(k) ) )
238 output.append( k )
240 if isinstance(v, dict):
241 output.append( chr(2) )
242 output.append( dictcode(v) )
244 else:
245 v = unicode(v).encode('utf-8')
246 output.append( chr(1) )
247 output.append( varint( len(v) ) )
248 output.append( v )
250 output.append( chr(0) )
252 output.append( chr(0x80) )
254 return ''.join(output)
256 def varint(i):
257 output = []
258 while i > 0x7f:
259 output.append( chr(i & 0x7f) )
260 i >>= 7
261 output.append( chr(i | 0x80) )
262 return ''.join(output)
264 def getMind():
265 username = config.getTivoUsername()
266 password = config.getTivoPassword()
268 if not username or not password:
269 raise Exception("tivo_username and tivo_password required")
271 return Mind(username, password)