class HangoutsBot: """The Hangouts Bot.""" def __init__( self, hass, refresh_token, intents, default_convs, error_suppressed_convs ): """Set up the client.""" self.hass = hass self._connected = False self._refresh_token = refresh_token self._intents = intents self._conversation_intents = None self._client = None self._user_list = None self._conversation_list = None self._default_convs = default_convs self._default_conv_ids = None self._error_suppressed_convs = error_suppressed_convs self._error_suppressed_conv_ids = None dispatcher.async_dispatcher_connect( self.hass, EVENT_HANGOUTS_MESSAGE_RECEIVED, self._async_handle_conversation_message, ) def _resolve_conversation_id(self, obj): if CONF_CONVERSATION_ID in obj: return obj[CONF_CONVERSATION_ID] if CONF_CONVERSATION_NAME in obj: conv = self._resolve_conversation_name(obj[CONF_CONVERSATION_NAME]) if conv is not None: return conv.id_ return None def _resolve_conversation_name(self, name): for conv in self._conversation_list.get_all(): if conv.name == name: return conv return None def async_update_conversation_commands(self): """Refresh the commands for every conversation.""" self._conversation_intents = {} for intent_type, data in self._intents.items(): if data.get(CONF_CONVERSATIONS): conversations = [] for conversation in data.get(CONF_CONVERSATIONS): conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: conversations.append(conv_id) data["_" + CONF_CONVERSATIONS] = conversations elif self._default_conv_ids: data["_" + CONF_CONVERSATIONS] = self._default_conv_ids else: data["_" + CONF_CONVERSATIONS] = [ conv.id_ for conv in self._conversation_list.get_all() ] for conv_id in data["_" + CONF_CONVERSATIONS]: if conv_id not in self._conversation_intents: self._conversation_intents[conv_id] = {} self._conversation_intents[conv_id][intent_type] = data try: self._conversation_list.on_event.remove_observer( self._async_handle_conversation_event ) except ValueError: pass self._conversation_list.on_event.add_observer( self._async_handle_conversation_event ) def async_resolve_conversations(self, _): """Resolve the list of default and error suppressed conversations.""" self._default_conv_ids = [] self._error_suppressed_conv_ids = [] for conversation in self._default_convs: conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: self._default_conv_ids.append(conv_id) for conversation in self._error_suppressed_convs: conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: self._error_suppressed_conv_ids.append(conv_id) dispatcher.async_dispatcher_send( self.hass, EVENT_HANGOUTS_CONVERSATIONS_RESOLVED ) async def _async_handle_conversation_event(self, event): from hangups import ChatMessageEvent if isinstance(event, ChatMessageEvent): dispatcher.async_dispatcher_send( self.hass, EVENT_HANGOUTS_MESSAGE_RECEIVED, event.conversation_id, event.user_id, event, ) async def _async_handle_conversation_message(self, conv_id, user_id, event): """Handle a message sent to a conversation.""" user = self._user_list.get_user(user_id) if user.is_self: return message = event.text _LOGGER.debug("Handling message '%s' from %s", message, user.full_name) intents = self._conversation_intents.get(conv_id) if intents is not None: is_error = False try: intent_result = await self._async_process(intents, message, conv_id) except (intent.UnknownIntent, intent.IntentHandleError) as err: is_error = True intent_result = intent.IntentResponse() intent_result.async_set_speech(str(err)) if intent_result is None: is_error = True intent_result = intent.IntentResponse() intent_result.async_set_speech("Sorry, I didn't understand that") message = ( intent_result.as_dict().get("speech", {}).get("plain", {}).get("speech") ) if (message is not None) and not ( is_error and conv_id in self._error_suppressed_conv_ids ): await self._async_send_message( [{"text": message, "parse_str": True}], [{CONF_CONVERSATION_ID: conv_id}], None, ) async def _async_process(self, intents, text, conv_id): """Detect a matching intent.""" for intent_type, data in intents.items(): for matcher in data.get(CONF_MATCHERS, []): match = matcher.match(text) if not match: continue if intent_type == INTENT_HELP: return await self.hass.helpers.intent.async_handle( DOMAIN, intent_type, {"conv_id": {"value": conv_id}}, text ) return await self.hass.helpers.intent.async_handle( DOMAIN, intent_type, {key: {"value": value} for key, value in match.groupdict().items()}, text, ) async def async_connect(self): """Login to the Google Hangouts.""" from .hangups_utils import HangoutsRefreshToken, HangoutsCredentials from hangups import Client from hangups import get_auth session = await self.hass.async_add_executor_job( get_auth, HangoutsCredentials(None, None, None), HangoutsRefreshToken(self._refresh_token), ) self._client = Client(session) self._client.on_connect.add_observer(self._on_connect) self._client.on_disconnect.add_observer(self._on_disconnect) self.hass.loop.create_task(self._client.connect()) def _on_connect(self): _LOGGER.debug("Connected!") self._connected = True dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_CONNECTED) async def _on_disconnect(self): """Handle disconnecting.""" if self._connected: _LOGGER.debug("Connection lost! Reconnect...") await self.async_connect() else: dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_DISCONNECTED) async def async_disconnect(self): """Disconnect the client if it is connected.""" if self._connected: self._connected = False await self._client.disconnect() async def async_handle_hass_stop(self, _): """Run once when Home Assistant stops.""" await self.async_disconnect() async def _async_send_message(self, message, targets, data): conversations = [] for target in targets: conversation = None if CONF_CONVERSATION_ID in target: conversation = self._conversation_list.get(target[CONF_CONVERSATION_ID]) elif CONF_CONVERSATION_NAME in target: conversation = self._resolve_conversation_name( target[CONF_CONVERSATION_NAME] ) if conversation is not None: conversations.append(conversation) if not conversations: return False from hangups import ChatMessageSegment, hangouts_pb2 messages = [] for segment in message: if messages: messages.append( ChatMessageSegment( "", segment_type=hangouts_pb2.SEGMENT_TYPE_LINE_BREAK ) ) if "parse_str" in segment and segment["parse_str"]: messages.extend(ChatMessageSegment.from_str(segment["text"])) else: if "parse_str" in segment: del segment["parse_str"] messages.append(ChatMessageSegment(**segment)) image_file = None if data: if data.get("image_url"): uri = data.get("image_url") try: websession = async_get_clientsession(self.hass) async with websession.get(uri, timeout=5) as response: if response.status != 200: _LOGGER.error( "Fetch image failed, %s, %s", response.status, response ) image_file = None else: image_data = await response.read() image_file = io.BytesIO(image_data) image_file.name = "image.png" except (asyncio.TimeoutError, aiohttp.ClientError) as error: _LOGGER.error("Failed to fetch image, %s", type(error)) image_file = None elif data.get("image_file"): uri = data.get("image_file") if self.hass.config.is_allowed_path(uri): try: image_file = open(uri, "rb") except OSError as error: _LOGGER.error( "Image file I/O error(%s): %s", error.errno, error.strerror ) else: _LOGGER.error('Path "%s" not allowed', uri) if not messages: return False for conv in conversations: await conv.send_message(messages, image_file) async def _async_list_conversations(self): import hangups self._user_list, self._conversation_list = await hangups.build_user_conversation_list( self._client ) conversations = {} for i, conv in enumerate(self._conversation_list.get_all()): users_in_conversation = [] for user in conv.users: users_in_conversation.append(user.full_name) conversations[str(i)] = { CONF_CONVERSATION_ID: str(conv.id_), CONF_CONVERSATION_NAME: conv.name, "users": users_in_conversation, } self.hass.states.async_set( f"{DOMAIN}.conversations", len(self._conversation_list.get_all()), attributes=conversations, ) dispatcher.async_dispatcher_send( self.hass, EVENT_HANGOUTS_CONVERSATIONS_CHANGED, conversations ) async def async_handle_send_message(self, service): """Handle the send_message service.""" await self._async_send_message( service.data[ATTR_MESSAGE], service.data[ATTR_TARGET], service.data.get(ATTR_DATA, {}), ) async def async_handle_update_users_and_conversations(self, _=None): """Handle the update_users_and_conversations service.""" await self._async_list_conversations() async def async_handle_reconnect(self, _=None): """Handle the reconnect service.""" await self.async_disconnect() await self.async_connect() def get_intents(self, conv_id): """Return the intents for a specific conversation.""" return self._conversation_intents.get(conv_id)
class HangoutsBot: """The Hangouts Bot.""" def __init__(self, hass, refresh_token, commands): """Set up the client.""" self.hass = hass self._connected = False self._refresh_token = refresh_token self._commands = commands self._word_commands = None self._expression_commands = None self._client = None self._user_list = None self._conversation_list = None def _resolve_conversation_name(self, name): for conv in self._conversation_list.get_all(): if conv.name == name: return conv return None def async_update_conversation_commands(self, _): """Refresh the commands for every conversation.""" self._word_commands = {} self._expression_commands = {} for command in self._commands: if command.get(CONF_CONVERSATIONS): conversations = [] for conversation in command.get(CONF_CONVERSATIONS): if 'id' in conversation: conversations.append(conversation['id']) elif 'name' in conversation: conversations.append(self._resolve_conversation_name( conversation['name']).id_) command['_' + CONF_CONVERSATIONS] = conversations else: command['_' + CONF_CONVERSATIONS] = \ [conv.id_ for conv in self._conversation_list.get_all()] if command.get(CONF_WORD): for conv_id in command['_' + CONF_CONVERSATIONS]: if conv_id not in self._word_commands: self._word_commands[conv_id] = {} word = command[CONF_WORD].lower() self._word_commands[conv_id][word] = command elif command.get(CONF_EXPRESSION): command['_' + CONF_EXPRESSION] = re.compile( command.get(CONF_EXPRESSION)) for conv_id in command['_' + CONF_CONVERSATIONS]: if conv_id not in self._expression_commands: self._expression_commands[conv_id] = [] self._expression_commands[conv_id].append(command) try: self._conversation_list.on_event.remove_observer( self._handle_conversation_event) except ValueError: pass self._conversation_list.on_event.add_observer( self._handle_conversation_event) def _handle_conversation_event(self, event): from hangups import ChatMessageEvent if event.__class__ is ChatMessageEvent: self._handle_conversation_message( event.conversation_id, event.user_id, event) def _handle_conversation_message(self, conv_id, user_id, event): """Handle a message sent to a conversation.""" user = self._user_list.get_user(user_id) if user.is_self: return _LOGGER.debug("Handling message '%s' from %s", event.text, user.full_name) event_data = None pieces = event.text.split(' ') cmd = pieces[0].lower() command = self._word_commands.get(conv_id, {}).get(cmd) if command: event_data = { 'command': command[CONF_NAME], 'conversation_id': conv_id, 'user_id': user_id, 'user_name': user.full_name, 'data': pieces[1:] } else: # After single-word commands, check all regex commands in the room for command in self._expression_commands.get(conv_id, []): match = command['_' + CONF_EXPRESSION].match(event.text) if not match: continue event_data = { 'command': command[CONF_NAME], 'conversation_id': conv_id, 'user_id': user_id, 'user_name': user.full_name, 'data': match.groupdict() } if event_data is not None: self.hass.bus.fire(EVENT_HANGOUTS_COMMAND, event_data) async def async_connect(self): """Login to the Google Hangouts.""" from .hangups_utils import HangoutsRefreshToken, HangoutsCredentials from hangups import Client from hangups import get_auth session = await self.hass.async_add_executor_job( get_auth, HangoutsCredentials(None, None, None), HangoutsRefreshToken(self._refresh_token)) self._client = Client(session) self._client.on_connect.add_observer(self._on_connect) self._client.on_disconnect.add_observer(self._on_disconnect) self.hass.loop.create_task(self._client.connect()) def _on_connect(self): _LOGGER.debug('Connected!') self._connected = True dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_CONNECTED) def _on_disconnect(self): """Handle disconnecting.""" _LOGGER.debug('Connection lost!') self._connected = False dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_DISCONNECTED) async def async_disconnect(self): """Disconnect the client if it is connected.""" if self._connected: await self._client.disconnect() async def async_handle_hass_stop(self, _): """Run once when Home Assistant stops.""" await self.async_disconnect() async def _async_send_message(self, message, targets): conversations = [] for target in targets: conversation = None if 'id' in target: conversation = self._conversation_list.get(target['id']) elif 'name' in target: conversation = self._resolve_conversation_name(target['name']) if conversation is not None: conversations.append(conversation) if not conversations: return False from hangups import ChatMessageSegment, hangouts_pb2 messages = [] for segment in message: if 'parse_str' in segment and segment['parse_str']: messages.extend(ChatMessageSegment.from_str(segment['text'])) else: if 'parse_str' in segment: del segment['parse_str'] messages.append(ChatMessageSegment(**segment)) messages.append(ChatMessageSegment('', segment_type=hangouts_pb2. SEGMENT_TYPE_LINE_BREAK)) if not messages: return False for conv in conversations: await conv.send_message(messages) async def _async_list_conversations(self): import hangups self._user_list, self._conversation_list = \ (await hangups.build_user_conversation_list(self._client)) conversations = {} for i, conv in enumerate(self._conversation_list.get_all()): users_in_conversation = [] for user in conv.users: users_in_conversation.append(user.full_name) conversations[str(i)] = {'id': str(conv.id_), 'name': conv.name, 'users': users_in_conversation} self.hass.states.async_set("{}.conversations".format(DOMAIN), len(self._conversation_list.get_all()), attributes=conversations) dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_CONVERSATIONS_CHANGED, conversations) async def async_handle_send_message(self, service): """Handle the send_message service.""" await self._async_send_message(service.data[ATTR_MESSAGE], service.data[ATTR_TARGET]) async def async_handle_update_users_and_conversations(self, _=None): """Handle the update_users_and_conversations service.""" await self._async_list_conversations()
class HangoutsBot: """The Hangouts Bot.""" def __init__(self, hass, refresh_token, intents, default_convs, error_suppressed_convs): """Set up the client.""" self.hass = hass self._connected = False self._refresh_token = refresh_token self._intents = intents self._conversation_intents = None self._client = None self._user_list = None self._conversation_list = None self._default_convs = default_convs self._default_conv_ids = None self._error_suppressed_convs = error_suppressed_convs self._error_suppressed_conv_ids = None dispatcher.async_dispatcher_connect( self.hass, EVENT_HANGOUTS_MESSAGE_RECEIVED, self._async_handle_conversation_message) def _resolve_conversation_id(self, obj): if CONF_CONVERSATION_ID in obj: return obj[CONF_CONVERSATION_ID] if CONF_CONVERSATION_NAME in obj: conv = self._resolve_conversation_name(obj[CONF_CONVERSATION_NAME]) if conv is not None: return conv.id_ return None def _resolve_conversation_name(self, name): for conv in self._conversation_list.get_all(): if conv.name == name: return conv return None def async_update_conversation_commands(self): """Refresh the commands for every conversation.""" self._conversation_intents = {} for intent_type, data in self._intents.items(): if data.get(CONF_CONVERSATIONS): conversations = [] for conversation in data.get(CONF_CONVERSATIONS): conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: conversations.append(conv_id) data['_' + CONF_CONVERSATIONS] = conversations elif self._default_conv_ids: data['_' + CONF_CONVERSATIONS] = self._default_conv_ids else: data['_' + CONF_CONVERSATIONS] = \ [conv.id_ for conv in self._conversation_list.get_all()] for conv_id in data['_' + CONF_CONVERSATIONS]: if conv_id not in self._conversation_intents: self._conversation_intents[conv_id] = {} self._conversation_intents[conv_id][intent_type] = data try: self._conversation_list.on_event.remove_observer( self._async_handle_conversation_event) except ValueError: pass self._conversation_list.on_event.add_observer( self._async_handle_conversation_event) def async_resolve_conversations(self, _): """Resolve the list of default and error suppressed conversations.""" self._default_conv_ids = [] self._error_suppressed_conv_ids = [] for conversation in self._default_convs: conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: self._default_conv_ids.append(conv_id) for conversation in self._error_suppressed_convs: conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: self._error_suppressed_conv_ids.append(conv_id) dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_CONVERSATIONS_RESOLVED) async def _async_handle_conversation_event(self, event): from hangups import ChatMessageEvent if isinstance(event, ChatMessageEvent): dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_MESSAGE_RECEIVED, event.conversation_id, event.user_id, event) async def _async_handle_conversation_message(self, conv_id, user_id, event): """Handle a message sent to a conversation.""" user = self._user_list.get_user(user_id) if user.is_self: return message = event.text _LOGGER.debug("Handling message '%s' from %s", message, user.full_name) intents = self._conversation_intents.get(conv_id) if intents is not None: is_error = False try: intent_result = await self._async_process(intents, message, conv_id) except (intent.UnknownIntent, intent.IntentHandleError) as err: is_error = True intent_result = intent.IntentResponse() intent_result.async_set_speech(str(err)) if intent_result is None: is_error = True intent_result = intent.IntentResponse() intent_result.async_set_speech( "Sorry, I didn't understand that") message = intent_result.as_dict().get('speech', {})\ .get('plain', {}).get('speech') if (message is not None) and not ( is_error and conv_id in self._error_suppressed_conv_ids): await self._async_send_message( [{'text': message, 'parse_str': True}], [{CONF_CONVERSATION_ID: conv_id}], None) async def _async_process(self, intents, text, conv_id): """Detect a matching intent.""" for intent_type, data in intents.items(): for matcher in data.get(CONF_MATCHERS, []): match = matcher.match(text) if not match: continue if intent_type == INTENT_HELP: return await self.hass.helpers.intent.async_handle( DOMAIN, intent_type, {'conv_id': {'value': conv_id}}, text) return await self.hass.helpers.intent.async_handle( DOMAIN, intent_type, {key: {'value': value} for key, value in match.groupdict().items()}, text) async def async_connect(self): """Login to the Google Hangouts.""" from .hangups_utils import HangoutsRefreshToken, HangoutsCredentials from hangups import Client from hangups import get_auth session = await self.hass.async_add_executor_job( get_auth, HangoutsCredentials(None, None, None), HangoutsRefreshToken(self._refresh_token)) self._client = Client(session) self._client.on_connect.add_observer(self._on_connect) self._client.on_disconnect.add_observer(self._on_disconnect) self.hass.loop.create_task(self._client.connect()) def _on_connect(self): _LOGGER.debug('Connected!') self._connected = True dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_CONNECTED) async def _on_disconnect(self): """Handle disconnecting.""" if self._connected: _LOGGER.debug('Connection lost! Reconnect...') await self.async_connect() else: dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_DISCONNECTED) async def async_disconnect(self): """Disconnect the client if it is connected.""" if self._connected: self._connected = False await self._client.disconnect() async def async_handle_hass_stop(self, _): """Run once when Home Assistant stops.""" await self.async_disconnect() async def _async_send_message(self, message, targets, data): conversations = [] for target in targets: conversation = None if CONF_CONVERSATION_ID in target: conversation = self._conversation_list.get( target[CONF_CONVERSATION_ID]) elif CONF_CONVERSATION_NAME in target: conversation = self._resolve_conversation_name( target[CONF_CONVERSATION_NAME]) if conversation is not None: conversations.append(conversation) if not conversations: return False from hangups import ChatMessageSegment, hangouts_pb2 messages = [] for segment in message: if messages: messages.append(ChatMessageSegment('', segment_type=hangouts_pb2. SEGMENT_TYPE_LINE_BREAK)) if 'parse_str' in segment and segment['parse_str']: messages.extend(ChatMessageSegment.from_str(segment['text'])) else: if 'parse_str' in segment: del segment['parse_str'] messages.append(ChatMessageSegment(**segment)) image_file = None if data: if data.get('image_url'): uri = data.get('image_url') try: websession = async_get_clientsession(self.hass) async with websession.get(uri, timeout=5) as response: if response.status != 200: _LOGGER.error( 'Fetch image failed, %s, %s', response.status, response ) image_file = None else: image_data = await response.read() image_file = io.BytesIO(image_data) image_file.name = "image.png" except (asyncio.TimeoutError, aiohttp.ClientError) as error: _LOGGER.error( 'Failed to fetch image, %s', type(error) ) image_file = None elif data.get('image_file'): uri = data.get('image_file') if self.hass.config.is_allowed_path(uri): try: image_file = open(uri, 'rb') except IOError as error: _LOGGER.error( 'Image file I/O error(%s): %s', error.errno, error.strerror ) else: _LOGGER.error('Path "%s" not allowed', uri) if not messages: return False for conv in conversations: await conv.send_message(messages, image_file) async def _async_list_conversations(self): import hangups self._user_list, self._conversation_list = \ (await hangups.build_user_conversation_list(self._client)) conversations = {} for i, conv in enumerate(self._conversation_list.get_all()): users_in_conversation = [] for user in conv.users: users_in_conversation.append(user.full_name) conversations[str(i)] = {CONF_CONVERSATION_ID: str(conv.id_), CONF_CONVERSATION_NAME: conv.name, 'users': users_in_conversation} self.hass.states.async_set("{}.conversations".format(DOMAIN), len(self._conversation_list.get_all()), attributes=conversations) dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_CONVERSATIONS_CHANGED, conversations) async def async_handle_send_message(self, service): """Handle the send_message service.""" await self._async_send_message(service.data[ATTR_MESSAGE], service.data[ATTR_TARGET], service.data.get(ATTR_DATA, {})) async def async_handle_update_users_and_conversations(self, _=None): """Handle the update_users_and_conversations service.""" await self._async_list_conversations() async def async_handle_reconnect(self, _=None): """Handle the reconnect service.""" await self.async_disconnect() await self.async_connect() def get_intents(self, conv_id): """Return the intents for a specific conversation.""" return self._conversation_intents.get(conv_id)
class HangoutsBot: """The Hangouts Bot.""" def __init__(self, hass, refresh_token, intents, error_suppressed_convs): """Set up the client.""" self.hass = hass self._connected = False self._refresh_token = refresh_token self._intents = intents self._conversation_intents = None self._client = None self._user_list = None self._conversation_list = None self._error_suppressed_convs = error_suppressed_convs self._error_suppressed_conv_ids = None dispatcher.async_dispatcher_connect( self.hass, EVENT_HANGOUTS_MESSAGE_RECEIVED, self._async_handle_conversation_message) def _resolve_conversation_id(self, obj): if CONF_CONVERSATION_ID in obj: return obj[CONF_CONVERSATION_ID] if CONF_CONVERSATION_NAME in obj: conv = self._resolve_conversation_name(obj[CONF_CONVERSATION_NAME]) if conv is not None: return conv.id_ return None def _resolve_conversation_name(self, name): for conv in self._conversation_list.get_all(): if conv.name == name: return conv return None def async_update_conversation_commands(self, _): """Refresh the commands for every conversation.""" self._conversation_intents = {} for intent_type, data in self._intents.items(): if data.get(CONF_CONVERSATIONS): conversations = [] for conversation in data.get(CONF_CONVERSATIONS): conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: conversations.append(conv_id) data['_' + CONF_CONVERSATIONS] = conversations else: data['_' + CONF_CONVERSATIONS] = \ [conv.id_ for conv in self._conversation_list.get_all()] for conv_id in data['_' + CONF_CONVERSATIONS]: if conv_id not in self._conversation_intents: self._conversation_intents[conv_id] = {} self._conversation_intents[conv_id][intent_type] = data try: self._conversation_list.on_event.remove_observer( self._async_handle_conversation_event) except ValueError: pass self._conversation_list.on_event.add_observer( self._async_handle_conversation_event) def async_handle_update_error_suppressed_conversations(self, _): """Resolve the list of error suppressed conversations.""" self._error_suppressed_conv_ids = [] for conversation in self._error_suppressed_convs: conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: self._error_suppressed_conv_ids.append(conv_id) async def _async_handle_conversation_event(self, event): from hangups import ChatMessageEvent if isinstance(event, ChatMessageEvent): dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_MESSAGE_RECEIVED, event.conversation_id, event.user_id, event) async def _async_handle_conversation_message(self, conv_id, user_id, event): """Handle a message sent to a conversation.""" user = self._user_list.get_user(user_id) if user.is_self: return message = event.text _LOGGER.debug("Handling message '%s' from %s", message, user.full_name) intents = self._conversation_intents.get(conv_id) if intents is not None: is_error = False try: intent_result = await self._async_process(intents, message) except (intent.UnknownIntent, intent.IntentHandleError) as err: is_error = True intent_result = intent.IntentResponse() intent_result.async_set_speech(str(err)) if intent_result is None: is_error = True intent_result = intent.IntentResponse() intent_result.async_set_speech( "Sorry, I didn't understand that") message = intent_result.as_dict().get('speech', {})\ .get('plain', {}).get('speech') if (message is not None) and not ( is_error and conv_id in self._error_suppressed_conv_ids): await self._async_send_message( [{'text': message, 'parse_str': True}], [{CONF_CONVERSATION_ID: conv_id}]) async def _async_process(self, intents, text): """Detect a matching intent.""" for intent_type, data in intents.items(): for matcher in data.get(CONF_MATCHERS, []): match = matcher.match(text) if not match: continue response = await self.hass.helpers.intent.async_handle( DOMAIN, intent_type, {key: {'value': value} for key, value in match.groupdict().items()}, text) return response async def async_connect(self): """Login to the Google Hangouts.""" from .hangups_utils import HangoutsRefreshToken, HangoutsCredentials from hangups import Client from hangups import get_auth session = await self.hass.async_add_executor_job( get_auth, HangoutsCredentials(None, None, None), HangoutsRefreshToken(self._refresh_token)) self._client = Client(session) self._client.on_connect.add_observer(self._on_connect) self._client.on_disconnect.add_observer(self._on_disconnect) self.hass.loop.create_task(self._client.connect()) def _on_connect(self): _LOGGER.debug('Connected!') self._connected = True dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_CONNECTED) def _on_disconnect(self): """Handle disconnecting.""" _LOGGER.debug('Connection lost!') self._connected = False dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_DISCONNECTED) async def async_disconnect(self): """Disconnect the client if it is connected.""" if self._connected: await self._client.disconnect() async def async_handle_hass_stop(self, _): """Run once when Home Assistant stops.""" await self.async_disconnect() async def _async_send_message(self, message, targets): conversations = [] for target in targets: conversation = None if CONF_CONVERSATION_ID in target: conversation = self._conversation_list.get( target[CONF_CONVERSATION_ID]) elif CONF_CONVERSATION_NAME in target: conversation = self._resolve_conversation_name( target[CONF_CONVERSATION_NAME]) if conversation is not None: conversations.append(conversation) if not conversations: return False from hangups import ChatMessageSegment, hangouts_pb2 messages = [] for segment in message: if 'parse_str' in segment and segment['parse_str']: messages.extend(ChatMessageSegment.from_str(segment['text'])) else: if 'parse_str' in segment: del segment['parse_str'] messages.append(ChatMessageSegment(**segment)) messages.append(ChatMessageSegment('', segment_type=hangouts_pb2. SEGMENT_TYPE_LINE_BREAK)) if not messages: return False for conv in conversations: await conv.send_message(messages) async def _async_list_conversations(self): import hangups self._user_list, self._conversation_list = \ (await hangups.build_user_conversation_list(self._client)) conversations = {} for i, conv in enumerate(self._conversation_list.get_all()): users_in_conversation = [] for user in conv.users: users_in_conversation.append(user.full_name) conversations[str(i)] = {CONF_CONVERSATION_ID: str(conv.id_), CONF_CONVERSATION_NAME: conv.name, 'users': users_in_conversation} self.hass.states.async_set("{}.conversations".format(DOMAIN), len(self._conversation_list.get_all()), attributes=conversations) dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_CONVERSATIONS_CHANGED, conversations) async def async_handle_send_message(self, service): """Handle the send_message service.""" await self._async_send_message(service.data[ATTR_MESSAGE], service.data[ATTR_TARGET]) async def async_handle_update_users_and_conversations(self, _=None): """Handle the update_users_and_conversations service.""" await self._async_list_conversations()