These new sections should be documented in pyTivo.conf.dist (are there
[pyTivo/wmcbrine.git] / mind.py
blob81b2f56ef7947f95d74e209196e8a99b4869eef8
1 import cookielib
2 import logging
3 import time
4 import urllib2
5 import urllib
6 import warnings
8 import config
10 try:
11 import xml.etree.ElementTree as ElementTree
12 except ImportError:
13 try:
14 import elementtree.ElementTree as ElementTree
15 except ImportError:
16 warnings.warn('Python 2.5 or higher or elementtree is ' +
17 'needed to use the TivoPush')
19 if 'ElementTree' not in locals():
21 class Mind:
22 def __init__(self, *arg, **karg):
23 raise Exception('Python 2.5 or higher or elementtree is ' +
24 'needed to use the TivoPush')
26 else:
28 class Mind:
29 def __init__(self, username, password):
30 self.__logger = logging.getLogger('pyTivo.mind')
31 self.__username = username
32 self.__password = password
34 cj = cookielib.CookieJar()
35 cp = urllib2.HTTPCookieProcessor(cj)
36 self.__opener = urllib2.build_opener(cp)
38 self.__login()
40 if not self.__pcBodySearch():
41 self.__pcBodyStore('pyTivo', True)
43 def pushVideo(self, tsn, url, description, duration, size,
44 title, subtitle):
45 # It looks like tivo only supports one pc per house
46 pc_body_id = self.__pcBodySearch()[0]
48 data = {
49 'bodyId': 'tsn:' + tsn,
50 'description': description,
51 'duration': duration,
52 'encodingType': 'mpeg2ProgramStream',
53 'partnerId': 'tivo:pt.3187',
54 'pcBodyId': pc_body_id,
55 'publishDate': time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()),
56 'size': size,
57 'source': ('file:/C%3A%2FDocuments%20and%20Settings%2F' +
58 'Stephanie%2FDesktop%2FVideo'),
59 'state': 'complete',
60 'subtitle': subtitle,
61 'title': title,
62 'url': url
65 offer_id, content_id = self.__bodyOfferModify(data)
66 self.__subscribe(offer_id, content_id, tsn)
68 def getDownloadRequests(self):
69 NEEDED_VALUES = [
70 'bodyId',
71 'bodyOfferId',
72 'description',
73 'partnerId',
74 'pcBodyId',
75 'publishDate',
76 'source',
77 'state',
78 'subscriptionId',
79 'subtitle',
80 'title',
81 'url'
84 # It looks like tivo only supports one pc per house
85 pc_body_id = self.__pcBodySearch()[0]
87 requests = []
88 offer_list = self.__bodyOfferSchedule(pc_body_id)
90 for offer in offer_list.findall('bodyOffer'):
91 d = {}
92 if offer.findtext('state') != 'scheduled':
93 continue
95 for n in NEEDED_VALUES:
96 d[n] = offer.findtext(n)
97 requests.append(d)
99 return requests
101 def completeDownloadRequest(self, request):
102 request['encodingType'] = 'mpeg2ProgramStream'
103 request['state'] = 'complete'
104 request['type'] = 'bodyOfferModify'
105 request['updateDate'] = time.strftime('%Y-%m-%d %H:%M%S',
106 time.gmtime())
108 offer_id, content_id = self.__bodyOfferModify(request)
109 self.__subscribe(offer_id, content_id, request['bodyId'][4:])
112 def getXMPPLoginInfo(self):
113 # It looks like tivo only supports one pc per house
114 pc_body_id = self.__pcBodySearch()[0]
116 xml = self.__bodyXmppInfoGet(pc_body_id)
118 results = {}
119 results['server'] = xml.findtext('server')
120 results['port'] = int(xml.findtext('port'))
121 results['username'] = xml.findtext('xmppId')
123 for sendPresence in xml.findall('sendPresence'):
124 results.setdefault('presence_list',[]).append(sendPresence.text)
126 return results
128 def __login(self):
130 data = {
131 'cams_security_domain': 'tivocom',
132 'cams_login_config': 'http',
133 'cams_cb_username': self.__username,
134 'cams_cb_password': self.__password,
135 'cams_original_url': '/mind/mind7?type=infoGet'
138 r = urllib2.Request(
139 'https://mind.tivo.com:8181/mind/login',
140 urllib.urlencode(data)
142 try:
143 result = self.__opener.open(r)
144 except:
145 pass
147 self.__logger.debug('__login\n%s' % (data))
149 def __dict_request(self, data, req):
150 r = urllib2.Request(
151 'https://mind.tivo.com:8181/mind/mind7?type=' + req,
152 dictcode(data),
153 {'Content-Type': 'x-tivo/dict-binary'}
155 result = self.__opener.open(r)
157 xml = ElementTree.parse(result).find('.')
159 self.__logger.debug('%s\n%s\n\n%sg' % (req, data,
160 ElementTree.tostring(xml)))
161 return xml
163 def __bodyOfferModify(self, data):
164 """Create an offer"""
166 xml = self.__dict_request(data, 'bodyOfferModify&bodyId=' +
167 data['bodyId'])
169 if xml.findtext('state') != 'complete':
170 raise Exception(ElementTree.tostring(xml))
172 offer_id = xml.findtext('offerId')
173 content_id = offer_id.replace('of','ct')
175 return offer_id, content_id
178 def __subscribe(self, offer_id, content_id, tsn):
179 """Push the offer to the tivo"""
180 data = {
181 'bodyId': 'tsn:' + tsn,
182 'idSetSource': {
183 'contentId': content_id,
184 'offerId': offer_id,
185 'type': 'singleOfferSource'
187 'title': 'pcBodySubscription',
188 'uiType': 'cds'
191 return self.__dict_request(data, 'subscribe&bodyId=tsn:' + tsn)
193 def __bodyOfferSchedule(self, pc_body_id):
194 """Get pending stuff for this pc"""
196 data = {'pcBodyId': pc_body_id}
197 return self.__dict_request(data, 'bodyOfferSchedule')
199 def __pcBodySearch(self):
200 """Find PCS"""
202 xml = self.__dict_request({}, 'pcBodySearch')
204 return [id.text for id in xml.findall('pcBody/pcBodyId')]
206 def __collectionIdSearch(self, url):
207 """Find collection ids"""
209 xml = self.__dict_request({'url': url}, 'collectionIdSearch')
210 return xml.findtext('collectionId')
212 def __pcBodyStore(self, name, replace=False):
213 """Setup a new PC"""
215 data = {
216 'name': name,
217 'replaceExisting': str(replace).lower()
220 return self.__dict_request(data, 'pcBodyStore')
222 def __bodyXmppInfoGet(self, body_id):
224 return self.__dict_request({'bodyId': body_id},
225 'bodyXmppInfoGet&bodyId=' + body_id)
228 def dictcode(d):
229 """Helper to create x-tivo/dict-binary"""
230 output = []
232 keys = [str(k) for k in d]
233 keys.sort()
235 for k in keys:
236 v = d[k]
238 output.append( varint( len(k) ) )
239 output.append( k )
241 if isinstance(v, dict):
242 output.append( chr(2) )
243 output.append( dictcode(v) )
245 else:
246 v = unicode(v).encode('utf-8')
247 output.append( chr(1) )
248 output.append( varint( len(v) ) )
249 output.append( v )
251 output.append( chr(0) )
253 output.append( chr(0x80) )
255 return ''.join(output)
257 def varint(i):
258 output = []
259 while i > 0x7f:
260 output.append( chr(i & 0x7f) )
261 i >>= 7
262 output.append( chr(i | 0x80) )
263 return ''.join(output)
265 def getMind():
266 username = config.getTivoUsername()
267 password = config.getTivoPassword()
269 if not username or not password:
270 raise Exception("tivo_username and tivo_password required")
272 return Mind(username, password)