From eBower Wiki
Jump to: navigation, search
(Created page with "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 gro...")
 
(Installation)
Line 15: Line 15:
 
= Installation =
 
= Installation =
  
First we need to install pip:
+
First we need to install pip and then [http://hangups.readthedocs.io/en/stable/index.html|Hangups]:
 
<syntaxhighlight lang="bash">
 
<syntaxhighlight lang="bash">
 
sudo apt-get install python3-pip
 
sudo apt-get install python3-pip
Line 21: Line 21:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
You'll need a login token. There are a few options:
 +
# On a desktop machine, just log into the proper Google account from a browser.
 +
# On a headless machine, you can install lynx to log in.
 +
# Alternatively, if you've got Hangups running on one PC you can pass the <tt>~/.cache/hangups/refresh_token</tt> 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.
 +
 +
Now we'll need a script to send a message. Stealing from the example scripts I present you <tt>send_hangouts_message</tt>:
 +
<syntaxhighlight lang="python">
 +
#!/usr/bin/python3
 +
 +
"""
 +
References:
 
https://hibern8.wordpress.com/2016/04/14/send-hangouts-message-from-command-line-on-debian/
 
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')
 +
</syntaxhighlight>

Revision as of 15:55, 30 December 2016

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

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.

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')