From eBower Wiki
Jump to: navigation, search

Email notifications are a bit hard to manage, they're really designed for non-realtime messaging when you're ready to read which means that email notifications tend to get grouped together and ignored, spam gets them lost, and non-reliable transport makes them hit-or-miss. Instant messaging is a better solution to this problem.

The Options

Obviously email is there. But email is tricky to set up, many command line solutions need a full server and a bit of lax security turns that simple notification system into a mass spam source getting your home IP address blacklisted. Email isn't going anywhere, but it really does need to die...

Of course you've got the venerable text message. Most of the time you can access this via an email, exposing all the problems above. There are some sites that offer a web API for sending texts, but again these are often abused as mobile spam grows so I don't deem them to be reliable.

GroupMe is an excellent solution for this with a API, but it means installing Yet Another Messaging App (YAMA) and my GroupMe notifications are low priority because they're all chatty personal groups. Besides, if there's a published API what's the fun in using it?

Slack could be another good solution, but my experience with it is limited and it's YAMA.

Hangouts is my go-to messaging app, text and IM with a mix of conversations with notifications and muted conversations. It's an ideal app for me to piggyback notifications onto.

Installation

First we need to install pip and then [1]:

sudo apt-get install python3-pip
pip3 install hangups

Initial Configuration

You'll need a login token. There are a few options:

  1. On a desktop machine, just log into the proper Google account from a browser.
  2. On a headless machine, you can install lynx to log in.
  3. Alternatively, if you've got Hangups running on one PC you can pass the ~/.cache/hangups/refresh_token from one machine to another - understanding that you'll be pairing those two devices and won't be able to kill one without killing the other.

It's easiest to log into a browser and hit [2] to create conversations. From there you can run hangups and view a list of them. Send a message to the one you're interested in. You can now look in ~/.cache/hangups/logs/hangups.log to see the conversation_id for the channel you want to send to - it's a long series of characters like this: Uddg2Lf53526Xy666N9l4AaABAagBvZH_DQ

Messaging Script

Now we'll need a script to send a message. Stealing from the example scripts I present you send_hangouts_message:

#!/usr/bin/python3
 
"""
References:
https://hibern8.wordpress.com/2016/04/14/send-hangouts-message-from-command-line-on-debian/
http://hangups.readthedocs.io/en/stable/index.html
"""
 
import argparse
import asyncio
import logging
import os
 
import hangups
import appdirs
 
def run_example(example_coroutine, *extra_args):
    args = _get_parser(extra_args).parse_args()
    logging.basicConfig(level=logging.DEBUG if args.debug else logging.WARNING)
    # Obtain hangups authentication cookies, prompting for credentials from
    # standard input if necessary.
    cookies = hangups.auth.get_auth_stdin(args.token_path)
    client = hangups.Client(cookies)
    task = asyncio.async(_async_main(example_coroutine, client, args))
    loop = asyncio.get_event_loop()
 
    try:
        loop.run_until_complete(task)
    except KeyboardInterrupt:
        task.cancel()
        loop.run_forever()
    finally:
        loop.close()
 
 
def _get_parser(extra_args):
    """Return ArgumentParser with any extra arguments."""
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    dirs = appdirs.AppDirs('hangups', 'hangups')
    default_token_path = os.path.join(dirs.user_cache_dir, 'refresh_token.txt')
    parser.add_argument(
        '--token-path', default=default_token_path,
        help='path used to store OAuth refresh token'
    )
    parser.add_argument(
        '-d', '--debug', action='store_true',
        help='log detailed debugging messages'
    )
    for extra_arg in extra_args:
        parser.add_argument(extra_arg, required=True)
    return parser
 
@asyncio.coroutine
def _async_main(example_coroutine, client, args):
    """Run the example coroutine."""
    # Spawn a task for hangups to run in parallel with the example coroutine.
    task = asyncio.async(client.connect())
 
    # Wait for hangups to either finish connecting or raise an exception.
    on_connect = asyncio.Future()
    client.on_connect.add_observer(lambda: on_connect.set_result(None))
    done, _ = yield from asyncio.wait(
        (on_connect, task), return_when=asyncio.FIRST_COMPLETED
    )
    yield from asyncio.gather(*done)
 
    # Run the example coroutine. Afterwards, disconnect hangups gracefully and
    # yield the hangups task to handle any exceptions.
    try:
        yield from example_coroutine(client, args)
    finally:
        yield from client.disconnect()
        yield from task
 
@asyncio.coroutine
def send_message(client, args):
    request = hangups.hangouts_pb2.SendChatMessageRequest(
        request_header=client.get_request_header(),
        event_request_header=hangups.hangouts_pb2.EventRequestHeader(
            conversation_id=hangups.hangouts_pb2.ConversationId(
                id=args.conversation_id
            ),
            client_generated_id=client.get_client_generated_id(),
        ),
        message_content=hangups.hangouts_pb2.MessageContent(
            segment=[
                hangups.ChatMessageSegment(args.message_text).serialize()
            ],
        ),
    )
    yield from client.send_chat_message(request)
 
 
if __name__ == '__main__':
    run_example(send_message, '--conversation-id', '--message-text')

To send a message, anyplace you'd usually use sendmail or similar just run:

send_hangouts_message --conversation-id Uddg2Lf53526Xy666N9l4AaABAagBvZH_DQ --message "Your text here"