def GetPublicUsers(baseUrl, deviceId=None): users = [] usersUrl = Url.append(baseUrl, constants.EMBY_PROTOCOL, constants.URL_USERS, constants.URL_USERS_PUBLIC) headers = Request.PrepareApiCallHeaders(deviceId=deviceId) resultObj = Request.GetAsJson(usersUrl, headers=headers) if not resultObj: return users for userObj in resultObj: # make sure the 'Name' and 'Id' properties are available if not constants.PROPERTY_USER_NAME in userObj or not constants.PROPERTY_USER_ID in userObj: continue # make sure the name and id properties are valid user = User(userObj[constants.PROPERTY_USER_NAME], userObj[constants.PROPERTY_USER_ID]) if not user.name or not user.id: continue # check if the user is disabled if constants.PROPERTY_USER_POLICY in userObj and \ constants.PROPERTY_USER_IS_DISABLED in userObj[constants.PROPERTY_USER_POLICY] and \ userObj[constants.PROPERTY_USER_POLICY][constants.PROPERTY_USER_IS_DISABLED]: continue users.append(user) return users
def Exchange(baseUrl, accessKey, userId, deviceId=None): if not baseUrl: raise ValueError('invalid baseUrl') if not accessKey: raise ValueError('invalid accessKey') if not userId: raise ValueError('invalid userId') exchangeUrl = server.Server.BuildConnectExchangeUrl(baseUrl, userId) headers = Request.PrepareApiCallHeaders(deviceId=deviceId) headers.update({ constants.EMBY_CONNECT_TOKEN_HEADER: accessKey, }) resultObj = Request.GetAsJson(exchangeUrl, headers=headers) if not resultObj or \ constants.PROPERTY_EMBY_CONNECT_EXCHANGE_LOCAL_USER_ID not in resultObj or \ constants.PROPERTY_EMBY_CONNECT_EXCHANGE_ACCESS_TOKEN not in resultObj: log('invalid response from {}: {}'.format(exchangeUrl, resultObj)) return None return EmbyConnect.AuthenticationResult( accessToken=resultObj.get(constants.PROPERTY_EMBY_CONNECT_EXCHANGE_ACCESS_TOKEN), userId=resultObj.get(constants.PROPERTY_EMBY_CONNECT_EXCHANGE_LOCAL_USER_ID) )
def Authenticate(baseUrl, authenticationMethod, username=None, userId=None, password=None, deviceId=None): if not password: raise ValueError('invalid password') # prepare the authentication URL authUrl = baseUrl authUrl = Url.append(authUrl, constants.URL_USERS) body = {constants.PROPERTY_USER_AUTHENTICATION_PASSWORD: password} if authenticationMethod == Authentication.Method.UserId: if not userId: raise ValueError('invalid userId') authUrl = Url.append(authUrl, userId, constants.URL_AUTHENTICATE) elif authenticationMethod == Authentication.Method.Username: if not username: raise ValueError('invalid username') authUrl = Url.append(authUrl, constants.URL_AUTHENTICATE_BY_NAME) body[constants.PROPERTY_USER_AUTHENTICATION_USERNAME] = username else: raise ValueError('invalid authenticationMethod') headers = Request.PrepareApiCallHeaders(deviceId=deviceId, userId=userId) headers['Content-Type'] = constants.EMBY_CONTENT_TYPE resultObj = Request.PostAsJson( authUrl, headers=headers, json=body, timeout=Authentication.REQUEST_TIMEOUT_S) if not resultObj: return Authentication.Result() if constants.PROPERTY_USER_AUTHENTICATION_ACCESS_TOKEN not in resultObj: return Authentication.Result() accessToken = \ resultObj[constants.PROPERTY_USER_AUTHENTICATION_ACCESS_TOKEN] if constants.PROPERTY_USER_AUTHENTICATION_USER not in resultObj: return Authentication.Result() userObj = resultObj[constants.PROPERTY_USER_AUTHENTICATION_USER] if constants.PROPERTY_USER_AUTHENTICATION_USER_ID not in userObj: return Authentication.Result() userId = userObj[constants.PROPERTY_USER_AUTHENTICATION_USER_ID] return Authentication.Result(result=True, accessToken=accessToken, userId=userId)
def Authenticate(username, password): if not username: raise ValueError('invalid username') if not password: raise ValueError('invalid password') url = Url.append(constants.URL_EMBY_CONNECT_BASE, constants.URL_EMBY_CONNECT_AUTHENTICATE) headers = EmbyConnect._getApplicationHeader() body = { constants.PROPERTY_EMBY_CONNECT_AUTHENTICATION_NAME_OR_EMAIL: username, constants.PROPERTY_EMBY_CONNECT_AUTHENTICATION_PASSWORD: hashlib.md5(password), # nosec } resultObj = Request.PostAsJson(url, headers=headers, json=body) if not resultObj or \ constants.PROPERTY_EMBY_CONNECT_AUTHENTICATION_ACCESS_TOKEN not in resultObj or \ constants.PROPERTY_EMBY_CONNECT_AUTHENTICATION_USER not in resultObj: log('invalid response from {}: {}'.format(url, resultObj)) return None userObj = resultObj.get(constants.PROPERTY_EMBY_CONNECT_AUTHENTICATION_USER) if constants.PROPERTY_EMBY_CONNECT_AUTHENTICATION_USER_ID not in userObj: log('invalid response from {}: {}'.format(url, resultObj)) return None return EmbyConnect.AuthenticationResult( accessToken=resultObj.get(constants.PROPERTY_EMBY_CONNECT_AUTHENTICATION_ACCESS_TOKEN), userId=userObj.get(constants.PROPERTY_EMBY_CONNECT_AUTHENTICATION_USER_ID) )
def _request(self, url, function, *args): headers = Request.PrepareApiCallHeaders(authToken=self.AccessToken(), userId=self.UserId(), deviceId=self._devideId) try: return function(url, headers, *args) except NotAuthenticatedError: # try to authenticate if not self._authenticate(force=True): return False # retrieve the headers again because the access token has changed headers = Request.PrepareApiCallHeaders(authToken=self.AccessToken(), userId=self.UserId(), deviceId=self._devideId) # execute the actual request again return function(url, headers, *args)
def checkLogin(self): if self.finished: return not self.expired url = Url.append(constants.URL_EMBY_CONNECT_BASE, constants.URL_EMBY_CONNECT_PIN) url = Url.addOptions(url, { constants.URL_QUERY_DEVICE_ID: self.deviceId, constants.URL_QUERY_PIN: self.pin, }) resultObj = Request.GetAsJson(url) if not resultObj or \ constants.PROPERTY_EMBY_CONNECT_PIN_IS_CONFIRMED not in resultObj or \ constants.PROPERTY_EMBY_CONNECT_PIN_IS_EXPIRED not in resultObj: log('failed to check status of PIN {} at {}: {}'.format(self.pin, url, resultObj), xbmc.LOGWARNING) self.finished = True self.expired = True return False self.finished = resultObj.get(constants.PROPERTY_EMBY_CONNECT_PIN_IS_CONFIRMED) self.expired = resultObj.get(constants.PROPERTY_EMBY_CONNECT_PIN_IS_EXPIRED) if self.expired: self.finished = True return self.finished
def exchange(self): if not self.pin: return None if not self.finished or self.expired: return None if self._authenticationResult: return self._authenticationResult url = Url.append(constants.URL_EMBY_CONNECT_BASE, constants.URL_EMBY_CONNECT_PIN, constants.URL_EMBY_CONNECT_PIN_AUTHENTICATE) body = { constants.URL_QUERY_DEVICE_ID: self.deviceId, constants.URL_QUERY_PIN: self.pin, } resultObj = Request.PostAsJson(url, json=body) if not resultObj or \ constants.PROPERTY_EMBY_CONNECT_PIN_USER_ID not in resultObj or \ constants.PROPERTY_EMBY_CONNECT_PIN_ACCESS_TOKEN not in resultObj: log('failed to authenticate with PIN {} at {}: {}'.format(self.pin, url, resultObj)) return None self._authenticationResult = EmbyConnect.AuthenticationResult( accessToken=resultObj.get(constants.PROPERTY_EMBY_CONNECT_PIN_ACCESS_TOKEN), userId=resultObj.get(constants.PROPERTY_EMBY_CONNECT_PIN_USER_ID)) return self._authenticationResult
def loadProviderSettings(handle, _): # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return settings = mediaProvider.getSettings() if not settings: log('cannot retrieve media provider settings', xbmc.LOGERROR) return # make sure we have a device identifier if not settings.getString(emby.constants.SETTING_PROVIDER_DEVICEID): settings.setString(emby.constants.SETTING_PROVIDER_DEVICEID, Request.GenerateDeviceId()) settings.registerActionCallback(emby.constants.SETTING_PROVIDER_LINK_EMBY_CONNECT, 'linkembyconnect') settings.registerActionCallback(emby.constants.SETTING_PROVIDER_TEST_AUTHENTICATION, 'testauthentication') settings.registerActionCallback(emby.constants.SETTING_PROVIDER_ADVANCED_RESET_DEVICE_ID, 'resetdeviceid') settings.registerActionCallback(emby.constants.SETTING_PROVIDER_ADVANCED_CHANGE_URL, 'changeurl') # register a setting options filler for the list of users settings.registerOptionsFillerCallback(emby.constants.SETTING_PROVIDER_USER, 'settingoptionsfillerusers') settings.setLoaded()
def linkEmbyConnect(handle, _): # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return # get the media provider settings providerSettings = mediaProvider.prepareSettings() if not providerSettings: return # make sure we have a valid device ID deviceId = providerSettings.getString(emby.constants.SETTING_PROVIDER_DEVICEID) if not deviceId: deviceId = Request.GenerateDeviceId() providerSettings.setString(emby.constants.SETTING_PROVIDER_DEVICEID, deviceId) embyConnect = linkToEmbyConnect(deviceId) if not embyConnect: return # make sure the configured Emby server is still accessible serverUrl = ProviderSettings.GetUrl(providerSettings) matchingServer = None serverId = Server.GetServerId(mediaProvider.getIdentifier()) # get all connected servers servers = EmbyConnect.GetServers(embyConnect.accessToken, embyConnect.userId) if not servers: log('no servers available for Emby Connect user id {}'.format(embyConnect.userId), xbmc.LOGWARNING) return for server in servers: if server.systemId == serverId: matchingServer = server break if not matchingServer: log('no Emby server matching {} found'.format(serverUrl), xbmc.LOGWARNING) xbmcgui.Dialog().ok(localise(32038), localise(32061)) return # change the settings providerSettings.setString(emby.constants.SETTING_PROVIDER_EMBY_CONNECT_USER_ID, embyConnect.userId) providerSettings.setString(emby.constants.SETTING_PROVIDER_EMBY_CONNECT_ACCESS_KEY, matchingServer.accessKey) success = False try: success = Server(mediaProvider).Authenticate(force=True) except: pass if success: xbmcgui.Dialog().ok(localise(32038), localise(32062)) log('successfully linked to Emby Connect server {} ({}) {}'.format(matchingServer.name, serverId, serverUrl)) else: xbmcgui.Dialog().ok(localise(32038), localise(32061)) log('failed to link to Emby Connect server {} ({}) {}'.format(matchingServer.name, serverId, serverUrl), xbmc.LOGWARNING)
def _request(self, url, function, *args): if not self._authenticate(): return False headers = Request.PrepareApiCallHeaders(authToken=self.AccessToken(), userId=self.UserId(), deviceId=self._devideId) return function(url, headers, *args)
def GetServers(accessToken, userId): if not accessToken: raise ValueError('invalid accessToken') if not userId: raise ValueError('invalid userId') url = Url.append(constants.URL_EMBY_CONNECT_BASE, constants.URL_EMBY_CONNECT_SERVERS) url = Url.addOptions(url, { constants.URL_QUERY_USER_ID: userId, }) headers = EmbyConnect._getApplicationHeader() headers.update({ constants.EMBY_CONNECT_USER_TOKEN_HEADER: accessToken, }) resultObj = Request.GetAsJson(url, headers=headers) if not resultObj: log('invalid response from {}: {}'.format(url, resultObj)) return None servers = [] for server in resultObj: id = server.get(constants.PROPERTY_EMBY_CONNECT_SERVER_ID, None) systemId = server.get( constants.PROPERTY_EMBY_CONNECT_SERVER_SYSTEM_ID, None) accessKey = server.get( constants.PROPERTY_EMBY_CONNECT_SERVER_ACCESS_KEY, None) name = server.get(constants.PROPERTY_EMBY_CONNECT_SERVER_NAME, None) remoteUrl = server.get( constants.PROPERTY_EMBY_CONNECT_SERVER_REMOTE_URL, None) localUrl = server.get( constants.PROPERTY_EMBY_CONNECT_SERVER_LOCAL_URL, None) if None in (id, accessKey, name, remoteUrl, localUrl): log('invalid Emby server received from {}: {}'.format( url, server)) continue servers.append( EmbyConnect.Server(id=id, systemId=systemId, accessKey=accessKey, name=name, remoteUrl=remoteUrl, localUrl=localUrl)) return servers
def _getPin(self): if self.pin: return self.pin url = Url.append(constants.URL_EMBY_CONNECT_BASE, constants.URL_EMBY_CONNECT_PIN) body = {constants.URL_QUERY_DEVICE_ID: self.deviceId} resultObj = Request.PostAsJson(url, json=body) if not resultObj or \ not constants.PROPERTY_EMBY_CONNECT_PIN in resultObj: log('failed to get a PIN from {}: {}'.format(url, resultObj)) return None self.pin = resultObj.get(constants.PROPERTY_EMBY_CONNECT_PIN) return self.pin
def resetDeviceId(handle, _): # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return # get the media provider settings providerSettings = mediaProvider.prepareSettings() if not providerSettings: return deviceId = Request.GenerateDeviceId() log('created a new device identifier for {}: {}'.format(mediaProvider2str(mediaProvider), deviceId)) providerSettings.setString(emby.constants.SETTING_PROVIDER_DEVICEID, deviceId) xbmcgui.Dialog().ok(mediaProvider.getFriendlyName(), localise(32063))
def ApiDelete(self, url): return self._request(url, lambda url, headers: Request.Delete(url, headers=headers))
def ApiPost(self, url, data=None, json=None): return self._request(url, lambda url, headers, data, json: Request.PostAsJson(url, headers=headers, body=data, json=json), data, json)
def ApiGet(self, url): return self._request(url, lambda url, headers: Request.GetAsJson(url, headers=headers))
def GetInfo(baseUrl): publicInfoUrl = server.Server.BuildPublicInfoUrl(baseUrl) headers = Request.PrepareApiCallHeaders() resultObj = Request.GetAsJson(publicInfoUrl, headers=headers) return Server.Info.fromPublicInfo(resultObj)
def discoverProviderWithEmbyConnect(handle, options): deviceId = Request.GenerateDeviceId() embyConnect = linkToEmbyConnect(deviceId) if not embyConnect: return None dialog = xbmcgui.Dialog() # get all connected servers servers = EmbyConnect.GetServers(embyConnect.accessToken, embyConnect.userId) if not servers: log( 'no servers available for Emby Connect user id {}'.format( embyConnect.userId), xbmc.LOGWARNING) return None if len(servers) == 1: server = servers[0] else: # ask the user which server to use serverChoices = [server.name for server in servers] serverChoice = dialog.select(localise(32057), serverChoices) if serverChoice < 0 or serverChoice >= len(serverChoices): return None server = server[serverChoice] if not server: return None urls = [] if server.localUrl: # ask the user whether to use a local or remote connection isLocal = dialog.yesno(localise(32058), localise(32059).format(server.name)) if isLocal: urls.append(server.localUrl) if server.remoteUrl: urls.append(server.remoteUrl) baseUrl = None # find a working connection / base URL for url in urls: try: _ = emby.api.server.Server.GetInfo(url) except: log('failed to connect to "{}" at {}'.format(server.name, url), xbmc.LOGDEBUG) continue baseUrl = url break if not baseUrl: dialog.ok(localise(32058), localise(32060).format(server.name)) log( 'failed to connect to Emby server "{}" with Emby Connect user ID {}' .format(server.name, embyConnect.userId), xbmc.LOGWARNING) return None providerId = Server.BuildProviderId(server.systemId) providerIconUrl = Server.BuildIconUrl(baseUrl) provider = xbmcmediaimport.MediaProvider( providerId, baseUrl, server.name, providerIconUrl, emby.constants.SUPPORTED_MEDIA_TYPES) provider.setIconUrl(kodi.Api.downloadIcon(provider)) # store Emby connect authentication in settings providerSettings = provider.prepareSettings() if not providerSettings: return None providerSettings.setString( emby.constants.SETTING_PROVIDER_AUTHENTICATION, emby.constants.SETTING_PROVIDER_AUTHENTICATION_OPTION_EMBY_CONNECT) providerSettings.setString( emby.constants.SETTING_PROVIDER_EMBY_CONNECT_USER_ID, embyConnect.userId) providerSettings.setString( emby.constants.SETTING_PROVIDER_EMBY_CONNECT_ACCESS_KEY, server.accessKey) providerSettings.setString(emby.constants.SETTING_PROVIDER_DEVICEID, deviceId) providerSettings.save() log('Emby Connect server {} successfully discovered at {}'.format( mediaProvider2str(provider), baseUrl)) return provider