mcevent.py
author Mikael Berthe <mikael@lilotux.net>
Sat, 24 Jun 2017 11:20:48 +0200
changeset 15 d4c85df8d0b8
parent 14 02d532e70dfb
permissions -rwxr-xr-x
Switch to notify2

#! /usr/bin/env python
# -*- coding: iso-8859-15 -*-
#
#  Copyright (C) 2007 Adam Wolk "Mulander" <netprobe@gmail.com>
#  Copyright (C) 2007, 2008 Mikael Berthe "McKael" <mikael@lilotux.net>
#
# This script is provided under the terms of the GNU General Public License,
# see the file COPYING in this directory.
#
"""
This script reads mcabber event lines from the standard input
and creates events accordingly.  Examples:
STATUS O user2@domain.org
MSG IN user@domain.org
UNREAD 1 0 1 0
"""

import sys, getopt
import os
import notify2

CONFFILE = "mcevent.cfg"

# Default option values
OPT = {
        'use_notify': 0,
        'use_voice':  0,
        'use_sound':  1,
        'short_nick': 1,
        'unread_file': '',
        'snd_cmd_msg_in': '/usr/bin/play -V0 -v3 sound.wav',
}

NOTIFY_TIMEOUT = 4000

CONTACT_MAP = { }
# Nickname used for the notification

CONTACT_CUSTOM_MSG = { }
# Message used for the notification

ONLINE_ALERTS = { }
# 0: disabled  1: normal notification  2: permanent notify box

VOICEMAP = { }
# Specify the name pronounced by espeak

BLACKLIST = { }
# No notification for these JIDs

CMD_ESPEAK = "/usr/bin/espeak"

ENCODING = ''

def init_notify():
    """
    Initialize the notify subsystem.
    """
    import locale
    global ENCODING, NOTIFY_LOADED
    notify2.init('mcnotify')
    ENCODING = (locale.getdefaultlocale())[1]
    NOTIFY_LOADED = True

def read_conf_from_file():
    """
    Read the configuration file.
    """
    import ConfigParser

    config = ConfigParser.ConfigParser()
    config.read(CONFFILE)

    CONTACT_MAP.clear()
    CONTACT_CUSTOM_MSG.clear()
    ONLINE_ALERTS.clear()
    VOICEMAP.clear()
    BLACKLIST.clear()

    if config.has_option("Notifications", "notify"):
        OPT['use_notify'] = int(config.get("Notifications", "notify"))
    if config.has_option("Notifications", "voice"):
        OPT['use_voice']  = int(config.get("Notifications", "voice"))
    if config.has_option("Notifications", "sound"):
        OPT['use_sound']  = int(config.get("Notifications", "sound"))
    if config.has_option("Notifications", "short_nick"):
        OPT['short_nick'] = int(config.get("Notifications", "short_nick"))
    if config.has_option("Notifications", "unread_file"):
        OPT['unread_file'] = config.get("Notifications", "unread_file")
    if config.has_option("Notifications", "snd_cmd_msg_in"):
        OPT['snd_cmd_msg_in'] = config.get("Notifications", "snd_cmd_msg_in")

    if config.has_section("Contacts"):
        for cid in config.options("Contacts"):
            CONTACT_MAP[cid] = config.get("Contacts", cid)

    if config.has_section("Contact_Customized_Messages"):
        for cid in config.options("Contact_Customized_Messages"):
            CONTACT_CUSTOM_MSG[cid] = config.get("Contact_Customized_Messages",
                                                 cid)

    if config.has_section("Alerts"):
        for cid in config.options("Alerts"):
            ONLINE_ALERTS[cid] = int(config.get("Alerts", cid))

    if config.has_section("Voicemap"):
        for cid in config.options("Voicemap"):
            VOICEMAP[cid] = config.get("Voicemap", cid)

    if config.has_section("Blacklist"):
        for cid in config.options("Blacklist"):
            BLACKLIST[cid] = int(config.get("Blacklist", cid))

    if OPT['use_notify'] and not NOTIFY_LOADED:
        init_notify()

def say(buddy, text):
    """
    Create a subprocess and run a speech synthesizer.
    """
    import subprocess
    p = subprocess.Popen(CMD_ESPEAK, stdin=subprocess.PIPE, close_fds=True)
    child_stdin = p.stdin
    child_stdin.write(buddy + " " + text)
    child_stdin.close()

def notify(buddy, msg, timeout):
    """
    Create a notify popup.
    """
    msgbox = notify2.Notification(unicode(buddy, ENCODING),
                                   unicode(msg, ENCODING))
    msgbox.set_timeout(timeout)
    msgbox.set_urgency(notify2.URGENCY_LOW)
    msgbox.show()

def get_nick(jid):
    """
    Return the nick of the given contact (JID).
    """
    if jid in CONTACT_MAP:
        buddy = CONTACT_MAP[jid]
    else:
        buddy = jid

    if OPT['short_nick'] and '@' in buddy:
        buddy = buddy[0:buddy.index('@')]
    return buddy

def is_blacklisted(jid):
    """
    Return True if the given contcat (JID) is blacklisted.
    """
    if jid in BLACKLIST and BLACKLIST[jid]:
        return True
    return False

def process_line(args):
    """
    Parse the provided line and run events.
    """
    argn = len(args)

    if argn < 2:
        print "Ignoring invalid event line."
        return
    elif argn == 2:
        event, arg1          = args[0:2]
        arg2                 = None
        filename             = None
    elif argn == 3:
        event, arg1, arg2    = args[0:3]
        filename             = None
    else:
        # For now we simply ignore the file name
        event, arg1, arg2    = args[0:3]
        filename             = None

    if event == 'MSG' and arg1 == 'IN':
        jid = arg2
        if not jid:
            print "Ignoring invalid MSG event line."
            return

        buddy = get_nick(jid)
        if jid in CONTACT_CUSTOM_MSG:
            msg = CONTACT_CUSTOM_MSG[jid]
        elif 'default' in CONTACT_CUSTOM_MSG:
            msg = CONTACT_CUSTOM_MSG['default']
        else:
            msg = 'sent you a message.'

        if not is_blacklisted(jid):
            textmsg = None

            if filename and os.path.exists(filename):
                fileh = file(filename)
                textmsg = fileh.read()

            if OPT['use_notify']:
                if not textmsg:
                    textmsg = msg
                notify(buddy, textmsg, NOTIFY_TIMEOUT)

            if OPT['use_sound']:
                os.system(OPT['snd_cmd_msg_in'] + '> /dev/null 2>&1')

            if OPT['use_voice'] and not is_blacklisted(jid):
                if jid in VOICEMAP:
                    buddy = VOICEMAP[jid]
                say(buddy, msg)

        if filename and os.path.exists(filename):
            os.remove(filename)

    elif event == 'STATUS':
        jid = arg2
        if arg1 == 'O' and jid in ONLINE_ALERTS:
            alert_type = ONLINE_ALERTS[jid]
            if alert_type > 0:
                if OPT['use_sound']:
                    os.system(OPT['snd_cmd_msg_in'] + '> /dev/null 2>&1')

                buddy = get_nick(jid)

                if OPT['use_notify']:
                    if alert_type == 1:
                        timeout = NOTIFY_TIMEOUT
                    else:
                        timeout = notify2.EXPIRES_NEVER
                    notify(buddy, "is online", timeout)

                    if filename and os.path.exists(filename):
                        os.remove(filename)

                if OPT['use_voice']:
                    if jid in VOICEMAP:
                        buddy = VOICEMAP[jid]
                    say(buddy, "is online now.")

    elif event == 'UNREAD':
        unread_all, unread_attn     = map(int, args[1:3])
        unread_muc, unread_muc_attn = map(int, args[3:5])
        unread = unread_all - (unread_muc - unread_muc_attn)
        if unread == unread_all:
            unread_str = str(unread)
        else:
            unread_str = "%d/%d" % (unread, unread_all)
        if OPT['unread_file'] != '':
            fileh = open(OPT['unread_file'], 'w')
            fileh.write(unread_str)
            fileh.close()


##### MAIN #####

try:
    opts, cargs = getopt.getopt(sys.argv[1:], "c:", ["help", "output="])
except getopt.GetoptError, err:
    print str(err)
    sys.exit(2)

for o, a in opts:
    if o == "-c":
        CONFFILE = a

try:
    last_conf_read = os.stat(CONFFILE).st_ctime
except OSError:
    sys.stderr.write("Cannot read config file!\n")
    sys.exit(3)

NOTIFY_LOADED = False

read_conf_from_file()

# Read stdin line by line, and process the commands
while 1:
    try:
        line = sys.stdin.readline()
    except KeyboardInterrupt:
        print "\nInterrupted!"
        sys.exit(0)
    if not line:
        break

    last_conf_change = os.stat(CONFFILE).st_ctime
    if last_conf_change > last_conf_read:
        read_conf_from_file()
        last_conf_read = last_conf_change
    lineargs = line.split()
    process_line(lineargs)

if NOTIFY_LOADED:
    notify2.uninit()

sys.exit(0)

# vim:set si et sts=4 sw=4: