class MatrixHandler():
    def __init__(self, username, password, room, gotMsgCallback=blackhole):
        try:
            from matrix_client.client import MatrixClient
        except ImportError:
            print("ERR:matrix_client import fail: Please exec:")
            print("cd ./matrix-python-sdk")
            print("pip install ./")
            raise RuntimeError("Failed to import matrix_client sdk")
        self.matrix = MatrixClient("https://matrix.org")
        self.matrix.login_with_password(username=username, password=password)
        self.matrix.start_listener_thread()
        self.room = self.matrix.join_room(room)
        self.room.send_text("Hello!")
        self.room.add_listener(gotMsgCallback)
        #self.gotMsgCallback=gotMsgCallback
    def sendMsg(self, msgData):
        res = self.room.send_text(msgData)
        return res

    def sendImg(self, imgdir, content_type="image/jpeg"):
        with open(imgdir, mode="rb") as f:
            uri = self.matrix.upload(f.read(), content_type)
            self.room.send_image(uri, 'wximg')
            f.close()

    def sendAudio(self, fdir, content_type="audio/mp3"):
        with open(fdir, mode="rb") as f:
            uri = self.matrix.upload(f.read(), content_type)
            self.room.send_audio(uri, 'wxaudio')
            f.close()

    def sendHtml(self, htmlData):
        res = self.room.send_html(htmlData)
        return res

    '''
Exemple #2
0
def main():

    tmp_dir = conf.zbx_matrix_tmp_dir
    if tmp_dir == "/tmp/" + conf.zbx_tg_prefix:
        print_message(
            "WARNING: it is strongly recommended to change `zbx_tg_tmp_dir` variable in config!!!"
        )
        print_message(
            "https://github.com/ableev/Zabbix-in-Telegram/wiki/Change-zbx_tg_tmp_dir-in-settings"
        )

    tmp_cookie = tmp_dir + "/cookie.py.txt"
    tmp_uids = tmp_dir + "/uids.txt"
    tmp_need_update = False  # do we need to update cache file with uids or not

    rnd = random.randint(0, 999)
    ts = time.time()
    hash_ts = str(ts) + "." + str(rnd)

    log_file = conf.log_path

    args = sys.argv

    settings = {
        "zbxtg_itemid": "0",  # itemid for graph
        "zbxtg_title": None,  # title for graph
        "zbxtg_image_period": None,
        "zbxtg_image_age": "3600",
        "zbxtg_image_width": "900",
        "zbxtg_image_height": "200",
        "tg_method_image":
        False,  # if True - default send images, False - send text
        "is_debug": False,
        "is_channel": False,
        "disable_web_page_preview": False,
        "location": None,  # address
        "lat": 0,  # latitude
        "lon": 0,  # longitude
        "is_single_message": False,
        "markdown": False,
        "html": False,
        "signature": None,
        "signature_disable": False,
        "graph_buttons": False,
        "extimg": None,
        "to": None,
        "to_group": None,
        "forked": False,
    }

    settings_description = {
        "itemid": {
            "name": "zbxtg_itemid",
            "type": "list",
            "help":
            "script will attach a graph with that itemid (could be multiple)",
            "url": "Graphs"
        },
        "title": {
            "name": "zbxtg_title",
            "type": "str",
            "help": "title for attached graph",
            "url": "Graphs"
        },
        "graphs_period": {
            "name": "zbxtg_image_period",
            "type": "int",
            "help": "graph period",
            "url": "Graphs"
        },
        "graphs_age": {
            "name": "zbxtg_image_age",
            "type": "str",
            "help": "graph period as age",
            "url": "Graphs"
        },
        "graphs_width": {
            "name": "zbxtg_image_width",
            "type": "int",
            "help": "graph width",
            "url": "Graphs"
        },
        "graphs_height": {
            "name": "zbxtg_image_height",
            "type": "int",
            "help": "graph height",
            "url": "Graphs"
        },
        "graphs": {
            "name": "tg_method_image",
            "type": "bool",
            "help": "enables graph sending",
            "url": "Graphs"
        },
        "chat": {
            "name": "tg_chat",
            "type": "bool",
            "help": "deprecated, don't use it, see 'group'",
            "url": "How-to-send-message-to-the-group-chat"
        },
        "group": {
            "name": "tg_group",
            "type": "bool",
            "help": "sends message to a group",
            "url": "How-to-send-message-to-the-group-chat"
        },
        "debug": {
            "name": "is_debug",
            "type": "bool",
            "help": "enables 'debug'",
            "url": "How-to-test-script-in-command-line"
        },
        "channel": {
            "name": "is_channel",
            "type": "bool",
            "help": "sends message to a channel",
            "url": "Channel-support"
        },
        "disable_web_page_preview": {
            "name": "disable_web_page_preview",
            "type": "bool",
            "help": "disable web page preview",
            "url": "Disable-web-page-preview"
        },
        "location": {
            "name": "location",
            "type": "str",
            "help": "address of location",
            "url": "Location"
        },
        "lat": {
            "name": "lat",
            "type": "str",
            "help": "specify latitude (and lon too!)",
            "url": "Location"
        },
        "lon": {
            "name": "lon",
            "type": "str",
            "help": "specify longitude (and lat too!)",
            "url": "Location"
        },
        "single_message": {
            "name": "is_single_message",
            "type": "bool",
            "help": "do not split message and graph",
            "url": "Why-am-I-getting-two-messages-instead-of-one"
        },
        "markdown": {
            "name": "markdown",
            "type": "bool",
            "help": "markdown support",
            "url": "Markdown-and-HTML"
        },
        "html": {
            "name": "html",
            "type": "bool",
            "help": "markdown support",
            "url": "Markdown-and-HTML"
        },
        "signature": {
            "name": "signature",
            "type": "str",
            "help": "bot's signature",
            "url": "Bot-signature"
        },
        "signature_disable": {
            "name": "signature_disable",
            "type": "bool",
            "help": "enables/disables bot's signature",
            "url": "Bot-signature"
        },
        "graph_buttons": {
            "name": "graph_buttons",
            "type": "bool",
            "help":
            "activates buttons under graph, could be using in ZbxTgDaemon",
            "url": "Interactive-bot"
        },
        "external_image": {
            "name": "extimg",
            "type": "str",
            "help":
            "should be url; attaches external image from different source",
            "url": "External-image-as-graph"
        },
        "to": {
            "name": "to",
            "type": "str",
            "help": "rewrite zabbix username, use that instead of arguments",
            "url": "Custom-to-and-to_group"
        },
        "to_group": {
            "name": "to_group",
            "type": "str",
            "help": "rewrite zabbix username, use that instead of arguments",
            "url": "Custom-to-and-to_group"
        },
        "forked": {
            "name": "forked",
            "type": "bool",
            "help": "internal variable, do not use it. Ever.",
            "url": ""
        },
    }

    if len(args) < 4:
        do_not_exit = False
        if "--features" in args:
            print(("List of available settings, see {0}/Settings\n---".format(
                url_wiki_base)))
            for sett, proprt in list(settings_description.items()):
                print(("{0}: {1}\ndoc: {2}/{3}\n--".format(
                    sett, proprt["help"], url_wiki_base, proprt["url"])))

        elif "--show-settings" in args:
            do_not_exit = True
            print_message("Settings: " + str(json.dumps(settings, indent=2)))

        else:
            print((
                "Hi. You should provide at least three arguments.\n"
                "zbxtg.py [TO] [SUBJECT] [BODY]\n\n"
                "1. Read main page and/or wiki: {0} + {1}\n"
                "2. Public Telegram group (discussion): {2}\n"
                "3. Public Telegram channel: {3}\n"
                "4. Try dev branch for test purposes (new features, etc): {0}/tree/dev"
                .format(url_github, url_wiki_base, url_tg_group,
                        url_tg_channel)))
        if not do_not_exit:
            sys.exit(0)

    zbx_to = args[1]
    zbx_subject = args[2]
    zbx_body = args[3]

    zbx = ZabbixWeb(server=conf.zbx_server,
                    username=conf.zbx_api_user,
                    password=conf.zbx_api_pass)
    client = MatrixClient(conf.server)

    token = None
    try:
        token = client.login_with_password(username=conf.username,
                                           password=conf.password)
    except MatrixRequestError as e:
        print(e)
        if e.code == 403:
            print_message("Bad username or password.")
            sys.exit(4)
        else:
            print_message("Check your sever details are correct.")
            sys.exit(2)
    except MissingSchema as e:
        print_message("Bad URL format.")
        print(e)
        sys.exit(3)
    except:
        print_message("ERROR (unknown) login_with_password()!")
        sys.exit(5)

    room = None
    try:
        room = client.join_room(zbx_to)
    except MatrixRequestError as e:
        print(e)
        if e.code == 400:
            print_message("Room ID/Alias in the wrong format")
            sys.exit(11)
        else:
            print_message("Couldn't find room.")
            sys.exit(12)
    except:
        print_message("ERROR (unknown) join_room()!")
        sys.exit(13)

    zbx.tmp_dir = tmp_dir

    # workaround for Zabbix 4.x
    zbx_version = 3

    try:
        zbx_version = conf.zbx_server_version
    except:
        pass


#    if conf.proxy_to_zbx:
#        zbx.proxies = {
#            "http": "http://{0}/".format(conf.proxy_to_zbx),
#            "https": "https://{0}/".format(conf.proxy_to_zbx)
#        }

# https://github.com/ableev/Zabbix-in-Telegram/issues/55
    try:
        if conf.zbx_basic_auth:
            zbx.basic_auth_user = conf.zbx_basic_auth_user
            zbx.basic_auth_pass = conf.zbx_basic_auth_pass
    except:
        pass

    try:
        zbx_api_verify = conf.zbx_api_verify
        zbx.verify = zbx_api_verify
    except:
        pass

    zbxtg_body = (zbx_subject + "\n" + zbx_body).splitlines()
    zbxtg_body_text = []
    ## seems redundant but necessary to avoid errors of using these variables before declaration... shouldn't happen
    tg_method_image = bool(settings["tg_method_image"])
    disable_web_page_preview = bool(settings["disable_web_page_preview"])
    is_single_message = bool(settings["is_single_message"])

    for line in zbxtg_body:
        if line.find(conf.zbx_tg_prefix) > -1:
            setting = re.split("[\s:=]+", line, maxsplit=1)
            key = setting[0].replace(conf.zbx_tg_prefix + ";", "")
            if key not in settings_description:
                # if "--debug" in args:
                room.send_text(
                    str("[ERROR] There is no '{0}' method, use --features to get help"
                        .format(key)))
                # continue
            if settings_description[key]["type"] == "list":
                value = setting[1].split(",")
            elif len(setting) > 1 and len(setting[1]) > 0:
                value = setting[1]
            elif settings_description[key]["type"] == "bool":
                value = True
            else:
                value = settings[settings_description[key]["name"]]
            if key in settings_description:
                settings[settings_description[key]["name"]] = value
            if key == "graphs" and value is True:
                tg_method_image = True
        else:
            zbxtg_body_text.append(line)

    if conf.DEBUG:
        is_debug = True
        zbx.debug = True
        log_file = tmp_dir + ".debug." + hash_ts + ".log"
        print_message(log_file)

    if not os.path.isdir(tmp_dir):
        if is_debug:
            print_message("Tmp dir doesn't exist, creating new one...")
        try:
            os.makedirs(tmp_dir)
            os.chmod(tmp_dir, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
        except:
            tmp_dir = "/tmp"
        if is_debug:
            print_message("Using {0} as a temporary dir".format(tmp_dir))

    done_all_work_in_the_fork = False
    # issue75

    if done_all_work_in_the_fork:
        sys.exit(0)

    # replace text with emojis
    internal_using_emoji = False  # I hate that, but... https://github.com/ableev/Zabbix-in-Telegram/issues/152
    if hasattr(conf, "emoji_map"):
        zbxtg_body_text_emoji_support = []
        for l in zbxtg_body_text:
            l_new = l
            for k, v in list(conf.emoji_map.items()):
                l_new = l_new.replace("{{" + k + "}}", v)
            zbxtg_body_text_emoji_support.append(l_new)
        if len("".join(zbxtg_body_text)) - len(
                "".join(zbxtg_body_text_emoji_support)):
            internal_using_emoji = True
        zbxtg_body_text = zbxtg_body_text_emoji_support

    if not is_single_message and tg_method_image is False:
        text = """%(zbx_subject)s 
        %(zbx_body)s
        """ % {
            "zbx_subject": zbx_subject,
            "zbx_body": zbx_body
        }
        room.send_text(text)

    if settings["zbxtg_image_age"]:
        age_sec = age2sec(settings["zbxtg_image_age"])
        if age_sec > 0 and age_sec > 3600:
            settings["zbxtg_image_period"] = age_sec

    message_id = 0
    if tg_method_image is True:
        zbx.login()
        room.send_text(settings["zbxtg_title"] + "\n" +
                       str(zbxtg_body_text[0]))
        if not zbx.cookie:
            text_warn = "Login to Zabbix web UI has failed (web url, user or password are incorrect), "\
                        "unable to send graphs check manually"
            room.send_text([text_warn])
            print_message(text_warn)
        else:
            if not settings["extimg"]:
                zbxtg_file_img = zbx.graph_get(settings["zbxtg_itemid"],
                                               settings["zbxtg_image_period"],
                                               settings["zbxtg_title"],
                                               settings["zbxtg_image_width"],
                                               settings["zbxtg_image_height"],
                                               version=zbx_version)
            else:
                zbxtg_file_img = external_image_get(settings["extimg"],
                                                    tmp_dir=zbx.tmp_dir)
            zbxtg_body_text, is_modified = list_cut(zbxtg_body_text, 200)
            if not zbxtg_file_img:
                text_warn = "Can't get graph image, check script manually, see logs, or disable graphs"
                room.send_text([text_warn])
                print_message(text_warn)
            else:
                if not is_single_message:
                    zbxtg_body_text = ""
                else:
                    if is_modified:
                        text_warn = "probably you will see MEDIA_CAPTION_TOO_LONG error, "\
                                    "the message has been cut to 200 symbols, "\
                                    "https://github.com/ableev/Zabbix-in-Telegram/issues/9"\
                                    "#issuecomment-166895044"
                        print_message(text_warn)
                in_file = open(zbxtg_file_img, "rb")
                uploaddata = in_file.read(
                )  # if you only wanted to read 512 bytes, do .read(512)
                in_file.close()

                response = client.upload(uploaddata, "image/png")
                room.send_image(str(response), zbxtg_file_img)
                os.remove(zbxtg_file_img)

    if "--show-settings" in args:
        print_message("Settings: " + str(json.dumps(settings, indent=2)))
Exemple #3
0
class MatrigramClient(object):
    def __init__(self, server, tb, username):
        self.client = MatrixClient(server)
        self.tb = tb
        self.token = None
        self.server = server
        self.username = username
        self.focus_room_id = None
        self.msg_type_router = {
            'm.image': self.forward_image_to_tb,
            'm.audio': self.forward_voice_to_tb,
            'm.video': self.forward_video_to_tb,
            'm.emote': self.forward_emote_to_tb,
        }
        self.room_listener_uid = None
        self.ephemeral_listener_uid = None

    def login(self, username, password):
        try:
            self.token = self.client.login_with_password(username, password)
            logger.info('token = %s', self.token)
            rooms = self.get_rooms_aliases()
            if rooms:
                # set focus room to "first" room
                self.set_focus_room(rooms.keys()[0])

            self.client.add_invite_listener(self.on_invite_event)
            self.client.add_leave_listener(self.on_leave_event)
            self.client.start_listener_thread()
            return True, "OK"
        except MatrixRequestError:
            return False, "Failed to login"
        except ConnectionError:
            return False, "Server is offline"

    def logout(self):
        self.client.logout()

    def on_event(self, _, event):
        logger.debug('entered with message %s', pprint_json(event))
        sender = event['sender'].split(':')[0].encode('utf-8')

        # Prevent messages loopback
        if sender.startswith(u'@{}'.format(self.username)):
            return

        if event['type'] == "m.room.message":
            msgtype = event['content']['msgtype']

            if msgtype != 'm.text':
                callback = self.msg_type_router.get(msgtype)
                if callback:
                    callback(event)
                return

            content = event['content']['body'].encode('utf-8')
            self.tb.send_message(sender, content, self)

        elif event['type'] == "m.room.topic":
            topic = event['content']['topic'].encode('utf-8')
            self.tb.send_topic(sender, topic, self)

    def on_ephemeral_event(self, _, ee):
        logger.debug(pprint_json(ee))

        if ee['type'] == 'm.typing':
            if ee['content']['user_ids']:
                self.tb.start_typing_thread(self)
            else:
                self.tb.stop_typing_thread(self)

    def on_leave_event(self, room_id, le):
        logger.debug(pprint_json(le))

        if le['timeline']['events'][0]['sender'] != le['timeline']['events'][
                0]['state_key']:
            self.tb.send_kick(self._room_id_to_alias(room_id), self)

    def on_invite_event(self, _, ie):
        logger.debug('invite event %s', pprint_json(ie))
        room_name = None
        for event in ie['events']:
            if event['type'] == 'm.room.name':
                room_name = event['content']['name']

        if room_name:
            self.tb.send_invite(self, self._room_id_to_alias(room_name))

    def join_room(self, room_id_or_alias):
        try:
            self.client.join_room(room_id_or_alias)
            self.set_focus_room(room_id_or_alias)
            return True
        except MatrixRequestError:
            return False

    def leave_room(self, room_id_or_alias):
        room = self.get_room_obj(room_id_or_alias)
        if not room:
            logger.error('cant find room')
            return False

        if self.focus_room_id == room.room_id:
            rooms = self.get_rooms_aliases()
            room_id = self._room_alias_to_id(room_id_or_alias)

            del rooms[room_id]
            new_focus_room = rooms.keys()[0] if rooms else None
            self.set_focus_room(new_focus_room)

        return room.leave()

    def set_focus_room(self, room_id_or_alias):
        if self._room_alias_to_id(room_id_or_alias) == self.focus_room_id:
            return

        # remove current focus room listeners
        if self.focus_room_id is not None:
            room_obj = self.get_room_obj(self.focus_room_id)
            room_obj.remove_listener(self.room_listener_uid)
            self.room_listener_uid = None
            room_obj.remove_ephemeral_listener(self.ephemeral_listener_uid)
            self.ephemeral_listener_uid = None
            logger.info("remove focus room %s", self.focus_room_id)
            self.focus_room_id = None

        # set new room on focus
        if room_id_or_alias is not None:
            self.focus_room_id = self._room_alias_to_id(room_id_or_alias)
            room_obj = self.get_room_obj(self.focus_room_id)
            self.room_listener_uid = room_obj.add_listener(self.on_event)
            self.ephemeral_listener_uid = room_obj.add_ephemeral_listener(
                self.on_ephemeral_event)
            logger.info("set focus room to %s", self.focus_room_id)

    def get_focus_room_alias(self):
        return self._room_id_to_alias(self.focus_room_id)

    def have_focus_room(self):
        return self.focus_room_id is not None

    def get_members(self):
        room_obj = self.get_room_obj(self.focus_room_id)
        rtn = room_obj.get_joined_members()

        return [
            member['displayname'] for _, member in rtn.items()
            if member.get('displayname')
        ]

    def set_name(self, name):
        user = self.client.get_user(self.client.user_id)
        user.set_display_name(name)

    def emote(self, body):
        room_obj = self.get_room_obj(self.focus_room_id)
        room_obj.send_emote(body)

    def create_room(self, alias, is_public=False, invitees=()):
        try:
            room_obj = self.client.create_room(alias, is_public, invitees)
            room_obj.update_aliases()
            logger.debug('room_id %s', room_obj.room_id)
            logger.debug('room_alias %s', room_obj.aliases[0])
            return (room_obj.room_id, room_obj.aliases[0])
        except MatrixRequestError:
            logger.error('error creating room')
            return None, None

    def backfill_previous_messages(self, limit=10):
        room_obj = self.get_room_obj(self.focus_room_id)
        room_obj.backfill_previous_messages(limit=limit)

    def get_rooms_aliases(self):
        # returns a dict with id: room obj
        rooms = self._get_rooms_updated()
        if not rooms:
            return rooms

        logger.debug("rooms got from server are %s", rooms)

        # return dict with id: list of aliases or id (if no alias exists)
        return {
            key: val.aliases if val.aliases else [key]
            for (key, val) in rooms.items()
        }

    def get_room_obj(self, room_id_or_alias):
        """Get room object of specific id or alias.

        Args:
            room_id_or_alias (str): Room's id or alias.

        Returns (Room): Room object corresponding to room_id_or_alias.

        """
        rooms = self._get_rooms_updated()
        room_id = self._room_alias_to_id(room_id_or_alias)

        return rooms.get(room_id)

    def send_message(self, msg):
        room_obj = self.get_room_obj(self.focus_room_id)

        if not room_obj:
            logger.error('cant find room')
        else:
            room_obj.send_text(msg)

    def send_photo(self, path):
        with open(path, 'rb') as f:
            mxcurl = self.client.upload(f.read(),
                                        mimetypes.guess_type(path)[0])

            room_obj = self.get_room_obj(self.focus_room_id)

            if not room_obj:
                logger.error('cant find room')
            else:
                room_obj.send_image(mxcurl, os.path.split(path)[1])

    def send_voice(self, path):
        with open(path, 'rb') as f:
            mxcurl = self.client.upload(f.read(),
                                        mimetypes.guess_type(path)[0])
            room_obj = self.get_room_obj(self.focus_room_id)
            room_obj.send_audio(mxcurl, os.path.split(path)[1])

    def send_video(self, path):
        with open(path, 'rb') as f:
            mxcurl = self.client.upload(f.read(),
                                        mimetypes.guess_type(path)[0])
            room_obj = self.get_room_obj(self.focus_room_id)
            room_obj.send_video(mxcurl, os.path.split(path)[1])

    def discover_rooms(self):
        res = requests.get('{}/_matrix/client/r0/publicRooms?limit=20'.format(
            self.server))
        res_json = res.json()

        room_objs = res_json['chunk']
        return [
            room['aliases'][0] for room in room_objs if room.get('aliases')
        ]

    def download_from_event(self, event):
        mxcurl = event['content']['url']
        link = self.client.api.get_download_url(mxcurl)
        media_id = mxcurl.split('/')[3]
        media_type = event['content']['info']['mimetype'].split('/')[1]
        path = os.path.join(self.tb.config['media_dir'],
                            '{}.{}'.format(media_id, media_type))
        download_file(link, path)

        return path

    def forward_image_to_tb(self, event):
        sender = event['sender'].split(':')[0].encode('utf-8')
        path = self.download_from_event(event)
        self.tb.send_photo(sender, path, self)

    def forward_voice_to_tb(self, event):
        sender = event['sender'].split(':')[0].encode('utf-8')
        path = self.download_from_event(event)
        self.tb.send_voice(sender, path, self)

    def forward_video_to_tb(self, event):
        sender = event['sender'].split(':')[0].encode('utf-8')
        path = self.download_from_event(event)
        self.tb.send_video(sender, path, self)

    def forward_emote_to_tb(self, event):
        sender = event['sender'].split(':')[0].encode('utf-8')
        content = event['content']['body'].encode('utf-8')
        self.tb.send_emote(sender, content, self)

    def _room_id_to_alias(self, id):
        """Convert room id to alias.

        Args:
            id (str): Room id.

        Returns (str): Room alias.

        """
        if id is None:
            return None
        if id.startswith('#'):
            return id
        rooms = self.get_rooms_aliases()
        if id in rooms:
            return rooms[id][0]
        else:
            return None

    def _room_alias_to_id(self, alias):
        """Convert room alias to id.

        Args:
            alias (str): Room alias.

        Returns (str): Room id.

        """
        if alias is None:
            return None

        if not alias.startswith('#'):
            return alias

        return self.client.api.get_room_id(alias)

    def _get_rooms_updated(self):
        """Return rooms dictionary with updated aliases.

        Returns (dict): Return rooms dictionary with updated aliases.

        """
        rooms = self.client.get_rooms()
        for room in rooms.values():
            room.update_aliases()

        return rooms
Exemple #4
0
class MatrixEngine(object):
    chatapi = ''
    chatclient = ''
    chattoken = ''
    listener_thread_id = 0
    new_msg_queue = []
    new_msg_senders = []

    def __init__(self, host=bothost, user=botuser, pwd=botpwd, room=botroom):
        print("Logging to matrix server...")
        self.chatclient = MatrixClient(host)
        try:
            self.chattoken = self.chatclient.login(username=user,
                                                   password=pwd,
                                                   sync=True)
        except MatrixRequestError as e:
            print(e)
            if e.code == 403:
                print("Bad username or password.")
                sys.exit(4)
            else:
                print("Check your sever details are correct.")
                sys.exit(2)
        except MissingSchema as e:
            print("Bad URL format.")
            print(e)
            sys.exit(3)
        self.chatapi = MatrixHttpApi(host, self.chattoken)
        print("Login OK")

        ### handle messages
        print("Setting up listener")
        try:
            room = self.chatclient.join_room(room)
        except MatrixRequestError as e:
            print(e)
            if e.code == 400:
                print("Room ID/Alias in the wrong format")
                sys.exit(11)
            else:
                print("Couldn't find room.")
                sys.exit(12)

        room.add_listener(self.on_message)
        self.listener_thread_id = self.chatclient.start_listener_thread()
        print('Listener set.. OK!')

    def sendmsg(self, msg, chatid=botroom):
        room = self.chatclient.join_room(chatid)
        response = self.chatapi.send_message(chatid, msg)

    def sendhtml(self, msg, chatid=botroom):
        room = self.chatclient.join_room(chatid)
        room.send_html(msg)

    def create_room(self, alias):
        ex_room = matrix_mongo.matrix_chats.find_one({"alias": alias})
        if ex_room:
            room_id = ex_room['room_id']
            room = self.chatclient.join_room(room_id)
            room.invite_user(ebuser)
        else:
            try:
                aldt = datetime.datetime.strftime(datetime.datetime.now(),
                                                  '%Y%m%d%H%M%S')
                new_room = self.chatclient.create_room(alias=alias + "_" +
                                                       aldt,
                                                       is_public=False,
                                                       invitees=[ebuser])
                room_id = new_room.room_id
                dtime = datetime.datetime.strftime(datetime.datetime.now(),
                                                   '%Y-%m-%d %H:%M:%S')
                chatdata = {
                    "created_date": dtime,
                    "alias": alias,
                    "room_alias": alias + "_" + aldt,
                    "room_id": room_id,
                    "room_data": room
                }

                recordID = matrix_mongo.matrix_chats.insert_one(chatdata)
            except MatrixRequestError as e:
                print(str(e))

        return room_id

    def sendfile(self, filename, chatid=botroom):
        room = self.chatclient.join_room(chatid)

        with open(filename, 'rb') as datafile:
            data = datafile.read()
            uri = self.chatclient.upload(content=data,
                                         content_type='image/jpg')
            print(uri)
            filedate = datetime.datetime.strftime(datetime.datetime.now(),
                                                  '%Y-%m-%d %H:%M:%S')
            room.send_image(
                uri, filedate + '_' + filename.replace('/', '_') + '.jpg')

    def on_message(self, room, event):
        try:
            # Called when a message is recieved.
            if event['type'] == "m.room.member":
                if event['membership'] == "join":
                    # print("{0} joined".format(event['content']['displayname']))
                    dtime = datetime.datetime.strftime(datetime.datetime.now(),
                                                       '%Y-%m-%d %H:%M:%S')
                    print(dtime + " new user joined to the room ")
            elif event['type'] == "m.room.message":
                if event['content']['msgtype'] == "m.text":
                    # print("{0}: {1}".format(event['sender'], event['content']['body']))
                    dtime = datetime.datetime.strftime(datetime.datetime.now(),
                                                       '%Y-%m-%d %H:%M:%S')
                    print(dtime + "|  '" + event['content']['body'] +
                          "' msg received from " + event['sender'])
                    if event['sender'] != "@mybot:matrix.org":
                        self.new_msg_senders.append(event['sender'])
                        self.new_msg_queue.append(event['content']['body'])
            else:
                print('new event in room:' + event['type'])
        except Exception as e:
            print('error @ on_message: ' + str(e))

    def parsemsg(self):
        for i in range(len(self.new_msg_queue)):
            origMsg = self.new_msg_queue[i]
            sender = self.new_msg_senders[i]

            msg = origMsg.lower()

            print("PARSER:  '" + origMsg + "' msg received from " + sender)

            def doshit():
                print('shit done')

            if msg == "test":
                doshit()
            elif msg == "msg":
                sendmsg("your msg responded!")
            elif msg == "html":
                sendhtml("your <b>html</b>  message <h1>responded</h1>!")
            else:
                print("message not understood")

            self.new_msg_queue.remove(origMsg)
            self.new_msg_senders.remove(sender)
Exemple #5
0
class MatrixHandler(object):
    """Handling matrix connection and bot integration"""

    __slots__ = [
        'client',
        'config',
        'db',
        'giphy_api_key',
        'hostname',
        'openweathermap_api_key',
        'password',
        'stored_msg',
        'sync_process',
        'uid',
        'username',
    ]

    def __init__(self, config: Dict[str, str]) -> None:
        self.config = config

        self.hostname = config['hostname']
        self.username = config['username']
        self.password = config['password']
        self.uid = config['uid']

        self.db = get_db()
        self.stored_msg = Query()

        self.giphy_api_key = config['giphy_api_key']
        self.openweathermap_api_key = config['openweathermap_api_key']

    def on_message(self, room: Room, event: Dict) -> None:
        """Callback for recieved messages

        Gets events and checks if something can be triggered.
        """
        logger.debug(event)

        logger.info('stores msg in db')
        self.store_msg(event)

        if event['content'].get('msgtype') == 'm.text' and event['sender'] != \
                self.uid:

            # add config to event
            event['config'] = self.config

            # gives event to mossbot and watching out for a return message
            msg = MOSS.serve(event)

            if msg and msg.data:

                if msg.type == 'text':
                    logger.info('sending text msg...')
                    room.send_text(msg.data)

                elif msg.type == 'notice':
                    logger.info('sending notice msg...')
                    room.send_notice(msg.data)

                elif msg.type == 'html':
                    logger.info('sending html msg...')
                    room.send_html(msg.data)

                elif msg.type == 'image':
                    logger.info('sending image msg...')
                    self.write_media('image', room, msg.data)

                else:
                    logger.error('could not recognize msg type "%s"', msg[0])

            elif msg and msg.type == 'skip':
                logger.info('skipping msg...')

            else:
                logger.debug('no matching in event')

    def on_invite(self, room_id, state):
        """Callback for recieving invites"""
        logger.info('got invite for room %s', room_id)
        self.client.join_room(room_id)

    def listen_forever(self, timeout_ms: int = 30000) -> None:
        """Loop to run _sync in a process"""
        while True:

            try:
                # pylint: disable=protected-access
                self.client._sync(timeout_ms)
            except BaseException as e:
                logger.exception('problem with sync: %s', e)
                time.sleep(10)

            time.sleep(0.1)

    def start_listener_process(self, timeout_ms: int = 30000) -> None:
        """Create sync process."""
        self.sync_process = Process(
            target=self.listen_forever,
            args=(timeout_ms, ),
            daemon=True,
        )
        self.sync_process.start()

    def connect(self) -> None:
        """Connection handler."""
        while True:

            try:

                logger.info('create matrix client')
                self.client = MatrixClient(self.hostname)

                logger.info('login with password')
                self.client.login_with_password(self.username, self.password)

                for room_id in self.client.get_rooms():
                    logger.info('join room %s', room_id)

                    room = self.client.join_room(room_id)
                    room.add_listener(self.on_message)

                self.client.add_invite_listener(self.on_invite)
                self.start_listener_process()

                start_time = pendulum.now()
                while True:

                    if pendulum.now() >= start_time.add(minutes=10):

                        logger.info('planed reconnect')
                        self.sync_process.terminate()

                        break

                    time.sleep(0.1)

            except KeyboardInterrupt:
                logger.info('GoodBye')
                self.sync_process.terminate()
                sys.exit()

            except BaseException as e:
                logger.exception('problem while try to connect: %s', e)
                time.sleep(10)

    def write_media(self, media_type: str, room: Room, url: str) -> None:
        """Get media, upload it and post to room
        """
        # image is the only media type supported right now
        if media_type != 'image':
            logger.error('%s as media type is not supported', media_type)
            return None

        # getting image and analyze it
        logger.info('download %s', url)
        image_data = get_image(url)
        logger.debug('got image_data: %s', image_data)

        if not image_data:
            logger.error('got no image_data')
            return

        # analyze image file and create image info dict
        media_info = {}  # type: Dict[str, Union[str, int, BytesIO, None]]

        # getting mimetype
        media_info['mimetype'] = image_data.get('content-type')
        if not media_info['mimetype']:
            media_info['mimetype'] = mimetypes.guess_type(url)[0]

        # getting name
        name = urlsplit(url).path.split('/')[-1]

        # image size
        media_info['h'] = image_data.get('height')
        media_info['w'] = image_data.get('width')

        logger.debug('media_info content: %s', media_info)

        # upload it to homeserver
        logger.info('upload file')
        uploaded = self.client.upload(image_data['image'],
                                      media_info['mimetype'])
        logger.debug('upload: %s', uploaded)

        # send image to room
        logger.info('send media: %s', name)
        room.send_image(uploaded, name, **media_info)

    def store_msg(self, event: Dict) -> None:
        """Store msgs in a db"""
        logger.debug('got event to store: %s', str(event))

        try:

            msgs_table = self.db.table('msgs')

            if event['content']['msgtype'] == 'm.text':

                all_sender_msgs = msgs_table.search(
                    self.stored_msg.sender == event['sender'])

                while len(all_sender_msgs) >= 10:

                    msgs_table.remove(doc_ids=[all_sender_msgs[0].doc_id])

                    all_sender_msgs = msgs_table.search(
                        self.stored_msg.sender == event['sender'])

                msgs_table.insert({
                    'sender': event['sender'],
                    'body': event['content']['body'],
                })

        except BaseException:
            logger.exception('could not store msg')

            return None
Exemple #6
0
class Navi:
    """
    The Navi API
    """
    def __init__(self, host_url, user_id, password, target_users, quiet=False):
        """ Starts up the bot. Connects to the homeserver and logs in.

        Args:
            base_url: Server url, e.g. https://example.com:8448
            user_id: @user:example.com
            password: p4ssw0rd
            target_users: List of users (@user_id:example.com) to push messages
                to 
        """
        self.quiet = quiet
        self.target_users = target_users
        self.host_url = host_url
        self.user_id = user_id
        self.password = password

        try:
            self.client = MatrixClient(self.host_url)
            self._log("Connecting to {}...".format(self.host_url))
            self.token = self.client.login_with_password(
                self.user_id, self.password)
        except MatrixRequestError as e:
            Navi._handle_matrix_exception(e)

        self._fetch_rooms()
        self._log("Current rooms:\n\t{}".format("\n\t".join(
            self.rooms.keys())))

    def shutdown(self):
        """ Stops the bot """
        self.client.logout()
        self._log("Connection closed.")

    def push_msg(self, msg):
        """ Push a message

        Args:
            msg: The message to push
        """
        self._cleanup_rooms()
        self._create_rooms()

        self._log("Pushing message...")
        for r in self.rooms.values():
            r.send_text(msg)

    def push_media(self, filepath):
        """ Push an image or video

        Args:
            filepath: The file path
        """
        self._cleanup_rooms()
        self._create_rooms()

        self._log("Pushing media...")

        mime = filetype.guess(filepath).mime
        if "image" in mime:
            media_type = "image"
        elif "video" in mime:
            media_type = "video"
        else:
            return

        with open(filepath, "rb") as fld:
            contents = fld.read()
            mxc = self.client.upload(contents, mime)
            if media_type == "image":
                for r in self.rooms.values():
                    r.send_image(mxc, os.path.basename(filepath))
            elif media_type == "video":
                for r in self.rooms.values():
                    r.send_video(mxc, os.path.basename(filepath))

    def leave_all_rooms(self):
        """ Leaves all rooms """
        self._log("Leaving all rooms..")
        for r in self.rooms.values():
            r.leave()
        self._log("Left {} rooms".format(len(rooms)))

    def _cleanup_rooms(self):
        """ Leaves rooms that no target user is in """
        for room_id in self.rooms.keys():
            room = self.rooms[room_id]
            users = room.get_joined_members().keys()
            if any(u in users for u in self.target_users): continue
            self._log("Leaving room {} (Name: {})".format(room_id, room.name))
            room.leave()
        self._fetch_rooms()

    def _create_rooms(self):
        """ Create rooms for users not found in any current room """
        # Compile list of all users
        if self.rooms:
            current_users = reduce(
                lambda a, b: a & b,
                map(lambda r: set(r.get_joined_members().keys()),
                    self.rooms.values()))
        else:
            current_users = set()
        missing_users = self.target_users - current_users
        for u in missing_users:
            try:
                self._log("Creating room for user {}...".format(u))
                self.client.create_room(invitees=[u])
            except MatrixRequestError as e:
                Navi._handle_matrix_exception(e)

    def _fetch_rooms(self):
        """ Fetches list of rooms """
        self.rooms = self.client.get_rooms()

    def _log(self, msg):
        if not self.quiet:
            print(msg)

    @staticmethod
    def _handle_matrix_exception(e):
        """ Print exception and quit """
        sys.stderr.write("Server Error {}: {}\n".format(e.code, e.content))
        sys.exit(-1)
class MatrixBot:
    def connect(self):
        try:
            logger.info("connecting to {}".format(self.server))
            self.client = MatrixClient(self.server)
            self.client.login_with_password(self.username, self.password)
            logger.info("connection established")

            # Store empty list of handlers
            self.handlers = []

            # listen for invites and automatically accept them
            self.client.add_invite_listener(self.handle_invite)
            self.rooms = []

            # Add all rooms we're currently in to self.rooms and add their callbacks
            for room_id, room in self.client.get_rooms().items():
                room.add_listener(self.handle_message)
                self.rooms.append(room_id)

        except Exception:
            logger.warning("connection to {} failed".format(self.server) +
                           ", retrying in 5 seconds...")
            sleep(5)
            self.connect()

    # username - Matrix username
    # password - Matrix password
    # server   - Matrix server url : port
    # rooms    - List of rooms ids to operate in, or None to accept all rooms
    def __init__(self, username, password, server, rooms=None):
        self.username = username
        self.password = password
        self.server = server
        self.rooms = rooms

        self.connect()

    def add_handler(self, handler):
        self.handlers.append(handler)

    def handle_message(self, room, event):
        # Make sure we didn't send this message
        if re.match("@" + self.username, event['sender']):
            return

        try:
            if event['type'] == "m.room.message":
                rx_msg = event['content']['body']
                if re.search(r"#book", rx_msg):
                    book_url = self.get_book_url(rx_msg)
                    print('found book: ' + book_url)
                    if (book_url):
                        img_info = self.get_book_img(book_url)
                        img = requests.get(img_info['url'])
                        mxc_url = self.client.upload(img.content, 'image/jpeg')
                        #html = "<a href='"+book_url+"'><img src='"+mxc_url+"'/></a>"
                        #print(html)
                        room.send_image(mxc_url, img_info['title'])
                        room.send_text(book_url)
        except (TypeError):
            pass

    def handle_invite(self, room_id, state):
        print("Got invite to room: " + str(room_id))
        print("Joining...")
        room = self.client.join_room(room_id)

        # Add message callback for this room
        room.add_listener(self.handle_message)

        print("Joined!")

        # Add room to list
        self.rooms.append(room)

    def get_book_url(self, search_string):
        search_words = search_string.split()
        filtered_search_words = filter(lambda w: not re.search(r"#book", w),
                                       search_words)
        query = '+'.join(filtered_search_words)
        page = requests.get("https://www.goodreads.com/search?q=" + query)
        soup = BeautifulSoup(page.content, 'html.parser')
        results = soup.find_all('a', class_='bookTitle')
        if (len(results) == 0):
            return None
        url = 'https://goodreads.com' + results[0]['href'].split('-')[0].split(
            '.')[0]
        return url

    def get_book_img(self, book_url):
        page = requests.get(book_url)
        soup = BeautifulSoup(page.content, 'html.parser')
        img_tag = soup.find("img", {"id": "coverImage"})
        h1_tag = soup.find("h1", {"id": "bookTitle"})
        return {'title': h1_tag.text, 'url': img_tag['src']}

    def start_polling(self):
        # Starts polling for messages
        self.client.start_listener_thread(
            exception_handler=lambda e: self.connect())
        return self.client.sync_thread
Exemple #8
0
class Matrix:
    _bridge = {}
    _slack = None

    _cache = {}
    _cache_file = 'matrix_cache.json'

    def __init__(self, user_id: str, http_matrix_server: str,
                 token: str) -> None:
        super().__init__()
        self.user_id = user_id
        self._client = MatrixClient(http_matrix_server,
                                    token=token,
                                    user_id=user_id)
        self.__load_cache()

    def __load_cache(self):
        if os.path.isfile(self._cache_file):
            with open(self._cache_file, 'r') as jsf:
                self._cache: json = json.load(jsf)
        if 'rooms' not in self._cache:
            self._cache['rooms'] = {}
        if 'uploaded_avatars' not in self._cache:
            self._cache['uploaded_avatars'] = {}

    def __save_cache(self):
        with open(self._cache_file, 'w') as file:
            file.write(json.dumps(self._cache))

    def set_slack(self, slack):
        self._slack = slack

    def bridge_slack_room(self, matrix_room_id, slack_room_id):
        self._bridge[matrix_room_id] = slack_room_id

    def send_message(self,
                     room_id: str,
                     text: str,
                     name: str = None,
                     avatar_url: str = None,
                     file_url: str = None,
                     file_name: str = None,
                     file_mimetype: str = None,
                     file_authorization: str = None):
        room = Room(self._client, room_id)
        current_avatar_url = None
        current_name = None
        avatar_uri = None
        if room_id in self._cache['rooms']:
            current_name = self._cache['rooms'][room_id]['name']
            current_avatar_url = self._cache['rooms'][room_id]['avatar_url']
        else:
            self._cache['rooms'][room_id] = {}
        if avatar_url is not None and avatar_url != current_avatar_url:
            if avatar_url in self._cache['uploaded_avatars']:
                avatar_uri = self._cache['uploaded_avatars'][avatar_url]
                print("Use cache avatar for an user " + avatar_uri + " (" +
                      avatar_url + ")")
            else:
                avatar_content = request.urlopen(avatar_url).read()
                avatar_uri = self._client.upload(avatar_content, 'image/png')
                self._cache['uploaded_avatars'][avatar_url] = avatar_uri
                print("Uploaded a new avatar for an user " + avatar_uri +
                      " (" + avatar_url + ")")
        if (name is not None
                and name is not current_name) or avatar_uri is not None:
            room.set_user_profile(displayname=name, avatar_url=avatar_uri)
            self._cache['rooms'][room_id]['name'] = name
            self._cache['rooms'][room_id]['avatar_url'] = avatar_url
            self.__save_cache()
        if file_url is not None and file_mimetype is not None and file_name is not None:
            rq = Request(file_url)
            rq.add_header('Authorization', file_authorization)
            file_content = urlopen(rq).read()
            file_uri = self._client.upload(file_content, file_mimetype)
            if file_mimetype in ['image/png', 'image/jpeg']:
                room.send_image(file_uri, file_name)
            else:
                room.send_file(file_uri, file_name)
        if text is not None:
            room.send_text(text)

    def start_listening(self):
        self._client.add_listener(self.__on_event)
        # room.add_listener(on_message)
        # client.add_listener(on_message)
        self._client.start_listener_thread()

    def __on_event(self, event):
        print(event)
        if event['type'] == "m.room.message" and event[
                'sender'] != self.user_id and event['room_id'] in self._bridge:
            if event['content']['msgtype'] == "m.text":
                self._slack.send_message(self._bridge[event['room_id']],
                                         event['content']['body'])

            if event['content']['msgtype'] in ["m.image", 'm.file']:
                image_url = self._client.api.get_download_url(
                    event['content']['url'])
                self._slack.send_file(self._bridge[event['room_id']],
                                      file_url=image_url,
                                      file_title=event['content']['body'])