core/homeassistant/components/hangouts/hangouts_bot.py

222 lines
8.5 KiB
Python

"""The Hangouts Bot."""
import logging
import re
from homeassistant.helpers import dispatcher
from .const import (
ATTR_MESSAGE, ATTR_TARGET, CONF_CONVERSATIONS, CONF_EXPRESSION, CONF_NAME,
CONF_WORD, DOMAIN, EVENT_HANGOUTS_COMMAND, EVENT_HANGOUTS_CONNECTED,
EVENT_HANGOUTS_CONVERSATIONS_CHANGED, EVENT_HANGOUTS_DISCONNECTED)
_LOGGER = logging.getLogger(__name__)
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()