async def _change_remote_access(hass, call): text = " zdalny dostęp do bramki z Internetu" access = hass.states.get("input_boolean.ais_remote_access").state # not allow to off on demo gate_id = ais_global.get_sercure_android_id_dom() if (ais_global.get_sercure_android_id_dom() in ("dom-demo", "dom-dev") and access != "on"): await hass.services.async_call( "ais_ai_service", "say_it", { "text": "Nie można zatrzymać zdalnego dostępu na instancji demo." }, ) hass.states.async_set("input_boolean.ais_remote_access", "on") return if access == "on": text = "Aktywuje " + text else: text = "Zatrzymuje " + text await hass.services.async_call("ais_ai_service", "say_it", {"text": text}) _LOGGER.info(text) if access == "on": await _run( "pm2 restart tunnel || pm2 start {}" " --name tunnel --output /dev/null --error /dev/null" " --restart-delay=150000 -- -h http://paczka.pro -p 8180 -s {}". format(G_LT_PATH, gate_id)) else: await _run("pm2 delete tunnel && pm2 save")
async def async_step_init( self, user_input: Optional[Dict[str, str]] = None) -> Dict[str, Any]: """Handle the step of the form.""" errors = {} if user_input is not None: try: cast(AisCloudAuthProvider, self._auth_provider).async_validate_login( user_input["username"], user_input["password"], user_input["gate_id"], ) except InvalidAuthError: errors["base"] = "invalid_auth" if not errors: user_input.pop("password") return await self.async_finish(user_input) sercure_android_id_dom = ais_global.get_sercure_android_id_dom() return self.async_show_form( step_id="init", data_schema=vol.Schema({ vol.Required("username"): str, vol.Required("password"): str, vol.Required("gate_id"): vol.In([sercure_android_id_dom]), }), errors=errors, )
def async_generate_url(hass, webhook_id): """Generate the full URL for a webhook_id.""" "ais-dom gate_id fix" from homeassistant.components.ais_dom import ais_global gate_id = ais_global.get_sercure_android_id_dom() return "{}{}".format("https://" + gate_id + ".paczka.pro", async_generate_path(webhook_id))
async def _set_ais_secure_android_id_dom(hass, call): # the G_AIS_SECURE_ANDROID_ID_DOM is set only in one place ais_global hass.states.async_set( "sensor.ais_secure_android_id_dom", ais_global.get_sercure_android_id_dom(), { "friendly_name": "Unikalny identyfikator bramki", "icon": "mdi:account-card-details", }, )
def _refresh_(self): import pyqrcode import png gate_id = ais_global.get_sercure_android_id_dom() _template = "https://" + gate_id + ".paczka.pro" qr_code = pyqrcode.create(_template) self._image.truncate(0) self._image.seek(0) qr_code.png(self._image, scale=6, module_color=[0, 0, 0], background=[0xFF, 0xFF, 0xFF])
async def async_step_user(self, user_input=None): """Handle a user initiated set up flow to create OwnTracks webhook.""" if self._async_current_entries(): return self.async_abort(reason="one_instance_allowed") if user_input is None: return self.async_show_form(step_id="user") webhook_id, webhook_url, cloudhook = await self._get_webhook_id() # from homeassistant.components.ais_dom import ais_global gate_id = ais_global.get_sercure_android_id_dom() webhook_url = webhook_url.replace("localhost:8180", gate_id + ".paczka.pro") secret = generate_secret(16) if supports_encryption(): secret_desc = ( "The encryption key is {} " "(on Android under preferences -> advanced)".format(secret)) else: secret_desc = ( "Encryption is not supported because libsodium is not " "installed.") return self.async_create_entry( title="OwnTracks", data={ CONF_WEBHOOK_ID: webhook_id, CONF_SECRET: secret, CONF_CLOUDHOOK: cloudhook, }, description_placeholders={ "secret": "", "webhook_url": webhook_url, "android_url": "https://play.google.com/store/apps/details?" "id=org.owntracks.android", "ios_url": "https://itunes.apple.com/us/app/owntracks/id692424691?mt=8", "docs_url": "https://sviete.github.io/AIS-docs/docs/en/next/ais_bramka_presence_detection.html", }, )
async def async_step_oauth(self, user_input=None): request = current_request.get() url_host = yarl.URL(request.url).host """Handle flow external step.""" ais_dom = ais_cloud.AisCloudWS(self.hass) json_ws_resp = ais_dom.key("google_calendar_web_client_id") client_id = json_ws_resp["key"] gate_id = ais_global.get_sercure_android_id_dom() auth_url = ( "https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?client_id=" + client_id + "&redirect_uri=https://" + ais_global.AIS_HOST + "/ords/dom/auth/google_calendar_callback" + "&response_type=code&scope=https://www.googleapis.com/auth/calendar" + "&access_type=offline" + "&state=" + gate_id + "ais0dom" + url_host + "ais0domgoogle-calendar-" + self.flow_id) return self.async_external_step(step_id="obtain_token", url=auth_url)
def _get_external_url( hass: HomeAssistant, *, allow_cloud: bool = True, allow_ip: bool = True, prefer_cloud: bool = False, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url(hass) if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) if ((allow_ip or not is_ip_address(str(external_url.host))) and (not require_current_request or external_url.host == _get_request_host()) and (not require_standard_port or external_url.is_default_port()) and (not require_ssl or (external_url.scheme == "https" and not is_ip_address(str(external_url.host))))): return normalize_url(str(external_url)) if allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url( hass, require_current_request=require_current_request) # get ais url remote_access = hass.states.get("input_boolean.ais_remote_access").state if remote_access == "on": import homeassistant.components.ais_dom.ais_global as ais_global return "https://" + ais_global.get_sercure_android_id_dom( ) + ".paczka.pro" else: return "http://" + hass.config.api.local_ip + ":" + str( hass.config.api.port) raise NoURLAvailableError
async def async_step_oauth(self, user_input=None): if user_input is not None: self.hass.http.register_view(AuthorizationCallbackView) request = current_request.get() url_host = yarl.URL(request.url).host """Handle flow external step.""" ais_dom = ais_cloud.AisCloudWS(self.hass) json_ws_resp = ais_dom.key("supla_mqtt_prod_client_id") self.client_id = json_ws_resp["key"] gate_id = ais_global.get_sercure_android_id_dom() redirect_uri = REDIRECT_URL.replace("AIS_HOST", ais_global.AIS_HOST) auth_url = ( f"{OAUTH_URL}?client_id={self.client_id}&redirect_uri={redirect_uri}&scope={AUTH_SCOPE}&response_type" f"=code&state={gate_id}ais0dom{url_host}ais0domsupla-mqtt-{self.flow_id}" ) return self.async_external_step(step_id="obtain_token", url=auth_url) return self.async_show_form(step_id="oauth")
async def async_handle_core_service(call): """Service handler for handling core services.""" # ais dom import homeassistant.components.ais_dom.ais_global as ais_global # return on demo if ais_global.get_sercure_android_id_dom() in ( "dom-274973439829002", "dom-demo", ): return ais_command = None if "ais_command" in call.data: ais_command = call.data["ais_command"] if call.service == SERVICE_HOMEASSISTANT_STOP: hass.async_create_task(hass.async_stop(ais_command=ais_command)) return try: errors = await conf_util.async_check_ha_config_file(hass) except HomeAssistantError: return if errors: _LOGGER.error(errors) hass.components.persistent_notification.async_create( "Config error. See [the logs](/config/logs) for details.", "Config validating", f"{ha.DOMAIN}.check_config", ) return if call.service == SERVICE_HOMEASSISTANT_RESTART: hass.async_create_task( hass.async_stop(RESTART_EXIT_CODE, ais_command=ais_command))
def setup(hass, config): """Set up Zeroconf and make AIS dom discoverable.""" if not ais_global.has_root(): return True from zeroconf import Zeroconf, ServiceInfo zero_config = Zeroconf() host_ip = util.get_local_ip() try: return_value = subprocess.check_output( "getprop net.hostname", timeout=15, shell=True, # nosec ) host_name = return_value.strip().decode("utf-8") except subprocess.CalledProcessError: host_name = socket.gethostname() if len(host_name) == 0: # get the mac address import uuid host_name = "".join([ "{:02x}".format((uuid.getnode() >> i) & 0xFF) for i in range(0, 8 * 6, 8) ][::-1]) if host_name.endswith(".local"): host_name = host_name[:-len(".local")] hass.states.async_set( "sensor.local_host_name", host_name.upper(), { "friendly_name": "Lokalna nazwa hosta", "icon": "mdi:dns" }, ) try: host_ip_pton = socket.inet_pton(socket.AF_INET, host_ip) except OSError: host_ip_pton = socket.inet_pton(socket.AF_INET6, host_ip) try: gate_id = ais_global.get_sercure_android_id_dom() except Exception: gate_id = "xxx" params = { "location_name": hass.config.location_name, "version": __version__, "company_url": "https://www.ai-speaker.com", "gate_id": gate_id, } # HTTP http_info = ServiceInfo( "_http._tcp.local.", name=host_name + "._http._tcp.local.", server=f"{host_name}.local.", addresses=[host_ip_pton], port=80, properties=params, ) # FTP ftp_info = ServiceInfo( "_ftp._tcp.local.", name=host_name + "._ftp._tcp.local.", server=f"{host_name}.local.", addresses=[host_ip_pton], port=21, properties=params, ) def zeroconf_hass_start(_event): """Expose Home Assistant on zeroconf when it starts. Wait till started or otherwise HTTP is not up and running. """ _LOGGER.info("Starting Zeroconf broadcast") try: zero_config.register_service(http_info) zero_config.register_service(ftp_info) except NonUniqueNameException: _LOGGER.error( "Home Assistant instance with identical name present in the local network" ) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, zeroconf_hass_start) def stop_zeroconf(event): """Stop Zeroconf.""" zero_config.unregister_service(http_info) zero_config.unregister_service(ftp_info) zero_config.close() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zeroconf) return True
def setup(hass, config): """Set up Zeroconf and make AIS dom discoverable.""" from zeroconf import Zeroconf, ServiceInfo zero_config = Zeroconf() host_ip = util.get_local_ip() try: return_value = subprocess.check_output("getprop net.hostname", shell=True, timeout=15) host_name = return_value.strip().decode("utf-8") except subprocess.CalledProcessError: host_name = socket.gethostname() if len(host_name) == 0: # get the mac address import uuid host_name = "".join([ "{:02x}".format((uuid.getnode() >> i) & 0xFF) for i in range(0, 8 * 6, 8) ][::-1]) if host_name.endswith(".local"): host_name = host_name[:-len(".local")] hass.states.async_set( "sensor.local_host_name", host_name.upper(), { "friendly_name": "Lokalna nazwa hosta", "icon": "mdi:dns" }, ) try: ip = socket.inet_pton(socket.AF_INET, host_ip) except socket.error: ip = socket.inet_pton(socket.AF_INET6, host_ip) try: gate_id = ais_global.get_sercure_android_id_dom() except: gate_id = "xxx" params = { "version": __version__, "company_url": "https://www.ai-speaker.com", "gate_id": gate_id, } # HASS hass_info = ServiceInfo( "_home-assistant._tcp.local.", host_name + "._home-assistant._tcp.local.", ip, 8180, 0, 0, params, host_name + ".local.", ) zero_config.register_service(hass_info) # HTTP http_info = ServiceInfo( "_http._tcp.local.", host_name + "._http._tcp.local.", ip, 8180, 0, 0, params, host_name + ".local.", ) zero_config.register_service(http_info) # MQTT is moved to the android # mqtt_info = ServiceInfo("_mqtt._tcp.local.", # host_name + "._mqtt._tcp.local.", # ip, 1883, 0, 0, # params, host_name + ".local.") # # zero_config.register_service(mqtt_info) # FTP ftp_info = ServiceInfo( "_ftp._tcp.local.", host_name + "._ftp._tcp.local.", ip, 1024, 0, 0, params, host_name + ".local.", ) zero_config.register_service(ftp_info) def stop_zeroconf(event): """Stop Zeroconf.""" zero_config.unregister_service(hass_info) zero_config.unregister_service(http_info) # zero_config.unregister_service(mqtt_info) zero_config.unregister_service(ftp_info) zero_config.close() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zeroconf) return True
async def async_setup(hass, config): """Set up the Spotify platform.""" global aisCloud aisCloud = ais_cloud.AisCloudWS(hass) import json import spotipy.oauth2 global AIS_SPOTIFY_TOKEN # info about discovery async def do_the_spotify_disco(service): """ Called when a Spotify integration has been discovered. """ await hass.config_entries.flow.async_init( "ais_spotify_service", context={"source": "discovery"}, data={}) await hass.async_block_till_done() try: json_ws_resp = await aisCloud.async_key("spotify_oauth") spotify_redirect_url = json_ws_resp["SPOTIFY_REDIRECT_URL"] spotify_client_id = json_ws_resp["SPOTIFY_CLIENT_ID"] spotify_client_secret = json_ws_resp["SPOTIFY_CLIENT_SECRET"] if "SPOTIFY_SCOPE_FULL" in json_ws_resp: spotify_scope = json_ws_resp["SPOTIFY_SCOPE_FULL"] else: spotify_scope = json_ws_resp["SPOTIFY_SCOPE"] try: json_ws_resp = await aisCloud.async_key("spotify_token") key = json_ws_resp["key"] AIS_SPOTIFY_TOKEN = json.loads(key) except: AIS_SPOTIFY_TOKEN = None _LOGGER.info("No AIS_SPOTIFY_TOKEN") except Exception as e: _LOGGER.error("No spotify oauth info: " + str(e)) return True cache = hass.config.path(DEFAULT_CACHE_PATH) gate_id = ais_global.get_sercure_android_id_dom() j_state = json.dumps({ "gate_id": gate_id, "real_ip": "real_ip_place", "flow_id": "flow_id_place" }) oauth = spotipy.oauth2.SpotifyOAuth( spotify_client_id, spotify_client_secret, spotify_redirect_url, scope=spotify_scope, cache_path=cache, state=j_state, ) setUrl(oauth.get_authorize_url()) token_info = oauth.get_cached_token() if not token_info: _LOGGER.info("no spotify token in cache;") if AIS_SPOTIFY_TOKEN is not None: with open(cache, "w") as outfile: json.dump(AIS_SPOTIFY_TOKEN, outfile) token_info = oauth.get_cached_token() # register services if not token_info: _LOGGER.info("no spotify token exit") hass.async_add_job(do_the_spotify_disco(hass)) return True data = hass.data[DOMAIN] = SpotifyData(hass, oauth) async def async_search(call): _LOGGER.info("search " + str(call)) await data.async_process_search(call) async def async_get_favorites(call): await data.async_process_get_favorites(call) def select_search_uri(call): _LOGGER.info("select_search_uri") data.select_search_uri(call) def select_track_uri(call): _LOGGER.info("select_track_uri") data.select_track_uri(call) def change_play_queue(call): _LOGGER.info("change_play_queue") data.change_play_queue(call) hass.services.async_register(DOMAIN, "search", async_search) hass.services.async_register(DOMAIN, "get_favorites", async_get_favorites) hass.services.async_register(DOMAIN, "select_search_uri", select_search_uri) hass.services.async_register(DOMAIN, "select_track_uri", select_track_uri) hass.services.async_register(DOMAIN, "change_play_queue", change_play_queue) return True
async def _change_remote_access(hass, call): text = " zdalny dostęp do bramki z Internetu" access = hass.states.get("input_boolean.ais_remote_access").state # not allow to off on demo gate_id = ais_global.get_sercure_android_id_dom() if (ais_global.get_sercure_android_id_dom() in ("dom-demo", "dom-dev") and access != "on"): await hass.services.async_call( "ais_ai_service", "say_it", { "text": "Nie można zatrzymać zdalnego dostępu na instancji demo." }, ) hass.states.async_set("input_boolean.ais_remote_access", "on") return if access == "on": text = "Aktywuje " + text else: text = "Zatrzymuje " + text await hass.services.async_call("ais_ai_service", "say_it", {"text": text}) _LOGGER.info(text) if access == "on": # delete old tunnel await _run("pm2 delete tunnel") if not os.path.isfile( "/data/data/pl.sviete.dom/files/home/.cloudflared/config.yaml" ): await _run( "mkdir -p /data/data/pl.sviete.dom/files/home/.cloudflared") with async_timeout.timeout(20): web_session = aiohttp_client.async_get_clientsession(hass) # store file async with web_session.get( "https://ai-speaker.com/ota/ais_cloudflared") as resp: if resp.status == 200: body = await resp.read() f = open( "/data/data/pl.sviete.dom/files/home/.cloudflared/cert.pem", mode="wb", ) f.write(body) f.close() # create named tunnel await _run( "/data/data/pl.sviete.dom/files/usr/bin/cloudflared --origincert " "/data/data/pl.sviete.dom/files/home/.cloudflared/cert.pem tunnel delete -f " + gate_id) await _run( "/data/data/pl.sviete.dom/files/usr/bin/cloudflared --origincert " "/data/data/pl.sviete.dom/files/home/.cloudflared/cert.pem tunnel create " + gate_id) # rename credentials file await _run( "mv /data/data/pl.sviete.dom/files/home/.cloudflared/*.json " "/data/data/pl.sviete.dom/files/home/.cloudflared/key.json") # create config.yaml f = open( "/data/data/pl.sviete.dom/files/home/.cloudflared/config.yaml", mode="w") f.write("tunnel: " + gate_id + "\n") f.write( "credentials-file: /data/data/pl.sviete.dom/files/home/.cloudflared/key.json\n" ) f.write("ingress:\n") f.write(" - hostname: " + gate_id + ".paczka.pro\n") f.write(" service: http://localhost:8180\n") f.write(" - service: http_status:404") f.close() # route traffic await _run( "/data/data/pl.sviete.dom/files/usr/bin/cloudflared --origincert " "/data/data/pl.sviete.dom/files/home/.cloudflared/cert.pem tunnel route dns -f " + gate_id + " " + gate_id + ".paczka.pro") # delete cert file await _run( "rm /data/data/pl.sviete.dom/files/home/.cloudflared/cert.pem") # await _run( "pm2 start /data/data/pl.sviete.dom/files/usr/bin/cloudflared " "--name tunnel " "--restart-delay=150000 -- --config /data/data/pl.sviete.dom/files/home/.cloudflared/config.yaml " "tunnel run") await _run("pm2 save") # OLD WAY # await _run( # "pm2 restart tunnel || pm2 start /data/data/pl.sviete.dom/files/usr/bin/cloudflared" # " --name tunnel --output /dev/null --error /dev/null" # " --restart-delay=150000 -- --hostname http://{}.paczka.pro --url http://localhost:8180".format( # gate_id # ) # ) else: await _run("pm2 delete tunnel && pm2 save") await _run("rm /data/data/pl.sviete.dom/files/home/.cloudflared/*")
async def get(self, request): global G_SPOTIFY_AUTH_URL hass = request.app["hass"] flow_id = request.query["flow_id"] try: step_ip = request.query["step_ip"] except Exception: step_ip = 0 # add the REAL_IP and FLOW_ID to Spotify Auth URL and redirect to Spotify for authentication if step_ip == "1": real_ip = request.url.host # if G_SPOTIFY_AUTH_URL is None: try: import json import spotipy.oauth2 from homeassistant.components import ais_cloud from homeassistant.components.ais_dom import ais_global from . import DEFAULT_CACHE_PATH aisCloud = ais_cloud.AisCloudWS(hass) json_ws_resp = await aisCloud.async_key("spotify_oauth") spotify_redirect_url = json_ws_resp["SPOTIFY_REDIRECT_URL"] spotify_client_id = json_ws_resp["SPOTIFY_CLIENT_ID"] spotify_client_secret = json_ws_resp["SPOTIFY_CLIENT_SECRET"] if "SPOTIFY_SCOPE_FULL" in json_ws_resp: spotify_scope = json_ws_resp["SPOTIFY_SCOPE_FULL"] else: spotify_scope = json_ws_resp["SPOTIFY_SCOPE"] except Exception as e: _LOGGER.error("No spotify oauth info: " + str(e)) return True cache = hass.config.path(DEFAULT_CACHE_PATH) gate_id = ais_global.get_sercure_android_id_dom() j_state = json.dumps( { "gate_id": gate_id, "real_ip": "real_ip_place", "flow_id": "flow_id_place", } ) oauth = spotipy.oauth2.SpotifyOAuth( spotify_client_id, spotify_client_secret, spotify_redirect_url, scope=spotify_scope, cache_path=cache, state=j_state, ) setUrl(oauth.get_authorize_url()) G_SPOTIFY_AUTH_URL = G_SPOTIFY_AUTH_URL.replace("real_ip_place", real_ip) G_SPOTIFY_AUTH_URL = G_SPOTIFY_AUTH_URL.replace("flow_id_place", flow_id) js_text = ( "<script>window.location.href='" + G_SPOTIFY_AUTH_URL + "'</script>" ) return aiohttp.web_response.Response( headers={"content-type": "text/html"}, text=js_text ) # the call was from ais-dom finish the integration hass.async_create_task( hass.config_entries.flow.async_configure(flow_id=flow_id, user_input="ok") ) # js_text = "<script>window.close()</script>" js_text = ( "<script>window.location.href='/config/integrations/dashboard'</script>" ) return aiohttp.web_response.Response( headers={"content-type": "text/html"}, text=js_text )
def play_media(self, media_type, media_content_id, **kwargs): """Send the media player the command for playing a media.""" if media_type == "ais_content_info": j_info = json.loads(media_content_id) # set image and name and id on list to be able to set correct bookmark and favorites ais_global.G_CURR_MEDIA_CONTENT = j_info # play self._media_content_id = j_info["media_content_id"] self._media_status_received_time = dt_util.utcnow() if "media_position_ms" in j_info: self._media_position = j_info["media_position_ms"] else: self._media_position = 0 if "IMAGE_URL" not in j_info: self._stream_image = "/static/icons/tile-win-310x150.png" else: self._stream_image = j_info["IMAGE_URL"] self._media_title = j_info["NAME"] if j_info["MEDIA_SOURCE"] == ais_global.G_AN_GOOGLE_ASSISTANT: # do no change self._media_source self._assistant_audio = True else: self._assistant_audio = False self._media_source = j_info["MEDIA_SOURCE"] self._currentplaylist = j_info["MEDIA_SOURCE"] if "ALBUM_NAME" in j_info: self._album_name = j_info["ALBUM_NAME"] else: self._album_name = "" if "DURATION" in j_info: self._duration = j_info["DURATION"] j_media_info = { "media_title": self._media_title, "media_source": self._media_source, "media_stream_image": self._stream_image, "media_album_name": self._album_name, "media_content_id": self._media_content_id, "setPlayerShuffle": self._shuffle, "setMediaPosition": self._media_position, } _publish_command_to_frame(self.hass, self._device_ip, "playAudioFullInfo", j_media_info) self._playing = True self._status = 3 # go to media player context on localhost if self._device_ip == "localhost": # refresh state old_state = self.hass.states.get( "media_player.wbudowany_glosnik") self.hass.states.set( "media_player.wbudowany_glosnik", STATE_PLAYING, old_state.attributes, force_update=True, ) self.hass.services.call( "ais_ai_service", "process_command_from_frame", { "topic": "ais/go_to_player", "payload": "" }, ) elif media_type == "ais_info": # TODO remove this - it is only used in one case - for local media_extractor # set only the audio info j_info = json.loads(media_content_id) ais_global.G_CURR_MEDIA_CONTENT = j_info if "IMAGE_URL" not in j_info: self._stream_image = "/static/icons/tile-win-310x150.png" else: self._stream_image = j_info["IMAGE_URL"] self._media_title = j_info["NAME"] self._media_source = j_info["MEDIA_SOURCE"] self._currentplaylist = j_info["MEDIA_SOURCE"] if "ALBUM_NAME" in j_info: self._album_name = j_info["ALBUM_NAME"] else: self._album_name = None if "DURATION" in j_info: self._duration = j_info["DURATION"] try: j_media_info = { "media_title": self._media_title, "media_source": self._media_source, "media_stream_image": self._stream_image, "media_album_name": self._album_name, } _publish_command_to_frame(self.hass, self._device_ip, "setAudioInfo", j_media_info) except Exception as e: _LOGGER.debug("problem to publish setAudioInfo: " + str(e)) self._playing = True self._status = 3 # go to media player context on localhost if self._device_ip == "localhost": # refresh state old_state = self.hass.states.get( "media_player.wbudowany_glosnik") self.hass.states.set( "media_player.wbudowany_glosnik", STATE_PLAYING, old_state.attributes, force_update=True, ) self.hass.services.call( "ais_ai_service", "process_command_from_frame", { "topic": "ais/go_to_player", "payload": "" }, ) elif media_type == "exo_info": self._media_status_received_time = dt_util.utcnow() try: message = json.loads( media_content_id.decode("utf8").replace("'", '"')) except Exception as e: message = json.loads(media_content_id) try: self._volume_level = message.get("currentVolume", 0) / 100 except Exception as e: _LOGGER.warning("no currentVolume info: " + str(e)) self._status = message.get("currentStatus", 0) self._playing = message.get("playing", False) self._media_position = message.get("currentPosition", 0) if "duration" in message: self._duration = message.get("duration", 0) temp_stream_image = message.get("media_stream_image", None) if temp_stream_image is not None: if temp_stream_image.startswith("spotify:image:"): temp_stream_image = temp_stream_image.replace( "spotify:image:", "") self._stream_image = "https://i.scdn.co/image/" + temp_stream_image else: self._stream_image = temp_stream_image self._media_title = message.get("currentMedia", "AI-Speaker") self._media_source = message.get("media_source", self._media_source) self._album_name = message.get("media_album_name", "AI-Speaker") # spotify info if self._media_source == ais_global.G_AN_SPOTIFY: # shuffle self._shuffle = message.get("isShuffling", False) # mark track on Spotify list state = self.hass.states.get("sensor.spotifylist") attr = state.attributes for i in range(len(attr)): track = attr.get(i) if track["uri"] == message.get("media_content_id", ""): self.hass.states.async_set("sensor.spotifylist", i, attr) break if "giveMeNextOne" in message: play_next = message.get("giveMeNextOne", False) # play next audio only if the current was not from assistant if play_next is True and self._assistant_audio is False: # TODO remove bookmark self.hass.async_add_job( self.hass.services.async_call( "media_player", "media_next_track", {"entity_id": "media_player.wbudowany_glosnik"}, )) else: # do not call async from threads - this will it can lead to a deadlock!!! # if media_content_id.startswith("ais_"): # from homeassistant.components.ais_exo_player import media_browser # # media_content_id = media_browser.get_media_content_id_form_ais( # self.hass, media_content_id # ) # this is currently used when the media are taken from gallery if media_content_id.startswith("ais_"): import requests if media_content_id.startswith("ais_tunein"): url_to_call = media_content_id.split("/", 3)[3] try: response_text = requests.get(url_to_call, timeout=2).text response_text = response_text.split("\n")[0] media_content_id = response_text if response_text.endswith(".pls"): response_text = requests.get(response_text, timeout=2).text media_content_id = response_text.split( "\n")[1].replace("File1=", "") if response_text.startswith("mms:"): response_text = requests.get(response_text.replace( "mms:", "http:"), timeout=2).text media_content_id = response_text.split( "\n")[1].replace("Ref1=", "") except Exception as e: pass elif media_content_id.startswith("ais_spotify"): media_content_id = media_content_id.replace( "ais_spotify/", "") self._media_content_id = media_content_id self._media_position = 0 self._media_status_received_time = dt_util.utcnow() _publish_command_to_frame(self.hass, self._device_ip, "playAudio", media_content_id) # 0. push media info to ais to share with mobile clients j_media_info = { "media_title": self._media_title, "media_source": self._media_source, "media_stream_image": self._stream_image, "media_album_name": self._album_name, "media_content_id": self._media_content_id, "gate_id": ais_global.get_sercure_android_id_dom(), } self._ais_gate.share_media_full_info(j_media_info)
async def ais_tunein_library(hass, media_content_id) -> BrowseMedia: import xml.etree.ElementTree as ET web_session = aiohttp_client.async_get_clientsession(hass) if media_content_id == "ais_tunein": try: # 7 sec should be enough with async_timeout.timeout(7): # we need this only for demo if ais_global.get_sercure_android_id_dom() == "dom-demo": headers = {"accept-language": "pl"} ws_resp = await web_session.get( "http://opml.radiotime.com/", headers=headers) else: ws_resp = await web_session.get( "http://opml.radiotime.com/") response_text = await ws_resp.text() root = ET.fromstring(response_text) # nosec tunein_types = [] for item in root.findall("body/outline"): tunein_types.append( BrowseMedia( title=item.get("text"), media_class=MEDIA_CLASS_DIRECTORY, media_content_id=media_content_id + "/2/" + item.get("text") + "/" + item.get("URL"), media_content_type=MEDIA_TYPE_APP, can_play=False, can_expand=True, thumbnail="", )) root = BrowseMedia( title="TuneIn", media_class=MEDIA_CLASS_DIRECTORY, media_content_id=media_content_id, media_content_type=MEDIA_TYPE_APP, can_expand=True, can_play=False, children=tunein_types, ) return root except Exception as e: _LOGGER.error("Can't connect tune in api: " + str(e)) raise BrowseError("Can't connect tune in api: " + str(e)) elif media_content_id.startswith("ais_tunein/2/"): try: # 7 sec should be enough with async_timeout.timeout(7): url_to_call = media_content_id.split("/", 3)[3] # we need to set language only for demo if ais_global.get_sercure_android_id_dom() == "dom-demo": headers = {"accept-language": "pl"} ws_resp = await web_session.get(url_to_call, headers=headers) else: ws_resp = await web_session.get(url_to_call) response_text = await ws_resp.text() root = ET.fromstring(response_text) # nosec tunein_items = [] for item in root.findall("body/outline"): if item.get("type") == "audio": tunein_items.append( BrowseMedia( title=item.get("text"), media_class=MEDIA_CLASS_DIRECTORY, media_content_id="ais_tunein/2/" + item.get("text") + "/" + item.get("URL"), media_content_type=MEDIA_TYPE_APP, can_play=True, can_expand=False, thumbnail=item.get("image"), )) elif item.get("type") == "link": tunein_items.append( BrowseMedia( title=item.get("text"), media_class=MEDIA_CLASS_DIRECTORY, media_content_id="ais_tunein/2/" + item.get("text") + "/" + item.get("URL"), media_content_type=MEDIA_TYPE_APP, can_play=False, can_expand=True, )) for item in root.findall("body/outline/outline"): if item.get("type") == "audio": tunein_items.append( BrowseMedia( title=item.get("text"), media_class=MEDIA_CLASS_DIRECTORY, media_content_id="ais_tunein/2/" + item.get("text") + "/" + item.get("URL"), media_content_type=MEDIA_TYPE_APP, can_play=True, can_expand=False, thumbnail=item.get("image"), )) elif item.get("type") == "link": tunein_items.append( BrowseMedia( title=item.get("text"), media_class=MEDIA_CLASS_DIRECTORY, media_content_id="ais_tunein/2/" + item.get("text") + "/" + item.get("URL"), media_content_type=MEDIA_TYPE_APP, can_play=False, can_expand=True, )) root = BrowseMedia( title=media_content_id.split("/", 3)[2], media_class=MEDIA_CLASS_DIRECTORY, media_content_id=media_content_id, media_content_type=MEDIA_TYPE_APP, can_expand=True, can_play=False, children=tunein_items, ) return root except Exception as e: _LOGGER.error("Can't connect tune in api: " + str(e)) raise BrowseError("Can't connect tune in api: " + str(e))
def state(self): gate_id = ais_global.get_sercure_android_id_dom() return "https://" + gate_id + ".paczka.pro"