Charcoal-SE/SmokeDetector-ng

View on GitHub
src/chat.py

Summary

Maintainability
B
4 hrs
Test Coverage
# vim: set filetype=python tabstop=4 shiftwidth=4 expandtab:

from ChatExchange.chatexchange import client as chatclient, events
import collections
import itertools
import json

from database import SESSION, SmokeyMessage
from excepthook import excepthook
import command_dispatch
import config
import git
import secrets

_init = False

_clients = {
    "stackexchange.com": None,
    "stackoverflow.com": None,
    "meta.stackexchange.com": None
}

_room_permissions = None
_rooms = {}


def require_chat(function):
    def f(*args, **kwargs):
        assert _init
        return function(*args, **kwargs)

    return f


@secrets.require_secrets
def init():
    global _clients
    global _init
    global _room_permissions
    global _rooms
    global _last_messages

    for site in _clients.keys():
        client = chatclient.Client(site)
        
        for _ in range(10):
            try:
                client.login(secrets.email.value, secrets.pw.value)
                break
            except:
                pass
        else:
            raise Exception("Failed to log into " + site)

        _clients[site] = client

    _room_permissions = parse_room_config()

    for site, roomid in _room_permissions["commands"]:
        room = _clients[site].get_room(roomid)

        room.join()
        room.watch_socket(excepthook(lambda msg, client: on_msg(msg, client, room)))
        _rooms[(site, roomid)] = room

    _init = True


def parse_room_config():
    with open("../config/rooms.json", "r") as room_config:
        room_dict = json.load(room_config)

        rooms = {}

        for site, site_rooms in room_dict.items():
            for roomid, room in site_rooms.items():
                room_identifier = (site, roomid)

                for perm in room["msg_types"]:
                    if perm not in rooms:
                        rooms[perm] = set()

                    rooms[perm].add(room_identifier)

    return rooms


@require_chat
def on_msg(msg, client, room):
    if isinstance(msg, events.MessagePosted) or isinstance(msg, events.MessageEdited):
        message = msg.message

        if message.owner.id in config.my_ids:
            SmokeyMessage.create(chat_site_url=client.host, room_id=room.id, message_id=message.id, is_report=False)
        elif message.parent and message.parent.owner.id in config.my_ids:
            command = message.content.split(" ", 1)[1]

            message.reply(command_dispatch.dispatch_reply_command(message.parent, message, command))
        elif message.content.startswith(config.shorthand_prefix):
            message.reply(command_dispatch.dispatch_shorthand_command(message, room))
        elif message.content.startswith(config.command_prefix):
            message.reply(command_dispatch.dispatch_command(message))


@require_chat
def send_to_room(room, msg, **kwargs):
    msg = msg.rstrip()

    if kwargs.get('prefix'):
        msg = "[ [SmokeDetector-ng]({}) ] ".format(config.github) + msg

    room.send_message(msg)


@require_chat
def tell_rooms_with(prop, msg, **kwargs):
    tell_rooms(msg, (prop,), (), **kwargs)


@require_chat
def tell_rooms_without(prop, msg, **kwargs):
    tell_rooms(msg, (), (prop,), **kwargs)


@require_chat
def tell_rooms(msg, has, hasnt, **kwargs):
    global _rooms

    target_rooms = set()

    for prop_has in has:
        for room in _room_permissions[prop_has]:
            if all(map(lambda prop_hasnt: room not in _room_permissions[prop_hasnt], hasnt)):
                if room not in _rooms:
                    site, roomid = room

                    new_room = _clients[site].get_room(roomid)
                    new_room.join()

                    _rooms[room] = new_room

                target_rooms.add(_rooms[room])

    for room in target_rooms:
        send_to_room(room, msg, **kwargs)


@require_chat
def handle_start():
    tell_rooms_with("debug", "SmokeDetector-ng started at revision [{}]({})."
                    .format(git.short_rev(), config.github + "/commit/" + git.rev()),
                    prefix=True)


@require_chat
def handle_signal(signal):
    tell_rooms_with("debug", "Recovered from signal %d." % signal)


@require_chat
def handle_err():
    tell_rooms_with("debug", "Recovered from exception.")


@require_chat
def get_last_messages(room, count):
    messages = SESSION.query(SmokeyMessage.message_id).filter(SmokeyMessage.chat_site_url == room._client.host,
                                                              SmokeyMessage.room_id == room.id)

    for msg_id, in messages.order_by(SmokeyMessage.message_id.desc()).limit(count):
        yield room._client.get_message(msg_id)