Simpler/cleaner.
[pyTivo/wgw.git] / mind.py
blobcbff208895f82117b4105a5d01231ea8150b102b
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 needed to use the TivoPush')
17 if 'ElementTree' not in locals():
19 class Mind:
20 def __init__(self, *arg, **karg):
21 raise Exception('Python 2.5 or higher or elementtree is needed to use the TivoPush')
23 else:
25 class Mind:
26 def __init__(self, username, password):
27 self.__logger = logging.getLogger('pyTivo.mind')
28 self.__username = username
29 self.__password = password
31 self.__cj = cookielib.CookieJar()
32 self.__opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.__cj))
34 self.__login()
36 if not self.__pcBodySearch():
37 self.__pcBodyStore('pyTivo', True)
39 def pushVideo(self, tsn, url, description, duration, size, title, subtitle):
40 # It looks like tivo only supports one pc per house
41 pc_body_id = self.__pcBodySearch()[0]
43 data = {
44 'bodyId': 'tsn:' + tsn,
45 'description': description,
46 'duration': duration,
47 'encodingType': 'mpeg2ProgramStream',
48 'partnerId': 'tivo:pt.3187',
49 'pcBodyId': pc_body_id,
50 'publishDate': time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()),
51 'size': size,
52 'source': 'file:/C%3A%2FDocuments%20and%20Settings%2FStephanie%2FDesktop%2FVideo',
53 'state': 'complete',
54 'subtitle': subtitle,
55 'title': title,
56 'url': url
59 offer_id, content_id = self.__bodyOfferModify(data)
60 self.__subscribe(offer_id, content_id, tsn)
62 def getDownloadRequests(self):
63 NEEDED_VALUES = [
64 'bodyId',
65 'bodyOfferId',
66 'description',
67 'partnerId',
68 'pcBodyId',
69 'publishDate',
70 'source',
71 'state',
72 'subscriptionId',
73 'subtitle',
74 'title',
75 'url'
78 # It looks like tivo only supports one pc per house
79 pc_body_id = self.__pcBodySearch()[0]
81 requests = []
82 offer_list = self.__bodyOfferSchedule(pc_body_id)
84 for offer in offer_list.findall('bodyOffer'):
85 d = {}
86 if offer.findtext('state') != 'scheduled':
87 continue
89 for n in NEEDED_VALUES:
90 d[n] = offer.findtext(n)
91 requests.append(d)
93 return requests
95 def completeDownloadRequest(self, request):
96 request['encodingType'] = 'mpeg2ProgramStream'
97 request['state'] = 'complete'
98 request['type'] = 'bodyOfferModify'
99 request['updateDate'] = time.strftime('%Y-%m-%d %H:%M%S', time.gmtime())
101 offer_id, content_id = self.__bodyOfferModify(request)
102 self.__subscribe(offer_id, content_id, request['bodyId'][4:])
105 def getXMPPLoginInfo(self):
106 # It looks like tivo only supports one pc per house
107 pc_body_id = self.__pcBodySearch()[0]
109 xml = self.__bodyXmppInfoGet(pc_body_id)
111 results = {}
112 results['server'] = xml.findtext('server')
113 results['port'] = int(xml.findtext('port'))
114 results['username'] = xml.findtext('xmppId')
116 for sendPresence in xml.findall('sendPresence'):
117 results.setdefault('presence_list', []).append(sendPresence.text)
119 return results
121 def __login(self):
123 data = {
124 'cams_security_domain': 'tivocom',
125 'cams_login_config': 'http',
126 'cams_cb_username': self.__username,
127 'cams_cb_password': self.__password,
128 'cams_original_url': '/mind/mind7?type=infoGet'
131 r = urllib2.Request(
132 'https://mind.tivo.com:8181/mind/login',
133 urllib.urlencode(data)
135 try:
136 result = self.__opener.open(r)
137 except:
138 pass
140 self.__logger.debug('__login\n%s' % (data))
142 def __dict_request(self, data, req):
143 r = urllib2.Request(
144 'https://mind.tivo.com:8181/mind/mind7?type=' + req,
145 dictcode(data),
146 {'Content-Type': 'x-tivo/dict-binary'}
148 result = self.__opener.open(r)
150 xml = ElementTree.parse(result).find('.')
152 self.__logger.debug('%s\n%s\n\n%sg' % (req, data,
153 ElementTree.tostring(xml)))
154 return xml
156 def __bodyOfferModify(self, data):
157 """Create an offer"""
159 xml = self.__dict_request(data, 'bodyOfferModify&bodyId=' +
160 data['bodyId'])
162 if xml.findtext('state') != 'complete':
163 raise Exception(ElementTree.tostring(xml))
165 offer_id = xml.findtext('offerId')
166 content_id = offer_id.replace('of','ct')
168 return offer_id, content_id
171 def __subscribe(self, offer_id, content_id, tsn):
172 """Push the offer to the tivo"""
173 data = {
174 'bodyId': 'tsn:' + tsn,
175 'idSetSource': {
176 'contentId': content_id,
177 'offerId': offer_id,
178 'type': 'singleOfferSource'
180 'title': 'pcBodySubscription',
181 'uiType': 'cds'
184 return self.__dict_request(data, 'subscribe&bodyId=tsn:' + tsn)
186 def __bodyOfferSchedule(self, pc_body_id):
187 """Get pending stuff for this pc"""
189 data = {'pcBodyId': pc_body_id}
190 return self.__dict_request(data, 'bodyOfferSchedule')
192 def __pcBodySearch(self):
193 """Find PCS"""
195 xml = self.__dict_request({}, 'pcBodySearch')
197 return [id.text for id in xml.findall('pcBody/pcBodyId')]
199 def __collectionIdSearch(self, url):
200 """Find collection ids"""
202 xml = self.__dict_request({'url': url}, 'collectionIdSearch')
203 return xml.findtext('collectionId')
205 def __pcBodyStore(self, name, replace=False):
206 """Setup a new PC"""
208 data = {
209 'name': name,
210 'replaceExisting': str(replace).lower()
213 return self.__dict_request(data, 'pcBodyStore')
215 def __bodyXmppInfoGet(self, body_id):
217 return self.__dict_request({'bodyId': body_id},
218 'bodyXmppInfoGet&bodyId=' + body_id)
221 def dictcode(d):
222 """Helper to create x-tivo/dict-binary"""
223 output = []
225 keys = [str(k) for k in d]
226 keys.sort()
228 for k in keys:
229 v = d[k]
231 output.append( varint( len(k) ) )
232 output.append( k )
234 if isinstance(v, dict):
235 output.append( chr(2) )
236 output.append( dictcode(v) )
238 else:
239 v = unicode(v).encode('utf-8')
240 output.append( chr(1) )
241 output.append( varint( len(v) ) )
242 output.append( v )
244 output.append( chr(0) )
246 output.append( chr(0x80) )
248 return ''.join(output)
250 def varint(i):
251 output = []
252 while i > 0x7f:
253 output.append( chr(i & 0x7f) )
254 i >>= 7
255 output.append( chr(i | 0x80) )
256 return ''.join(output)
258 def getMind():
259 username = config.getTivoUsername()
260 password = config.getTivoPassword()
262 if not username or not password:
263 raise Exception("tivo_username and tivo_password required")
265 return Mind(username, password)