222 lines
8.5 KiB
Python
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()
|