1 from enigma import HBBTV_USER_AGENT
3 from oauth2client.client import OAuth2Credentials
5 from twisted.internet import reactor
6 from twisted.internet.defer import succeed
7 from twisted.internet.ssl import ClientContextFactory
8 from twisted.web.client import Agent, readBody, PartialDownloadError
9 from twisted.web.http_headers import Headers
10 from twisted.web.iweb import IBodyProducer
11 from zope.interface import implements
15 class StringProducer(object):
16 implements(IBodyProducer)
18 def __init__(self, body):
20 self.length = len(body)
22 def startProducing(self, consumer):
23 consumer.write(self.body)
26 def pauseProducing(self):
29 def stopProducing(self):
32 class GoogleUserCode(object):
33 def __init__(self, data):
35 self.device_code = self._data['device_code']
36 self.interval = int(self._data['interval'])
37 self.user_code = self._data['user_code']
38 self.verification_url = data['verification_url']
39 self._expires_at = time() + int(self._data['expires_in'])
42 print "Expires in %s" % (self._expires_at - time())
43 return time() >= self._expires_at
46 return "[GoogleUserCode] %s" % (self._data,)
48 class WebClientContextFactory(ClientContextFactory):
49 def getContext(self, hostname, port):
50 return ClientContextFactory.getContext(self)
52 class YoutubeAuth(object):
53 AUTH_SCOPE_YT = "https://www.googleapis.com/auth/youtube"
54 AUTH_SCOPE_YT_RO = "https://www.googleapis.com/auth/youtube.readonly"
55 AUTH_SCOPE_YT_FORCE_SSL = "https://www.googleapis.com/auth/youtube.force-ssl"
57 AUTH_REQUEST_URI = "https://accounts.google.com/o/oauth2/device/code"
58 AUTH_RESPONSE_URI = "https://accounts.google.com/o/oauth2/token"
59 GRANT_TYPE_DEVICE_AUTH = "http://oauth.net/grant_type/device/1.0"
60 GRANT_TYPE_REFRESH_CREDENTIALS = "refresh_token"
62 CLIENT_ID = "59698529433-7n49jvm74m8mfkpjp2i1s2l18vsj35fu.apps.googleusercontent.com"
63 CLIENT_SECRET = "QaAAkmuTmYDo35vZt8aQ093N"
65 ERROR_AUTH_REQUEST = 0
66 ERROR_AUTH_REQUEST_PARSE = 1
67 ERROR_CREDENTIALS_REQUEST = 2
68 ERROR_CREDENTIALS_REQUEST_PARSE = 3
69 ERROR_CREDENTIALS_REQUEST_EXPIRED = 4
70 ERROR_CREDENTIALS_UNKOWN = 5
72 USER_AGENT = HBBTV_USER_AGENT
74 def __init__(self, scope=AUTH_SCOPE_YT_RO):
75 self._auth_scope = scope
76 self._agent = Agent(reactor, WebClientContextFactory())
77 self.onUserCodeReady = []
78 self.onCredentialsReady = []
80 self._user_code = None
81 self._requestDeferred = None
82 self._responseDeferred = None
85 if self._requestDeferred:
86 self._requestDeferred.cancel()
87 if self._responseDeferred:
88 self._responseDeferred.cancel()
90 def startAuthFlow(self):
91 d = self._agent.request(
93 self.AUTH_REQUEST_URI,
95 'User-Agent' : [self.USER_AGENT],
96 'Content-Type' : ["application/x-www-form-urlencoded"],
98 StringProducer("client_id=%s&scope=%s" % (self.CLIENT_ID, self._auth_scope))
100 d.addCallbacks(self._onRequestResponse, self._onRequestError)
101 self._requestDeferred = d
104 def _onError(self, type, errorText=""):
105 print "ERROR! %s, %s" % (type, errorText)
106 self._requestDeferred = None
107 for fnc in self.onError:
110 def _onRequestResponse(self, response):
111 self._requestDeferred = None
112 readBody(response).addCallback(self._onRequestBodyReady).addErrback(self._onRequestBodyError)
114 def _onRequestError(self, error):
115 self._requestDeferred = None
116 self._onError(self.ERROR_AUTH_REQUEST, str(error))
118 def _onRequestBodyReady(self, body):
119 self._parseAuthRequestResponse(body)
120 self._pollForResult()
122 def _onRequestBodyError(self, failure):
123 if isinstance(failure.value, PartialDownloadError):
124 self._onRequestBodyReady(failure.value.response)
126 self._onError(self.ERROR_AUTH_REQUEST, str(failure))
128 def _parseAuthRequestResponse(self, body):
129 response = json.loads(body)
130 self._user_code = GoogleUserCode(response)
131 for fnc in self.onUserCodeReady:
134 def _pollForResult(self):
135 if self._user_code.expired():
136 self._onError(self.ERROR_CREDENTIALS_REQUEST_EXPIRED)
138 d = self._agent.request(
140 self.AUTH_RESPONSE_URI,
142 'User-Agent' : [self.USER_AGENT],
143 'Content-Type' : ["application/x-www-form-urlencoded"],
145 StringProducer("client_id=%s&client_secret=%s&code=%s&grant_type=%s" % (self.CLIENT_ID, self.CLIENT_SECRET, str(self._user_code.device_code), self.GRANT_TYPE_DEVICE_AUTH))
147 d.addCallbacks(self._onCredentialsPollResponse, self._onCredentialsPollError)
148 self._responseDeferred = d
151 def _onCredentialsPollResponse(self, response):
152 self._responseDeferred = None
153 readBody(response).addCallback(self._onCredentialsPollBodyReady).addErrback(self._onCredentialsPollBodyError)
155 def _onCredentialsPollError(self, error):
156 self._responseDeferred = None
157 self._onError(self.ERROR_CREDENTIALS_REQUEST, str(error))
159 def _onCredentialsPollBodyReady(self, body):
161 result = json.loads(body)
162 error = result.get("error", None)
164 if error == "authorization_pending":
165 print "not ready, yet"
166 reactor.callLater(self._user_code.interval, self._pollForResult)
167 elif error == "slow_down":
168 print "too fast, slowing down"
169 self._device_code.interval = self._device_code.interval * 2
170 elif error == "expired_token":
171 self._onError(self.ERROR_CREDENTIALS_REQUEST_EXPIRED)
174 self._onError(self.ERROR_CREDENTIALS_REQUEST_PARSE, error)
175 elif "access_token" in result:
176 access_token = result.get("access_token")
177 refresh_token = result.get("refresh_token")
178 token_expiry = str( int(time()) + int(result.get("expires_in")) )
179 self._credentials = OAuth2Credentials(access_token, self.CLIENT_ID, self.CLIENT_SECRET, refresh_token, token_expiry, self.AUTH_REQUEST_URI, self.USER_AGENT)
180 for fnc in self.onCredentialsReady:
181 fnc(self._credentials)
183 self._onError(self.ERROR_CREDENTIALS_REQUEST_PARSE, error)
185 def _onCredentialsPollBodyError(self, failure):
186 if isinstance(failure.value, PartialDownloadError):
187 self._onCredentialsPollBodyReady(failure.value.response)
189 self._onError(self.ERROR_CREDENTIALS_REQUEST_PARSE, str(failure))
191 if __name__ == "__main__":
192 def userCodeReady(userCode):
193 print "%s => %s" % (userCode.verification_url, userCode.user_code)
195 def credentialsReady(credentials):
196 print "CREDENTIALS READY: %s" % (credentials.to_json(),)
199 yta.onUserCodeReady.append(userCodeReady)
200 yta.onCredentialsReady.append(credentialsReady)