#!/usr/bin/env python3 import re import json import asyncio import aiohttp import requests import requests.exceptions from .bus import MessageBus, MsgDirection from .base import BaseBotInstance, EmptyBot from .models import MessageType, Message, ChannelType from .helpers import string_date_time, get_logger from .config import config logger = get_logger("Gitter") class Gitter(BaseBotInstance): ChanTag = ChannelType.Gitter SupportMultiline = True _stream_api = "https://stream.gitter.im/v1/rooms/{room}/chatMessages" _post_api = "https://api.gitter.im/v1/rooms/{room}/chatMessages" def __init__(self, token, rooms, me): self.token = token self.rooms = rooms self.me = me @property def headers(self): return { 'Accept': 'application/json', 'Authorization': 'Bearer %s' % self.token, } def _must_post(self, api, data=None, json=None, timeout=10, **kwargs): if data is not None: kwargs['data'] = data elif json is not None: kwargs['json'] = json else: kwargs['data'] = {} kwargs['timeout'] = timeout try: r = requests.post(api, **kwargs) return r except requests.exceptions.Timeout: logger.error("Timeout requesting Gitter") except KeyboardInterrupt: raise except: logger.exception("Unknown error requesting Gitter") return None async def fetch(self, session, room, id_blacklist): url = self._stream_api.format(room=room) while True: # print("polling on url %s" % url) try: with aiohttp.Timeout(300): async with session.get(url, headers=self.headers) as resp: while True: line = await resp.content.readline() line = bytes.decode(line, 'utf-8').strip() if not line: continue msg = self.parse_jmsg(room, json.loads(line)) if msg.sender in id_blacklist: continue self.send_to_bus(msg) except asyncio.TimeoutError: pass except: raise def parse_jmsg(self, room, jmsg) -> Message: from_user = jmsg['fromUser']['username'] content = jmsg['text'] date, time = string_date_time(jmsg['sent']) mtype = MessageType.Command \ if self.is_cmd(content) \ else MessageType.Text return Message( ChannelType.Gitter, from_user, room, content, mtype, date=date, time=time, media_url=None, opt={} ) def send_msg(self, target, content, sender=None, raw=None, **kwargs): url = self._post_api.format(room=target) if sender: sender = re.sub(r'([\[\*_#])', r'\\\1', sender) reply = "" if 'reply_text' in kwargs: reply_to = kwargs['reply_to'] reply_text_lines = kwargs['reply_text'].splitlines() if len(reply_text_lines) > 0: for line in reply_text_lines: if not line.startswith(">"): reply_text = line break else: reply_text = reply_text_lines[0] reply = "> [{reply_to}] {reply_text}\n\n".format( reply_to=reply_to, reply_text=reply_text, ) text = "**[{sender}]** {content}" if sender else "{content}" if raw is not None: if raw.mtype in (MessageType.Photo, MessageType.Sticker): content = "%s\n![](%s)" % (raw.mtype, raw.media_url) j = { 'text': reply + text.format(sender=sender, content=content) } self._must_post(url, json=j, headers=self.headers) def send_to_bus(self, msg): raise NotImplementedError() def listen_message_stream(self, id_blacklist=None): id_blacklist = set(id_blacklist or [self.me, ]) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) with aiohttp.ClientSession(loop=loop) as session: self.aioclient_session = session tasks = [ asyncio.ensure_future(self.fetch(session, room, id_blacklist)) for room in self.rooms ] done, _ = loop.run_until_complete( asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION) ) for d in done: if d.exception(): raise d.exception() def Gitter2FishroomThread(gt: Gitter, bus: MessageBus): if gt is None or isinstance(gt, EmptyBot): return def send_to_bus(msg): bus.publish(msg) gt.send_to_bus = send_to_bus gt.listen_message_stream() def Fishroom2GitterThread(gt: Gitter, bus: MessageBus): if gt is None or isinstance(gt, EmptyBot): return for msg in bus.message_stream(): gt.forward_msg_from_fishroom(msg) def init(): from .db import get_redis redis_client = get_redis() im2fish_bus = MessageBus(redis_client, MsgDirection.im2fish) fish2im_bus = MessageBus(redis_client, MsgDirection.fish2im) rooms = [b["gitter"] for _, b in config['bindings'].items() if 'gitter' in b] token = config['gitter']['token'] me = config['gitter']['me'] return (Gitter(token, rooms, me), im2fish_bus, fish2im_bus) def main(): if "gitter" not in config: return from .runner import run_threads bot, im2fish_bus, fish2im_bus = init() run_threads([ (Gitter2FishroomThread, (bot, im2fish_bus, ), ), (Fishroom2GitterThread, (bot, fish2im_bus, ), ), ]) def test(): gitter = Gitter( token="", rooms=( "57397795c43b8c60197322b9", "5739b957c43b8c6019732c0b", ), me='' ) def response(msg): print(msg) gitter.send_msg( target=msg.receiver, content=msg.content, sender="fishroom") gitter.send_to_bus = response try: gitter.listen_message_stream(id_blacklist=("master_tuna_twitter", )) except Exception: import traceback traceback.print_exc() if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() parser.add_argument("--test", default=False, action="store_true") args = parser.parse_args() if args.test: test() else: main() # vim: ts=4 sw=4 sts=4 expandtab