class Director: def __init__(self): self.BOTUSERNAME = "******" self.BOTPASSWORD = "******" self.BOTSERVO = "matrix.org" self.RID = "!RnpiUpFIsfzZfHdHQf:matrix.org" self.realRID = "!obQcCWaLRAUgiGBvMg:postmarketos.org" self.MainClient = MatrixClient("https://" + self.BOTSERVO) self.token = self.MainClient.login_with_password( username=self.BOTUSERNAME, password=self.BOTPASSWORD) self.APIWrapper = MatrixHttpApi("https://" + self.BOTSERVO, token=self.token) self.target_room = Room(self.MainClient, self.RID) print("ready") def mainThread(self): self.MainClient.add_listener(self.callback_event, event_type="m.room.member") self.MainClient.start_listener_thread() while True: input() def callback_event(room, event): if event.get("content").get("membership") == "join": user_id = event.get("sender") d.target_room.send_html( 'This room is not official postmarketOS room. Please join the <a href="https://matrix.to/#/#porting:postmarketos.org">#porting:postmarketos.org</a> room' ) try: d.APIWrapper.invite_user(d.realRID, user_id) except: pass # user already joined the rooms
class MCat: def __init__(self, config): self.client = MatrixClient(config['server']) self.token = self.client.login_with_password(username=config['user'], password=config['password']) self.room = self.client.join_room(config['room']) self.room_id = config['room'] self.message_queue = [] self.limited_until = None def dequeue(self): """Dequeue as many messages as the server lets us""" for message in self.message_queue: if time.time() * 1000 < self.limited_until: return try: self.room.send_html(message) self.message_queue.pop(0) except Exception as e: necessary_delay = json.loads(e.content)['retry_after_ms'] sys.stderr.write("Sleeping for %s seconds... Queue depth %s\n" % (necessary_delay, len(self.message_queue))) sys.stderr.flush() self.limited_until = time.time() * 1000 + necessary_delay def enqueue(self, message): self.message_queue.append(message) def f_to_matrix(self, f): for line in f: self.enqueue(line) self.dequeue() while len(self.message_queue) > 0: self.dequeue() def matrix_to_f(self, f): def stdout_cb(chunk): if chunk[u'type'] != u"m.presence" and chunk[u'room_id'] == self.room_id: f.write(json.dumps(chunk)) f.write('\n') f.flush() self.client.add_listener(stdout_cb) self.client.listen_forever()
def run_bot(homeserver, authorize, username, password): allowed_users = authorize shell_env = os.environ.copy() shell_env['TERM'] = 'vt100' child_pid, master = pty.fork() if child_pid == 0: # we are the child os.execlpe('sh', 'sh', shell_env) pin = os.fdopen(master, 'w') stop = threading.Event() client = MatrixClient(homeserver) client.login_with_password_no_sync(username, password) # listen for invites during initial event sync so we don't miss any client.add_invite_listener(lambda room_id, state: on_invite( client, room_id, state, allowed_users)) client.listen_for_events() # get rid of initial event sync client.add_listener(lambda event: on_message(event, pin, allowed_users), event_type='m.room.message') shell_stdout_handler_thread = threading.Thread(target=shell_stdout_handler, args=(master, client, stop)) shell_stdout_handler_thread.start() while True: try: client.listen_forever() except KeyboardInterrupt: stop.set() sys.exit(0) except requests.exceptions.Timeout: logger.warn("timeout. Trying again in 5s...") time.sleep(5) except requests.exceptions.ConnectionError as e: logger.warn(repr(e)) logger.warn("disconnected. Trying again in 5s...") time.sleep(5)
def test_remove_listener(): def dummy_listener(): pass client = MatrixClient("http://example.com") handler = client.add_listener(dummy_listener) found_listener = False for listener in client.listeners: if listener["uid"] == handler: found_listener = True break assert found_listener, "listener was not added properly" client.remove_listener(handler) found_listener = False for listener in client.listeners: if listener["uid"] == handler: found_listener = True break assert not found_listener, "listener was not removed properly"
class MatrixBackend(ErrBot): def __init__(self, config): super().__init__(config) if not hasattr(config, 'MATRIX_HOMESERVER'): log.fatal(""" You need to specify a homeserver to connect to in config.MATRIX_HOMESERVER. For example: MATRIX_HOMESERVER = "https://matrix.org" """) sys.exit(1) self._homeserver = config.MATRIX_HOMESERVER self._username = config.BOT_IDENTITY['username'] self._password = config.BOT_IDENTITY['password'] self._api = None self._token = None def serve_once(self): def dispatch_event(event): log.info("Received event: %s" % event) if event['type'] == "m.room.member": if event['membership'] == "invite" and event['state_key'] == self._client.user_id: room_id = event['room_id'] self._client.join_room(room_id) log.info("Auto-joined room: %s" % room_id) if event['type'] == "m.room.message" and event['sender'] != self._client.user_id: sender = event['sender'] room_id = event['room_id'] body = event['content']['body'] log.info("Received message from %s in room %s" % (sender, room_id)) # msg = Message(body) # msg.frm = MatrixPerson(self._client, sender, room_id) # msg.to = MatrixPerson(self._client, self._client.user_id, room_id) # self.callback_message(msg) msg = self.build_message(body) room = MatrixRoom(room_id) msg.frm = MatrixRoomOccupant(self._api, room, sender) msg.to = room self.callback_message(msg) self.reset_reconnection_count() self.connect_callback() self._client = MatrixClient(self._homeserver) try: self._token = self._client.register_with_password(self._username, self._password,) except MatrixRequestError as e: if e.code == 400 or e.code == 403: try: self._token = self._client.login_with_password(self._username, self._password,) except MatrixRequestError: log.fatal(""" Incorrect username or password specified in config.BOT_IDENTITY['username'] or config.BOT_IDENTITY['password']. """) sys.exit(1) self._api = MatrixHttpApi(self._homeserver, self._token) self.bot_identifier = MatrixPerson(self._api) self._client.add_listener(dispatch_event) try: while True: self._client.listen_for_events() except KeyboardInterrupt: log.info("Interrupt received, shutting down...") return True finally: self.disconnect_callback() def rooms(self): rooms = [] raw_rooms = self._client.get_rooms() for rid, robject in raw_rooms: # TODO: Get the canonical alias rather than the first one from # `Room.aliases`. log.debug('Found room %s (aka %s)' % (rid, rid.aliases[0])) def send_message(self, mess): super().send_message(mess) room_id = mess.to.room.id text_content = item_url = mess.body if item_url.startswith("http://") or item_url.startswith("https://"): if item_url.endswith("gif"): self._api.send_content(room_id, item_url, "image", "m.image") return # text_content = Markdown().convert(mess.body) self._api.send_message(room_id, text_content) def connect_callback(self): super().connect_callback() def build_identifier(self, txtrep): raise Exception( "XXX" ) def build_reply(self, mess, text=None, private=False): log.info("build_reply") response = self.build_message(text) response.frm = self.bot_identifier response.to = mess.frm return response def change_presence(self, status: str = '', message: str = ''): raise Exception( "XXX" ) @property def mode(self): return 'matrix' def query_room(self, room): raise Exception( "XXX" )
class matrixConnector: """ Connector to the Matrix network Example: matrixObj = MatrixConnector(url, username, password, updatePersistentData, token) url = the url to the matrix server username = the username of the matrix Server password = the password of the matrix user updatePersistentData = Callback to the function to save the learned token token = the token of this client, we will resived this at the first connection. """ # declare the variable __rooms = {} def __init__(self, url, username, password, updatePersistentData, token=None ): # save my own name matrixConnector.username = username if token is None: # Try to login with username and password self.__client = MatrixClient(url) try: self.__token = self.__client.login_with_password(username, password) # save the token value = self.__token updatePersistentData('matrix_token', value) 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) else: # Try to login with token try: self.__client = MatrixClient(url, token, username) print("UserId:", self.__client.user_id) 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) def joinRoom(self, room_id_alias): """ Join a Room room = obj.joinRoom(room_id_alias) """ roomID = room_id_alias if room_id_alias.startswith("#"): roomID = self.__client.api.get_room_id(room_id_alias) if roomID not in self.discoverJoinedRooms().keys(): return False try: room = self.__client.join_room(roomID) 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) self.__rooms[roomID] = room def discoverJoinedRooms(self): # Discover what rooms the user has joined return self.__client.get_rooms() def start_listener_thread(self, onMessage): # Login to the rooms, to get the message out of the room matrixConnector.onMessage = onMessage self.__client.add_listener(self.__onEvent) self.__client.start_listener_thread() def __onEvent(room, event): # get the events out of the room message = event["content"]["body"] roomId = event["room_id"] matrixConnector.onMessage(message, roomId) def sendMessage(self, message, roomId): # Let the bot say something self.__rooms[roomId].send_html(message)
def start(stdscr): global size, room, data, rooms, access_token, endTime, rooms, all_rooms, lastEventRoom, room_keys curses.curs_set(0) curses.use_default_colors() size = stdscr.getmaxyx() stdscr.addstr(0, 0, "loading...") stdscr.refresh() loadCredentials("./credentials.json") client = MatrixClient(server) access_token = client.login_with_password( username, password, size[0]) rooms = client.get_rooms() all_rooms = "all rooms" rooms[all_rooms] = Room(client, all_rooms) rooms[all_rooms].events = [] room_keys = list(rooms.keys()) room = all_rooms #room_keys[1] # "all_rooms" nextRoom = 1 endTime = client.end curses.halfdelay(10) maxDisplayName = 24 displayNamestartingPos = 20 PAD_COMMENTS = True pause = False client.add_listener(processMessage) client.start_listener_thread() curses.echo() stdscr.keypad(True) inputBuffer = "" lastEventRoom = all_rooms the_room_to_post_to = None # store the last room we saw before we started typing while(True): size = stdscr.getmaxyx() maxChars = size[1] - 1 - len(username) - 3 stdscr.clear() # we want NAME aka ALIAS[0] (ROOM_ID) # or 2nd choice: ALIAS[0] (ROOM_ID) # or fallback: ROOM_ID line = str(room) if line == all_rooms: pass elif rooms[room].name is None: if len(rooms[room].aliases) > 0 and rooms[room].aliases[0] != room: line = rooms[room].aliases[0] + " (" + line + ")" else: if len(rooms[room].aliases) > 0 and rooms[room].aliases[0] != room: line = rooms[room].name + " aka " + getFirstRoomAlias(rooms[room]) + " (" + line + ")" else: if rooms[room].name != room: line = rooms[room].name + " (" + line + ")" #line.encode("utf-8") if rooms[room].topic is not None: line += " · topic: " + rooms[room].topic stdscr.addstr( 0, 0, ( "redpill v0.7 · screen size: " + str(size) + " · chat size: " + str(len(rooms[room].events)) + " · room: " + str(line) + " the variables: room: " + room + " last: " + lastEventRoom ), curses.A_UNDERLINE ) current = len(rooms[room].events) - 1 if True: y = 1 if current >= 0: # TODO: something when the first event is a typing event currentLine = size[0] - 1 # input space = "" for i in range(size[1] - 1): space += " " stdscr.addstr(currentLine, 0, space, curses.A_DIM) stdscr.addstr(currentLine, 0, "<" + username + ">", curses.A_DIM) stdscr.addstr(currentLine - 1, 0, space, curses.A_UNDERLINE) for event in reversed(rooms[room].events): if event["type"] == "m.typing": #if True: continue # do something clever elif event["type"] == "m.presence": #if True: continue # do something clever elif event["type"] == "m.roomchange": room_id = event["room_id"] #lin = (str(rooms[room_id].name) + " aka " + getFirstRoomAlias(rooms[room_id]) + " (" + # rooms[room_id].room_id + ")") line = room_id if line == all_rooms: pass elif rooms[line].name is None: if len(rooms[room_id].aliases) > 0 and rooms[room_id].aliases[0] != room_id: line = rooms[room_id].aliases[0] + " (" + line + ")" else: if len(rooms[room_id].aliases) > 0 and rooms[room_id].aliases[0] != room_id: line = rooms[room_id].name + " aka " + getFirstRoomAlias(rooms[room_id]) + " (" + line + ")" else: if rooms[room_id].name != room_id: line = rooms[room_id].name + " (" + line + ")" #if rooms[room].topic is not None: # line += " · topic: " + rooms[room].topic currentLine -= 1 stdscr.addstr(currentLine, 0, "Event(s) from " + line, curses.A_DIM) else: #currentLine = size[0] - y currentLine -= 1 if currentLine < 3: # how many lines we want to reserve break #if currentLine == 5: # currentLine -= 1 y += 1 if "origin_server_ts" in event: convertedDate = datetime.datetime.fromtimestamp( int( event["origin_server_ts"] / 1000) ).strftime('%Y-%m-%d %H:%M:%S') # assumption: body == normal message length = 0 if "user_id" in event: length = len( event["user_id"] ) if "body" in event["content"]: rawText = event["content"]["body"].encode('utf-8') if event["content"]["msgtype"] == "m.emote": if len(rawText) > 0 and rawText[0] == " ": rawText = rawText[1:] linesNeeded = (displayNamestartingPos + maxDisplayName + 3 + len(rawText)) / size[1] lin = (displayNamestartingPos + maxDisplayName + 3 + len(rawText)) #if currentLine == size[0] - 2: # stdscr.addstr(currentLine, 0, str(lin) + " " + str(size[1]) + " " + str(linesNeeded) + " ", curses.A_UNDERLINE) #else: # stdscr.addstr(currentLine, 0, str(lin) + " " + str(size[1]) + " " + str(linesNeeded) + " ") linesNeeded = 0 buf = "" lineByLineText = [] first = True bufSinceLastWord = "" for char in rawText: if True: #for char in line: bufSinceLastWord += char if char == '\n': linesNeeded += 1 buf += bufSinceLastWord if PAD_COMMENTS or first: linesNeeded += (displayNamestartingPos + maxDisplayName + 3 + len(buf)) / size[1] else: linesNeeded += len(buf) / size[1] first = False lineByLineText.append(buf) buf = "" bufSinceLastWord = "" else: if ((PAD_COMMENTS and (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1) or (not PAD_COMMENTS and (len(buf + bufSinceLastWord)) == size[1] - 1)): #if (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1: if len(buf) == 0: buf += bufSinceLastWord bufSinceLastWord = "" if char.isspace(): buf += bufSinceLastWord lineByLineText.append(buf) bufSinceLastWord = "" buf = "" else: lineByLineText.append(buf) buf = bufSinceLastWord bufSinceLastWord = "" linesNeeded += 1 if char.isspace(): buf += bufSinceLastWord bufSinceLastWord = "" # if (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1: if ((PAD_COMMENTS and (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1) or (not PAD_COMMENTS and (len(buf + bufSinceLastWord)) == size[1] - 1)): buf += bufSinceLastWord bufSinceLastWord = "" lineByLineText.append(buf) linesNeeded += 1 buf = "" #elif char == ' ': # skip all whitespace # self.X += 1 buf += bufSinceLastWord lineByLineText.append(buf) linesNeeded += (displayNamestartingPos + maxDisplayName + 3 + len(buf)) / size[1] buf = "" currentLine -= linesNeeded if currentLine - linesNeeded < 2: # how many lines we want to reserve break if currentLine == size[0] - 2: stdscr.addstr(currentLine, 0, str(lin) + " " + str(size[1]) + " " + str(linesNeeded) + " ", curses.A_UNDERLINE) else: stdscr.addstr(currentLine, 0, str(lin) + " " + str(size[1]) + " " + str(linesNeeded) + " ") #for i in range(linesNeeded): if PAD_COMMENTS: pad = displayNamestartingPos + maxDisplayName + 3 #if linesNeeded == 0: linesNeeded += 1 for i in range(linesNeeded): buf = rawText[:size[1] - pad] rawText = rawText[size[1] - pad:] if currentLine + i == size[0] - 2: stdscr.addstr( currentLine + i, displayNamestartingPos + maxDisplayName + 3, lineByLineText[i], curses.A_BOLD + curses.A_UNDERLINE ) else: try: stdscr.addstr( currentLine + i, displayNamestartingPos + maxDisplayName + 3, lineByLineText[i], curses.A_BOLD ) except: e = sys.exc_info()[0] print("Error: unable to start thread. " + str(e)) stdscr.addstr(1, 0, str(e)) else: # TODO: need to split this out to get proper underline if currentLine == size[0] - 2: stdscr.addstr( currentLine, displayNamestartingPos + maxDisplayName + 3, rawText, curses.A_BOLD + curses.A_UNDERLINE ) else: stdscr.addstr( currentLine, displayNamestartingPos + maxDisplayName + 3, rawText, curses.A_BOLD ) usern = event["user_id"] if length > maxDisplayName: usern = usern[:maxDisplayName - 3] + "..." if event["content"]["msgtype"] == "m.emote": usern = "* " + usern if currentLine == size[0] - 2: stdscr.addstr( currentLine, displayNamestartingPos + max(0, maxDisplayName - length), str(usern), curses.A_UNDERLINE + curses.A_BOLD ) else: stdscr.addstr( currentLine, displayNamestartingPos + max(0, maxDisplayName - length), str(usern), curses.A_BOLD ) else: usern = "<" + usern + ">" if currentLine == size[0] - 2: stdscr.addstr( currentLine, displayNamestartingPos + max(0, maxDisplayName - length), str(usern), curses.A_UNDERLINE ) else: stdscr.addstr( currentLine, displayNamestartingPos + max(0, maxDisplayName - length), str(usern) ) if currentLine == size[0] - 2: stdscr.addstr(currentLine, 0, convertedDate, curses.A_UNDERLINE) else: stdscr.addstr(currentLine, 0, convertedDate) #if currentLine == size[1]: # last line # stdscr.addstr( # currentLine, displayNamestartingPos + # maxDisplayName + 3, buf[:size[1] - # (displayNamestartingPos + maxDisplayName + 4)], # curses.A_BOLD # ) #else: # stdscr.addstr( # currentLine, displayNamestartingPos + # maxDisplayName + 3, buf, # curses.A_BOLD # ) # membership == join/leave events elif "membership" in event["content"]: buf = " invited someone" if event["content"]["membership"] == "invite": if "state_key" in event: buf = " invited " + event["state_key"] elif event["content"]["membership"] == "join": buf = " has joined" elif event["content"]["membership"] == "leave": buf = " has left" if length > maxDisplayName: if currentLine == size[0] - 2: stdscr.addstr( currentLine, displayNamestartingPos + 1, str(event["user_id"]), curses.A_DIM + curses.A_UNDERLINE ) stdscr.addstr( currentLine, displayNamestartingPos + length + 1, buf, curses.A_DIM + curses.A_UNDERLINE ) else: stdscr.addstr( currentLine, displayNamestartingPos + 1, str(event["user_id"]), curses.A_DIM ) stdscr.addstr( currentLine, displayNamestartingPos + length + 1, buf, curses.A_DIM ) else: if currentLine == size[0] - 2: stdscr.addstr( currentLine, displayNamestartingPos + 1 + maxDisplayName - length, str(event["user_id"]), curses.A_DIM + curses.A_UNDERLINE ) stdscr.addstr( currentLine, displayNamestartingPos + maxDisplayName + 1, buf, curses.A_DIM + curses.A_UNDERLINE ) else: stdscr.addstr( currentLine, displayNamestartingPos + 1 + maxDisplayName - length, str(event["user_id"]), curses.A_DIM ) stdscr.addstr( currentLine, displayNamestartingPos + maxDisplayName + 1, buf, curses.A_DIM ) current -= 1 if pause: stdscr.addstr( int(size[0] / 2) - 1, int(size[1] / 2), " ", curses.A_REVERSE ) stdscr.addstr( int(size[0] / 2), int(size[1] / 2), " PAUSED ", curses.A_REVERSE ) stdscr.addstr( int(size[0] / 2) + 1, int(size[1] / 2), " ", curses.A_REVERSE ) try: stdscr.addstr(size[0] - 1, len(username) + 3, inputBuffer[-maxChars:]) except: e = sys.exc_info()[0] print("Error: unable to start thread. " + str(e)) stdscr.addstr(1, 0, str(e)) stdscr.refresh() # getInput(stdscr) #def getInput(stdscr): # if True: try: c = stdscr.getch(size[0] - 1, len(username) + 3) #c = stdscr.getkey(size[0] - 1, len(username) + 3) #stri = stdscr.getstr(size[0] - 1, len(username) + 3, 10) if c == -1: stdscr.addstr(1, 0, "timeout") else: if c <= 256 and c != 10 and c != 9: ## enter and tab inputBuffer += chr(c) if len(inputBuffer) == 1: # e.g. just started typing if lastEventRoom != all_rooms: the_room_to_post_to = lastEventRoom if c == 9: #stdscr.addstr(1, 0, "%s was pressed\n" % c) room = room_keys[nextRoom] nextRoom = (nextRoom + 1) % len(rooms) the_room_to_post_to = None elif c == 10: # enter with open('sends.log', 'a') as the_file: the_file.write("the_room_to_post_to:" + str(the_room_to_post_to) + "\n") the_file.write("lastEventRoom: " + str(lastEventRoom) + "\n") the_file.write("room: " + str(room) + "\n") the_file.write("inputBuffer: " + str(inputBuffer) + "\n") the_file.write("---\n") if inputBuffer.startswith("/invite"): user_id = inputBuffer[7:].strip() rooms[room].invite_user(user_id) elif inputBuffer.startswith("/kick"): user_id = inputBuffer[5:].strip() reason = "no reason..." rooms[room].kick_user(user_id, reason) elif inputBuffer.startswith("/power"): user_id = inputBuffer[7:].strip() power_level = 50 rooms[room].set_power_level(user_id, power_level) elif inputBuffer.startswith("/op"): user_id = inputBuffer[2:].strip() rooms[room].set_power_level(user_id) elif inputBuffer.startswith("/ban"): # reason user_id = inputBuffer[4:].strip() reason = "sux" #FIXME rooms[room].ban(user_id, reason) elif inputBuffer.startswith("/join"): # there's a /join that supports aliases room_alias = inputBuffer[5:].strip() client.join_room(room_alias) elif inputBuffer.startswith("/j"): room_alias = inputBuffer[2:].strip() client.join_room(room_alias) elif inputBuffer.startswith("/leave"): rooms[room].leave_room(room_id) elif inputBuffer.startswith("/create"): # create a new room is_public = True invitees = () # def create_room(self, alias=None, is_public=False, invitees=()): room_alias = inputBuffer[7:].strip() client.create_room(room_alias, is_public, invitees) elif inputBuffer.startswith("/topic"): # get or set topic new_topic = inputBuffer[6:].strip() if len(new_topic) > 0: rooms[room].topic = new_topic else: pass #rooms[room].topic = "fail" else: if room == all_rooms: if the_room_to_post_to is None: if lastEventRoom != all_rooms: the_room_to_post_to = lastEventRoom else: stdscr.addstr(1, 0, "No idea what room to post to!") stdscr.refresh() inputBuffer = "No idea what room to post to!" continue else: the_room_to_post_to = room if inputBuffer.startswith("/me"): rooms[the_room_to_post_to].send_emote(inputBuffer[3:]) else: rooms[the_room_to_post_to].send_text(inputBuffer) inputBuffer = "" the_room_to_post_to = None elif c == curses.KEY_DC: inputBuffer = "" the_room_to_post_to = None elif c == curses.KEY_BACKSPACE: if len(inputBuffer) > 0: inputBuffer = inputBuffer[:-1] if len(inputBuffer) == 0: the_room_to_post_to = None elif c == curses.KEY_IC: pause = not(pause) if pause: curses.nocbreak() curses.cbreak() stdscr.timeout(-1) stdscr.addstr( int(size[0] / 2) - 1, int(size[1] / 2), " ", curses.A_REVERSE ) stdscr.addstr( int(size[0] / 2), int(size[1] / 2), " PAUSING ", curses.A_REVERSE ) stdscr.addstr( int(size[0] / 2) + 1, int(size[1] / 2), " ", curses.A_REVERSE ) stdscr.refresh() else: stdscr.addstr( int(size[0] / 2) - 1, int(size[1] / 2), " ", curses.A_REVERSE ) stdscr.addstr( int(size[0] / 2), int(size[1] / 2), " RESUMING ", curses.A_REVERSE ) stdscr.addstr( int(size[0] / 2) + 1, int(size[1] / 2), " ", curses.A_REVERSE ) stdscr.refresh() curses.halfdelay(10) stdscr.timeout(1) elif c == 27: # need to test for alt combo or ESC curses.cbreak() curses.echo() #curses.curs_set(1) stdscr.keypad(0) curses.endwin() quit() elif c == curses.KEY_F2: PAD_COMMENTS = not PAD_COMMENTS #stdscr.addstr(2, 0, "time() == %s\n" % time.time()) finally: do_nothing = True
def main(): global client global config global github global repo global msc_labels global logger global room_specific_data # Retrieve login information from config file with open("config.toml", "r") as f: try: config = toml.loads(f.read()) except: log_fatal("Error reading config file:") return # Configure logging # Determine whether we are using a logfile or not logging_format = "[%(levelname)s] %(asctime)s: %(message)s" if "logfile" in config["logging"]: logging.basicConfig( level=logging.INFO if config["logging"]["level"] != "DEBUG" else logging.DEBUG, format=logging_format, filename=config["logging"]["logfile"]) else: logging.basicConfig( level=logging.INFO if config["logging"]["level"] != "DEBUG" else logging.DEBUG, format=logging_format) logger = logging.getLogger() # Retrieve room-specific data if config file exists if "data_filepath" in config["bot"]: data_filepath = config["bot"]["data_filepath"] if os.path.exists(config["bot"]["data_filepath"]): with open(data_filepath, 'r') as f: room_specific_data = json.loads(f.read()) # Schedule daily summary messages per-room for room_id in room_specific_data.keys(): # Check if summaries are enabled in this room if get_room_setting(room_id, "summary_enabled") == False: continue # Check if this room has a custom summary time if get_room_setting(room_id, "summary_time"): # Set a scheduler for that time # Tag with the room ID so we can easily cancel later if necessary schedule.every().day.at(config["bot"]["daily_summary_time"]).do(send_summary, room_id).tag( room_id) # Schedule daily summary messages to rooms that do not have a custom time set_up_default_summaries() # Login to Github github = Github(config["github"]["token"]) repo = github.get_repo(config["github"]["repo"]) log_info("Connected to Github") # Get MSC-related label objects from specified Github repository labels = config["github"]["labels"] msc_labels = {label.name: label for label in repo.get_labels() if label.name in labels} # Login to Matrix and listen for messages homeserver = "https://" + config["matrix"]["user_id"].split(":")[-1] client = MatrixClient(homeserver, user_id=config["matrix"]["user_id"], token=config["matrix"]["token"]) client.add_invite_listener(invite_received) client.add_listener(event_received, event_type="m.room.message") log_info("Connected to Matrix") # Sync continuously and check time for daily summary sending while True: try: client.listen_for_events() except: log_warn("Unable to contact /sync") schedule.run_pending() time.sleep(config["matrix"]["sync_interval"]) # Wait a few seconds between syncs
class MpyBot: def __init__(self, configfile, run=True): logger.debug('load config') config_dic = load_yaml_config(configfile) self.bot_startcmd = config_dic.get('bot_startcmd', STARTCMD) self._full_cmds = {} self._local_cmds = {} self._module_calls = {} for moduledic in config_dic.get('modules', []): self.add_module(moduledic) matrix_server = config_dic['matrix_server'] logger.debug('init bot') self.mcl = MatrixClient(**matrix_server) self.auto_join_invited_rooms = config_dic.get('auto_join_invited_rooms', True) self.auto_join_servers = set(config_dic.get('auto_join_servers', [])) self.admin_ids = set(config_dic.get('admin_ids', [])) disp_name = config_dic.get('bot_display_name', '') if disp_name: user = self.mcl.get_user(self.mcl.user_id) if user.get_display_name() != disp_name: user.set_display_name(disp_name) self.mcl.add_invite_listener(self._process_invite) self.mcl.add_listener(self._process_message, 'm.room.message') logger.info('bot initialized') if run: self._run() def _run(self): logger.debug('run listen_forever') self.mcl.listen_forever() def join_room(self, room_id): try: logger.info('joining new room {}'.format(room_id)) room = self.mcl.join_room(room_id) room.send_text("Welcome! Type {} to control me.".format(self.bot_startcmd)) return True except MatrixError as e: logger.exception('{} while joining room {}'.format(repr(e), room_id)) return False def leave_room(self, room_id): logger.info('trying to leave room with id {}'.format(room_id)) leave_room = self.mcl.get_rooms().get(room_id, '') if not leave_room: logger.debug('bot not in room {}'.format(room_id)) return False if leave_room.leave(): logger.debug('left room {}'.format(room_id)) return True else: logger.debug('failed to leave known room with id {}'.format(leave_room.room_id)) return False def _process_invite(self, room_id, state=None): logger.debug('received invitation to {}, state: {}'.format(room_id, state)) if self.auto_join_invited_rooms: if self.auto_join_servers and \ room_id.split(':')[-1] not in self.auto_join_servers: return self.join_room(room_id) def _process_message(self, roomchunk): if roomchunk['sender'] == self.mcl.user_id: return age = roomchunk.get('unsigned', {}).get('age') if age is None: # fallback age = abs(time.time() - roomchunk['origin_server_ts']/1000) else: age /= 1000 if age > 60: logger.debug('received old message in {}, event_id: {}'.format(roomchunk['room_id'], roomchunk['event_id'])) return content = roomchunk['content'] if content['msgtype'] == 'm.text': msg = content['body'].lstrip() if msg.startswith(self.bot_startcmd): room = self.mcl.get_rooms()[roomchunk['room_id']] msg = msg[len(self.bot_startcmd):].lstrip() self._evaluate_bot_message(room, roomchunk['sender'], msg) else: s_msg = msg.split(' ', 1) cmd = s_msg[0] msg = s_msg[1] if len(s_msg) > 1 else '' modulename = self._full_cmds.get(cmd) if modulename: room = self.mcl.get_rooms()[roomchunk['room_id']] self._call_module(modulename, room, roomchunk['sender'], msg) def _evaluate_bot_message(self, room, sender, msg): if msg.startswith('ctl'): logger.debug("received control message '{}' in room '{}'".format(msg, room.room_id)) if sender not in self.admin_ids: logger.debug('{} has no permissions to send a ctl-message'.format(sender)) room.send_notice('{} has no permissions to send a ctl-message'.format(sender)) return data = msg.split()[1:] if len(data) == 2: if data[0] == 'join': if not self.join_room(data[1]): room.send_notice('something went wrong while joining room') elif data[0] == 'leave': if data[1] == 'this': data[1] = room.room_id if not self.leave_room(data[1]): room.send_notice('room could not be left') return elif msg.startswith('-'): msg = msg[1:].strip() if msg.startswith('help'): text = 'Available local commands:\n' for k in self._local_cmds: text += ' - ' + k + '\n' text += 'Available full commands:\n' for k in self._full_cmds: text += ' - ' + k + '\n' room.send_text(text) elif msg.startswith('time'): room.send_text('UTC: {:.0f}'.format(time.time())) s_msg = msg.split(' ', 1) cmd = s_msg[0] msg = s_msg[1] if len(s_msg) > 1 else '' modulename = self._local_cmds.get(cmd) if modulename: self._call_module(modulename, room, sender, msg) def add_module(self, moduledic): try: name = moduledic['name'] module = importlib.import_module('modules.' + name) opt = moduledic.get('options') logging.debug('here') if opt: module.set_options(opt) self._module_calls[name] = module.msg_call cmd = moduledic.get('local_cmd') if cmd: self._local_cmds[cmd] = name cmd = moduledic.get('full_cmd') if cmd: self._full_cmds[cmd] = name logger.info('module {} added'.format(moduledic)) except Exception as e: logger.exception('not possible to add module {}: {}'.format(moduledic, repr(e))) def _call_module(self, modulename, room, sender, msg): logger.debug("modulecall {} for message '{}' in room {}".format(modulename, msg, room.room_id)) try: res = self._module_calls[modulename](room=room, sender=sender, msg=msg) if res and isinstance(res, str): room.send_text(res) except Exception as e: logger.exception('Failed to call module {}'.format(modulename))
class MatrixProtocol(Protocol): # called on bot init; the following are already created by __init__: # self.bot = SibylBot instance # self.log = the logger you should use def setup(self): self.connected = False self.rooms = {} self.bot.add_var("credentials", persist=True) # Incoming message queue - messageHandler puts messages in here and # process() looks here periodically to send them to sibyl self.msg_queue = Queue() # Create a client in setup() because we might use self.client before # connect() is called homeserver = self.opt('matrix.server') self.client = MatrixClient(homeserver) # @raise (ConnectFailure) if can't connect to server # @raise (AuthFailure) if failed to authenticate to server def connect(self): homeserver = self.opt('matrix.server') user = self.opt('matrix.username') pw = self.opt('matrix.password') self.log.debug("Connecting to %s" % homeserver) try: self.log.debug("Logging in as %s" % user) # Log in with the existing access token if we already have a token if (self.bot.credentials and self.bot.credentials[0] == user): self.client = MatrixClient(homeserver, user_id=user, token=self.bot.credentials[1]) # Otherwise, log in with the configured username and password else: token = self.client.login_with_password(user, pw) self.bot.credentials = (user, token) self.rooms = self.client.get_rooms() self.log.debug("Already in rooms: %s" % self.rooms) # Connect to Sibyl's message callback self.client.add_listener(self.messageHandler) self.client.start_listener_thread() self.connected = True except MatrixRequestError as e: if (e.code == 403): self.log.debug( "Credentials incorrect! Maybe your access token is outdated?" ) raise AuthFailure else: self.log.debug("Failed to connect to homeserver!") raise ConnectFailure # @return (bool) True if we are connected to the server def is_connected(self): return self.connected # receive/process messages and call bot._cb_message() # must ignore msgs from myself and from users not in any of our rooms # @call bot._cb_message(Message) upon receiving a valid status or message # @raise (PingTimeout) if implemented # @raise (ConnectFailure) if disconnected # @raise (ServerShutdown) if server shutdown def process(self): while (not self.msg_queue.empty()): self.bot._cb_message(self.msg_queue.get()) def messageHandler(self, msg): if (self.opt('matrix.debug')): self.log.debug(str(msg)) try: # Create a new Message to send to Sibyl u = self.new_user(msg['sender'], Message.GROUP) r = self.new_room(msg['room_id']) msgtype = msg['content']['msgtype'] if (msgtype == 'm.text'): m = Message(u, msg['content']['body'], room=r, typ=Message.GROUP) self.log.debug('Handling m.text: ' + msg['content']['body']) self.msg_queue.put(m) elif (msgtype == 'm.emote'): m = Message(u, msg['content']['body'], room=r, typ=Message.GROUP, emote=True) self.log.debug('Handling m.emote: ' + msg['content']['body']) self.msg_queue.put(m) elif (msgtype == 'm.image' or msgtype == 'm.audio' or msgtype == 'm.file' or msgtype == 'm.video'): media_url = urlparse(msg['content']['url']) http_url = self.client.api.base_url + "/_matrix/media/r0/download/{0}{1}".format( media_url.netloc, media_url.path) if (msgtype == 'm.image'): body = "{0} uploaded {1}: {2}".format( msg['sender'], msg['content'].get('body', 'an image'), http_url) elif (msgtype == 'm.audio'): body = "{0} uploaded {1}: {2}".format( msg['sender'], msg['content'].get('body', 'an audio file'), http_url) elif (msgtype == 'm.video'): body = "{0} uploaded {1}: {2}".format( msg['sender'], msg['content'].get('body', 'a video file'), http_url) elif (msgtype == 'm.file'): body = "{0} uploaded {1}: {2}".format( msg['sender'], msg['content'].get('body', 'a file'), http_url) m = Message(u, body, room=r, typ=Message.GROUP) self.log.debug("Handling " + msgtype + ": " + body) self.msg_queue.put(m) elif (msgtype == 'm.location'): body = "{0} sent a location: {1}".format( msg['sender'], msg['content']['geo_uri']) m = Message(u, body, room=r, typ=Message.GROUP) self.log.debug('Handling m.location: ' + body) self.msg_queue.put(m) else: self.log.debug('Not handling message, unknown msgtype') except KeyError as e: self.log.debug( "Incoming message did not have all required fields: " + e.message) # called when the bot is exiting for whatever reason # NOTE: sibylbot will already call part_room() on every room in get_rooms() def shutdown(self): pass # send a message to a user # @param mess (Message) message to be sent # @raise (ConnectFailure) if failed to send message # Check: get_emote() def send(self, mess): (text, to) = (mess.get_text(), mess.get_to()) if (mess.get_emote()): to.room.send_emote(text) else: to.room.send_text(text) # send a message with text to every user in a room # optionally note that the broadcast was requested by a specific User # @param mess (Message) the message to broadcast # @return (str,unicode) the text that was actually sent # Check: get_user(), get_users() def broadcast(self, mess): """send a message to every user in a room""" (text, room, frm) = (mess.get_text(), mess.get_to(), mess.get_user()) users = self.get_occupants(room) + (mess.get_users() or []) # Matrix has no built-in broadcast, so we'll just highlight everyone s = 'all: %s --- ' % text if frm: self.log.debug('Broadcast message from: ' + str(frm)) s += frm.get_name() + ' --- ' me = self.get_user() names = [ u.get_name() for u in users if (u != me and (not frm or u != frm)) ] s += ', '.join(set(names)) self.send(Message(self.get_user(), s, to=room)) return s # join the specified room using the specified nick and password # @param room (Room) the room to join # @call bot._cb_join_room_success(room) on successful join # @call bot._cb_join_room_failure(room,error) on failed join def join_room(self, room): try: res = self.client.join_room(room.room.room_id) self.bot._cb_join_room_success(room) except MatrixRequestError as e: self.bot._cb_join_room_failure(room, e.message) # part the specified room # @param room (Room) the room to leave def part_room(self, room): raise NotImplementedError # helper function for get_rooms() for protocol-specific flags # only needs to handle: FLAG_PARTED, FLAG_PENDING, FLAG_IN, FLAG_ALL # @param flag (int) one of Room.FLAG_* enums # @return (list of Room) rooms matching the flag def _get_rooms(self, flag): mxrooms = self.client.get_rooms() return [self.new_room(mxroom) for mxroom in mxrooms] # @param room (Room) the room to query # @return (list of User) the Users in the specified room def get_occupants(self, room): memberdict = room.room.get_joined_members() return [self.new_user(x) for x in memberdict] # @param room (Room) the room to query # @return (str) the nick name we are using in the specified room def get_nick(self, room): return self.get_user().get_name() # TODO: per-room nicknames # @param room (Room) the room to query # @param nick (str) the nick to examine # @return (User) the "real" User behind the specified nick/room def get_real(self, room, nick): raise NotImplementedError # @return (User) our username def get_user(self): return MatrixUser(self, self.opt('matrix.username'), Message.GROUP) # @param user (str) a user id to parse # @param typ (int) either Message.GROUP or Message.PRIVATE # @param real (User) [self] the "real" user behind this user # @return (User) a new instance of this protocol's User subclass def new_user(self, user, typ=None, real=None): return MatrixUser(self, user, typ, real) # @param name (object) the identifier for this Room # @param nick (str) [None] the nick name to use in this Room # @param pword (str) [None] the password for joining this Room # @return (Room) a new instance of this protocol's Room subclass def new_room(self, room_id_or_alias, nick=None, pword=None): return MatrixRoom(self, room_id_or_alias, nick, pword)
class MatrixProtocol(Protocol): # List of occupants in each room # Used to avoid having to re-request the list of members each time room_occupants = {} # Keep track of when we joined rooms this session # Used to filter out historical messages after accepting room invites join_timestamps = {} # called on bot init; the following are already created by __init__: # self.bot = SibylBot instance # self.log = the logger you should use def setup(self): self.rooms = {} self.bot.add_var("credentials",persist=True) # Incoming message queue - messageHandler puts messages in here and # process() looks here periodically to send them to sibyl self.msg_queue = Queue() # Create a client in setup() because we might use self.client before # connect() is called homeserver = self.opt('matrix.server') self.client = MatrixClient(homeserver) # @raise (ConnectFailure) if can't connect to server # @raise (AuthFailure) if failed to authenticate to server def connect(self): homeserver = self.opt('matrix.server') user = self.opt('matrix.username') pw = self.opt('matrix.password') self.log.debug("Connecting to %s" % homeserver) try: self.log.debug("Logging in as %s" % user) # Log in with the existing access token if we already have a token if(self.bot.credentials and self.bot.credentials[0] == user): self.client = MatrixClient(homeserver, user_id=user, token=self.bot.credentials[1]) # Otherwise, log in with the configured username and password else: token = self.client.login_with_password(user,pw) self.bot.credentials = (user, token) self.rooms = self.client.get_rooms() self.log.debug("Already in rooms: %s" % self.rooms) # Connect to Sibyl's message callback self.client.add_listener(self.messageHandler) self.client.add_invite_listener(self.inviteHandler) self.log.debug("Starting Matrix listener thread") self.client.start_listener_thread(exception_handler=self._matrix_exception_handler) except MatrixRequestError as e: if(e.code in [401, 403]): self.log.debug("Credentials incorrect! Maybe your access token is outdated?") raise self.AuthFailure else: if(self.opt('matrix.debug')): tb = traceback.format_exc() self.log.debug(tb) self.log.debug("Failed to connect to homeserver!") raise self.ConnectFailure except MatrixHttpLibError as e: self.log.error("Failed to connect to homeserver!") self.log.debug("Received error:" + str(e)) raise self.ConnectFailure def _matrix_exception_handler(self, e): self.msg_queue.put(e) # receive/process messages and call bot._cb_message() # must ignore msgs from myself and from users not in any of our rooms # @call bot._cb_message(Message) upon receiving a valid status or message # @raise (PingTimeout) if implemented # @raise (ConnectFailure) if disconnected # @raise (ServerShutdown) if server shutdown def process(self): while(not self.msg_queue.empty()): next = self.msg_queue.get() if(isinstance(next, Message)): self.log.debug("Placing message into queue: " + next.get_text()) self.bot._cb_message(next) elif(isinstance(next, MatrixHttpLibError)): self.log.debug("Received error from Matrix SDK, stopping listener thread: " + str(next)) self.client.stop_listener_thread() raise self.ConnectFailure("Connection error returned by requests library: " + str(next)) def messageHandler(self, msg): if(self.opt('matrix.debug')): self.log.debug(str(msg)) try: # Create a new Message to send to Sibyl u = self.new_user(msg['sender'], Message.GROUP) r = self.new_room(msg['room_id']) if(r in self.join_timestamps and datetime.datetime.fromtimestamp(msg['origin_server_ts']/1000, pytz.utc) < self.join_timestamps[r]): self.log.info('Message received in {} from before room join, ignoring'.format(msg['room_id'])) return None if('msgtype' in msg['content']): msgtype = msg['content']['msgtype'] if(msgtype == 'm.text'): m = Message(u, msg['content']['body'], room=r, typ=Message.GROUP) self.log.debug('Handling m.text: ' + msg['content']['body']) self.msg_queue.put(m) elif(msgtype == 'm.emote'): m = Message(u, msg['content']['body'], room=r, typ=Message.GROUP, emote=True) self.log.debug('Handling m.emote: ' + msg['content']['body']) self.msg_queue.put(m) elif(msgtype == 'm.image' or msgtype == 'm.audio' or msgtype == 'm.file' or msgtype == 'm.video'): media_url = urlparse(msg['content']['url']) http_url = self.client.api.base_url + "/_matrix/media/r0/download/{0}{1}".format(media_url.netloc, media_url.path) if(msgtype == 'm.image'): body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'an image'), http_url) elif(msgtype == 'm.audio'): body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'an audio file'), http_url) elif(msgtype == 'm.video'): body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'a video file'), http_url) elif(msgtype == 'm.file'): body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'a file'), http_url) m = Message(u, body, room=r, typ=Message.GROUP) self.log.debug("Handling " + msgtype + ": " + body) self.msg_queue.put(m) elif(msgtype == 'm.location'): body = "{0} sent a location: {1}".format(msg['sender'], msg['content']['geo_uri']) m = Message(u, body, room=r, typ=Message.GROUP) self.log.debug('Handling m.location: ' + body) self.msg_queue.put(m) else: self.log.debug('Not handling message, unknown msgtype') elif('membership' in msg): if(msg['membership'] == 'join'): self.room_occupants[r].add(self.new_user(msg['state_key'], Message.GROUP)) elif(msg['membership'] == 'leave'): self.room_occupants[r].remove(self.new_user(msg['state_key'], Message.GROUP)) except KeyError as e: self.log.debug("Incoming message did not have all required fields: " + e.message) def inviteHandler(self, room_id, state): join_on_invite = self.opt('matrix.join_on_invite') invite_events = [x for x in state['events'] if x['type'] == 'm.room.member' and x['state_key'] == str(self.get_user()) and x['content']['membership'] == 'invite'] if(len(invite_events) != 1): raise KeyError("Something's up, found more than one invite state event for " + room_id) inviter = invite_events[0]['sender'] inviter_domain = inviter.split(':')[1] my_domain = str(self.get_user()).split(':')[1] if(join_on_invite == 'accept' or (join_on_invite == 'domain' and inviter_domain == my_domain)): self.log.debug('Joining {} on invite from {}'.format(room_id, inviter)) self.join_room(MatrixRoom(self, room_id)) elif(join_on_invite == 'domain' and inviter_domain != my_domain): self.log.debug("Received invite for {} but inviter {} is on a different homeserver").format(room_id, inviter) else: self.log.debug("Received invite for {} from {} but join_on_invite is disabled".format(room_id, inviter)) # called when the bot is exiting for whatever reason # NOTE: sibylbot will already call part_room() on every room in get_rooms() def shutdown(self): pass # send a message to a user # @param mess (Message) message to be sent # @raise (ConnectFailure) if failed to send message # Check: get_emote() def send(self,mess): (text,to) = (mess.get_text(),mess.get_to()) try: if(mess.get_emote()): to.room.send_emote(text) else: to.room.send_text(text) except MatrixError as e: raise self.ConnectFailure # send a message with text to every user in a room # optionally note that the broadcast was requested by a specific User # @param mess (Message) the message to broadcast # @return (str,unicode) the text that was actually sent # Check: get_user(), get_users() def broadcast(self,mess): """send a message to every user in a room""" (text,room,frm) = (mess.get_text(),mess.get_to(),mess.get_user()) users = self.get_occupants(room)+(mess.get_users() or []) # Matrix has no built-in broadcast, so we'll just highlight everyone s = 'all: %s --- ' % text if frm: self.log.debug('Broadcast message from: ' + str(frm)) s += frm.get_name()+' --- ' me = self.get_user() names = [u.get_name() for u in users if (u!=me and (not frm or u!=frm))] s += ', '.join(set(names)) self.send(Message(self.get_user(),s,to=room)) return s # join the specified room using the specified nick and password # @param room (Room) the room to join # @call bot._cb_join_room_success(room) on successful join # @call bot._cb_join_room_failure(room,error) on failed join def join_room(self,room): try: res = self.client.join_room(room.room.room_id) self.bot._cb_join_room_success(room) self.join_timestamps[room] = datetime.datetime.now(pytz.utc) except MatrixError as e: self.bot._cb_join_room_failure(room, e.message) # part the specified room # @param room (Room) the room to leave def part_room(self,room): raise NotImplementedError # helper function for get_rooms() for protocol-specific flags # only needs to handle: FLAG_PARTED, FLAG_PENDING, FLAG_IN, FLAG_ALL # @param flag (int) one of Room.FLAG_* enums # @return (list of Room) rooms matching the flag def _get_rooms(self,flag): mxrooms = self.client.get_rooms() return [self.new_room(mxroom) for mxroom in mxrooms] # @param room (Room) the room to query # @return (list of User) the Users in the specified room def get_occupants(self,room): if(room in self.room_occupants): return list(self.room_occupants[room]) else: try: memberdict = room.room.get_joined_members() users = [ self.new_user(x) for x in memberdict ] self.room_occupants[room] = set(users) return users except MatrixError as e: raise self.ConnectFailure # @param room (Room) the room to query # @return (str) the nick name we are using in the specified room def get_nick(self,room): return self.get_user().get_name() # TODO: per-room nicknames # @param room (Room) the room to query # @param nick (str) the nick to examine # @return (User) the "real" User behind the specified nick/room def get_real(self,room,nick): raise NotImplementedError # @return (User) our username def get_user(self): return MatrixUser(self,self.opt('matrix.username'),Message.GROUP) # @param user (str) a user id to parse # @param typ (int) either Message.GROUP or Message.PRIVATE # @param real (User) [self] the "real" user behind this user # @return (User) a new instance of this protocol's User subclass def new_user(self,user,typ=None,real=None): return MatrixUser(self,user,typ,real) # @param name (object) the identifier for this Room # @param nick (str) [None] the nick name to use in this Room # @param pword (str) [None] the password for joining this Room # @return (Room) a new instance of this protocol's Room subclass def new_room(self,room_id_or_alias,nick=None,pword=None): return MatrixRoom(self,room_id_or_alias,nick,pword)
class MatrixProtocol(Protocol): # List of occupants in each room # Used to avoid having to re-request the list of members each time room_occupants = {} # Keep track of when we joined rooms this session # Used to filter out historical messages after accepting room invites join_timestamps = {} # called on bot init; the following are already created by __init__: # self.bot = SibylBot instance # self.log = the logger you should use def setup(self): self.rooms = {} self.bot.add_var("credentials",persist=True) # Incoming message queue - messageHandler puts messages in here and # process() looks here periodically to send them to sibyl self.msg_queue = Queue() # Create a client in setup() because we might use self.client before # connect() is called homeserver = self.opt('matrix.server') self.client = MatrixClient(homeserver) # @raise (ConnectFailure) if can't connect to server # @raise (AuthFailure) if failed to authenticate to server def connect(self): homeserver = self.opt('matrix.server') user = self.opt('matrix.username') pw = self.opt('matrix.password') self.log.debug("Connecting to %s" % homeserver) try: self.log.debug("Logging in as %s" % user) # Log in with the existing access token if we already have a token if(self.bot.credentials and self.bot.credentials[0] == user): self.client = MatrixClient(homeserver, user_id=user, token=self.bot.credentials[1]) # Otherwise, log in with the configured username and password else: token = self.client.login_with_password(user,pw) self.bot.credentials = (user, token) self.rooms = self.client.get_rooms() self.log.debug("Already in rooms: %s" % self.rooms) # Connect to Sibyl's message callback self.client.add_listener(self.messageHandler) self.client.add_invite_listener(self.inviteHandler) self.log.debug("Starting Matrix listener thread") self.client.start_listener_thread(exception_handler=self._matrix_exception_handler) except MatrixRequestError as e: if(e.code in [401, 403]): self.log.debug("Credentials incorrect! Maybe your access token is outdated?") raise self.AuthFailure else: if(self.opt('matrix.debug')): tb = traceback.format_exc() self.log.debug(tb) self.log.debug("Failed to connect to homeserver!") raise self.ConnectFailure except MatrixHttpLibError as e: self.log.error("Failed to connect to homeserver!") self.log.debug("Received error:" + str(e)) raise self.ConnectFailure def _matrix_exception_handler(self, e): self.msg_queue.put(e) # receive/process messages and call bot._cb_message() # must ignore msgs from myself and from users not in any of our rooms # @call bot._cb_message(Message) upon receiving a valid status or message # @raise (PingTimeout) if implemented # @raise (ConnectFailure) if disconnected # @raise (ServerShutdown) if server shutdown def process(self): while(not self.msg_queue.empty()): next = self.msg_queue.get() if(isinstance(next, Message)): self.log.debug("Placing message into queue: " + next.get_text()) self.bot._cb_message(next) elif(isinstance(next, MatrixHttpLibError)): self.log.debug("Received error from Matrix SDK, stopping listener thread: " + str(next)) self.client.stop_listener_thread() raise self.ConnectFailure("Connection error returned by requests library: " + str(next)) def messageHandler(self, msg): if(self.opt('matrix.debug')): self.log.debug(str(msg)) try: # Create a new Message to send to Sibyl u = self.new_user(msg['sender'], Message.GROUP) r = self.new_room(msg['room_id']) if(r in self.join_timestamps and datetime.datetime.fromtimestamp(msg['origin_server_ts']/1000, pytz.utc) < self.join_timestamps[r]): self.log.info('Message received in {} from before room join, ignoring'.format(msg['room_id'])) return None if('msgtype' in msg['content']): msgtype = msg['content']['msgtype'] if(msgtype == 'm.text'): m = Message(u, msg['content']['body'], room=r, typ=Message.GROUP) self.log.debug('Handling m.text: ' + msg['content']['body']) self.msg_queue.put(m) elif(msgtype == 'm.emote'): m = Message(u, msg['content']['body'], room=r, typ=Message.GROUP, emote=True) self.log.debug('Handling m.emote: ' + msg['content']['body']) self.msg_queue.put(m) elif(msgtype == 'm.image' or msgtype == 'm.audio' or msgtype == 'm.file' or msgtype == 'm.video'): media_url = urlparse(msg['content']['url']) http_url = self.client.api.base_url + "/_matrix/media/r0/download/{0}{1}".format(media_url.netloc, media_url.path) if(msgtype == 'm.image'): body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'an image'), http_url) elif(msgtype == 'm.audio'): body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'an audio file'), http_url) elif(msgtype == 'm.video'): body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'a video file'), http_url) elif(msgtype == 'm.file'): body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'a file'), http_url) m = Message(u, body, room=r, typ=Message.GROUP) self.log.debug("Handling " + msgtype + ": " + body) self.msg_queue.put(m) elif(msgtype == 'm.location'): body = "{0} sent a location: {1}".format(msg['sender'], msg['content']['geo_uri']) m = Message(u, body, room=r, typ=Message.GROUP) self.log.debug('Handling m.location: ' + body) self.msg_queue.put(m) else: self.log.debug('Not handling message, unknown msgtype') elif('membership' in msg): if(msg['membership'] == 'join'): self.room_occupants[r].add(self.new_user(msg['state_key'], Message.GROUP)) elif(msg['membership'] == 'leave'): self.room_occupants[r].remove(self.new_user(msg['state_key'], Message.GROUP)) except KeyError as e: self.log.debug("Incoming message did not have all required fields: " + e.message) def inviteHandler(self, room_id, state): join_on_invite = self.opt('matrix.join_on_invite') invite_events = [x for x in state['events'] if x['type'] == 'm.room.member' and x['state_key'] == str(self.get_user()) and x['content']['membership'] == 'invite'] if(len(invite_events) != 1): raise KeyError("Something's up, found more than one invite state event for " + room_id) inviter = invite_events[0]['sender'] inviter_domain = inviter.split(':')[1] my_domain = str(self.get_user()).split(':')[1] if(join_on_invite == 'accept' or (join_on_invite == 'domain' and inviter_domain == my_domain)): self.log.debug('Joining {} on invite from {}'.format(room_id, inviter)) self.join_room(MatrixRoom(self, room_id)) elif(join_on_invite == 'domain' and inviter_domain != my_domain): self.log.debug("Received invite for {} but inviter {} is on a different homeserver").format(room_id, inviter) else: self.log.debug("Received invite for {} from {} but join_on_invite is disabled".format(room_id, inviter)) # called when the bot is exiting for whatever reason # NOTE: sibylbot will already call part_room() on every room in get_rooms() def shutdown(self): pass # send a message to a user # @param mess (Message) message to be sent # @raise (ConnectFailure) if failed to send message # Check: get_emote() def send(self,mess): (text,to) = (mess.get_text(),mess.get_to()) if(mess.get_emote()): to.room.send_emote(text) else: to.room.send_text(text) # send a message with text to every user in a room # optionally note that the broadcast was requested by a specific User # @param mess (Message) the message to broadcast # @return (str,unicode) the text that was actually sent # Check: get_user(), get_users() def broadcast(self,mess): """send a message to every user in a room""" (text,room,frm) = (mess.get_text(),mess.get_to(),mess.get_user()) users = self.get_occupants(room)+(mess.get_users() or []) # Matrix has no built-in broadcast, so we'll just highlight everyone s = 'all: %s --- ' % text if frm: self.log.debug('Broadcast message from: ' + str(frm)) s += frm.get_name()+' --- ' me = self.get_user() names = [u.get_name() for u in users if (u!=me and (not frm or u!=frm))] s += ', '.join(set(names)) self.send(Message(self.get_user(),s,to=room)) return s # join the specified room using the specified nick and password # @param room (Room) the room to join # @call bot._cb_join_room_success(room) on successful join # @call bot._cb_join_room_failure(room,error) on failed join def join_room(self,room): try: res = self.client.join_room(room.room.room_id) self.bot._cb_join_room_success(room) self.join_timestamps[room] = datetime.datetime.now(pytz.utc) except MatrixRequestError as e: self.bot._cb_join_room_failure(room, e.message) # part the specified room # @param room (Room) the room to leave def part_room(self,room): raise NotImplementedError # helper function for get_rooms() for protocol-specific flags # only needs to handle: FLAG_PARTED, FLAG_PENDING, FLAG_IN, FLAG_ALL # @param flag (int) one of Room.FLAG_* enums # @return (list of Room) rooms matching the flag def _get_rooms(self,flag): mxrooms = self.client.get_rooms() return [self.new_room(mxroom) for mxroom in mxrooms] # @param room (Room) the room to query # @return (list of User) the Users in the specified room def get_occupants(self,room): if(room in self.room_occupants): return list(self.room_occupants[room]) else: memberdict = room.room.get_joined_members() users = [ self.new_user(x) for x in memberdict ] self.room_occupants[room] = set(users) return users # @param room (Room) the room to query # @return (str) the nick name we are using in the specified room def get_nick(self,room): return self.get_user().get_name() # TODO: per-room nicknames # @param room (Room) the room to query # @param nick (str) the nick to examine # @return (User) the "real" User behind the specified nick/room def get_real(self,room,nick): raise NotImplementedError # @return (User) our username def get_user(self): return MatrixUser(self,self.opt('matrix.username'),Message.GROUP) # @param user (str) a user id to parse # @param typ (int) either Message.GROUP or Message.PRIVATE # @param real (User) [self] the "real" user behind this user # @return (User) a new instance of this protocol's User subclass def new_user(self,user,typ=None,real=None): return MatrixUser(self,user,typ,real) # @param name (object) the identifier for this Room # @param nick (str) [None] the nick name to use in this Room # @param pword (str) [None] the password for joining this Room # @return (Room) a new instance of this protocol's Room subclass def new_room(self,room_id_or_alias,nick=None,pword=None): return MatrixRoom(self,room_id_or_alias,nick,pword)
def main(): global client global data global log global lock lock = threading.RLock() log.debug("try lock before main load_data()") with lock: log.debug("success lock before main load_data()") data = load_data() #FIXME отладка парсера #data["users"]["@progserega:matrix.org"]={} #data["users"]["@progserega:matrix.org"]["test"]={} #data["users"]["@progserega:matrix.org"]["test"]["alarms"]=[] #data["users"]["@progserega:matrix.org"]["test"]["lang"]="ru" #print(process_alarm_cmd("@progserega:matrix.org","test","напомни послезавтра после работы проверить звук в машине и подтёки масла, т.к. 11 июня закончится гарантия.")) #sys.exit(1) log.info("try init matrix-client") client = MatrixClient(conf.server) log.info("success init matrix-client") try: log.info("try login matrix-client") client.login_with_password(username=conf.username, password=conf.password) log.info("success login matrix-client") except MatrixRequestError as e: print(e) log.debug(e) if e.code == 403: log.error("Bad username or password.") sys.exit(4) else: log.error("Check your sever details are correct.") sys.exit(2) except MissingSchema as e: log.error("Bad URL format.") print(e) log.debug(e) sys.exit(3) log.info("try init listeners") client.add_listener(on_message) client.add_ephemeral_listener(on_event) client.add_invite_listener(on_invite) #client.start_listener_thread() # Слушанье сокета и пересоединение в случае сбоя: #client.listen_forever(timeout_ms=30000, exception_handler=exception_handler,bad_sync_timeout=5) client.start_listener_thread(exception_handler=exception_handler) #client.listen_forever(timeout_ms=30000, exception_handler=exception_handler,bad_sync_timeout=5) #client.listen_forever() log.info("success init listeners") x = 0 log.info("enter main loop") while True: # Проверяем уведомления: log.debug("try lock before main loop") with lock: log.debug("success lock before main loop") for user in data["users"]: for room in data["users"][user]: for item in data["users"][user][room]["alarms"]: alarm_timestamp = item["time"] alarm_text = item["text"] time_now = time.time() if alarm_timestamp < time_now: # Уведомляем: html = "<p><strong>Напоминаю Вам:</strong></p>\n<ul>\n" html += "<li>%s</li>\n" % alarm_text html += "</ul>\n" if send_html(room, html) == True: data["users"][user][room]["alarms"].remove( item) save_data(data) break # выходим из текущего цикла, т.к. изменили количество в маассиве (валится в корку) - следующей проверкой проверим оставшиеся else: log.error( "error send alarm at '%s' with text: '%s'" % (time.strftime( "%Y.%m.%d-%T", time.localtime(alarm_timestamp)), alarm_text)) #print("step %d"%x) #x+=1 time.sleep(10) log.info("exit main loop")
class Application(QApplication): loggedIn = Signal(str) messageReceived = Signal(object, str, object, float) roomSwitched = Signal(object) roomJoined = Signal(object) roomUpdated = Signal(str, str, str) roomLeft = Signal(object) roomInvited = Signal(object) def __init__(self, *args, **kwargs): super(Application, self).__init__(*args, **kwargs) # setup the application name and icon self.setApplicationName('Qui') self.iconPath = os.path.dirname(__file__) self.iconPath = os.path.join(self.iconPath, 'icons/icon.png') self.setWindowIcon(QIcon(self.iconPath)) # setup the tray icon self.showNotifications = False self.tray = QSystemTrayIcon(QIcon(self.iconPath)) self.tray.show() self.tray.activated.connect(self.showWindow) # setup the tray icon menu self.menu = QMenu() self.menu.addAction('Quit').triggered.connect(self.quit) self.tray.setContextMenu(self.menu) # setup signals self.messageReceived.connect(self.receiveMessage) # show the window self.window = None self.showWindow(None) # try loading the login info self.client = None self.settings = QSettings('Qui', 'Qui') self.url = self.settings.value("url") self.token = self.settings.value("token") self.user = self.settings.value("user") invalid = self.url is None or self.url == "" or self.token is None or self.token == "" or self.user is None or self.user == "" if not invalid: try: self.client = MatrixClient(base_url=self.url, token=self.token, user_id=self.user) self.postLogin() except: invalid = True # show the login form if we can't login if invalid: self.loginForm = LoginForm() self.loginForm.loggedIn.connect(self.login) self.loginForm.show() def showWindow(self, reason): if self.window is None: # make a new window if we don't have one self.window = MainWindow() # setup signals self.loggedIn.connect(self.window.login) self.messageReceived.connect(self.window.receiveMessage) self.roomLeft.connect(self.window.roomLeft) self.roomJoined.connect(self.window.roomJoined) self.roomUpdated.connect(self.window.roomUpdated) self.window.createRoom.connect(self.createRoom) self.window.leaveRoom.connect(self.leaveRoom) self.window.joinRoom.connect(self.joinRoom) # show it self.window.show() else: # show it if it's minimized or something self.window.showNormal() def quit(self): sys.exit(0) def login(self, client, url): self.client = client self.settings.setValue('url', url) self.url = url self.settings.setValue('token', client.token) self.token = client.token self.settings.setValue('user', client.user_id) self.user = client.user_id self.postLogin() def postLogin(self): self.loggedIn.emit(self.user) #self.messages.userId = self.user self.client.add_listener(self.eventCallback) self.client.add_presence_listener(self.presenceCallback) self.client.add_invite_listener(self.inviteCallback) self.client.add_leave_listener(self.leaveCallback) self.client.start_listener_thread() for room, obj in self.client.get_rooms().items(): self.roomJoined.emit(obj) for event in obj.events: self.eventCallback(event) self.showNotifications = True def eventCallback(self, event): if 'type' in event and 'room_id' in event and 'content' in event and event[ 'type'] == 'm.room.message': room = Room(self.client, event['room_id']) self.messageReceived.emit(room, event['sender'], event['content'], time.time() - event['unsigned']['age']) if 'type' in event and 'room_id' in event and 'content' in event and event[ 'type'] == 'm.room.canonical_alias': self.roomUpdated.emit(event['room_id'], 'canonical_alias', event['content']['alias']) def presenceCallback(self, event): return print('presence: {}'.format(event)) def inviteCallback(self, roomId, state): return print('invite: {} {}'.format(roomId, state)) def leaveCallback(self, roomId, room): return print('leave: {} {}'.format(roomId, room)) def receiveMessage(self, room, sender, content, timestamp): if self.showNotifications: if self.window is None or not self.window.isActiveWindow(): self.tray.showMessage(sender, content['body']) def leaveRoom(self, room): self.client.api.leave_room(room.room_id) self.roomLeft.emit(room) def joinRoom(self, roomId): room = self.client.join_room(roomId) self.roomJoined.emit(room) def createRoom(self, roomId): room = self.client.create_room(roomId) self.roomJoined.emit(room)
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'])
def main(): global client global data global log global lock lock = threading.RLock() log.debug("try lock before main load_data()") with lock: log.debug("success lock before main load_data()") data=load_data() log.debug("release lock() after access global data") log.info("try init matrix-client") client = MatrixClient(conf.server) log.info("success init matrix-client") try: log.info("try login matrix-client") token = client.login(username=conf.username, password=conf.password,device_id=conf.device_id) log.info("success login matrix-client") except MatrixRequestError as e: print(e) log.debug(e) if e.code == 403: log.error("Bad username or password.") sys.exit(4) else: log.error("Check your sever details are correct.") sys.exit(2) except MissingSchema as e: log.error("Bad URL format.") print(e) log.debug(e) sys.exit(3) log.info("try init listeners") client.add_listener(on_message) client.add_ephemeral_listener(on_event) client.add_invite_listener(on_invite) client.start_listener_thread(exception_handler=exception_handler) log.info("success init listeners") try: x=0 log.info("enter main loop") while True: if conf.type_translate == "yandex_long": # check yandex_long_jobs: num_jobs=0 for room_id in data["rooms"]: if "jobs" in data["rooms"][room_id]: for job in data["rooms"][room_id]["jobs"]: ret_value=False num_jobs+=1 ret_value=check_long_yandex_job(log,room_id,data["rooms"][room_id]["jobs"],job) if ret_value==False: log.error("check_long_yandex_job(), room_id=%s, job_id=%s"%(room_id,job["id"])) result_string="error get result from yandex speech api - yandex api error" log.error(result_string) if send_notice(room_id,result_string)==False: log.error("send_notice(%s)"%room_id) if num_jobs > 0: log.debug("len jobs list for all rooms = %d"%num_jobs) time.sleep(3) except Exception as e: log.error(get_exception_traceback_descr(e)) log.error("exception at main loop check jobs: %s"%e) sys.exit(1) log.info("exit main loop")
def start(stdscr): global server, base_url, username, access_token, password global size, room, rooms, all_rooms, lastEventRoom, room_keys curses.curs_set(0) curses.use_default_colors() size = stdscr.getmaxyx() stdscr.addstr(0, 0, "loading...") stdscr.refresh() loadCredentials("./credentials.json") client = MatrixClient(base_url, token=access_token, user_id='@{}:{}'.format(username, server)) if access_token is None: access_token = client.login_with_password(username, password, size[0]) rooms = client.get_rooms() all_rooms = "all rooms" rooms[all_rooms] = all_rooms room_keys = list(rooms.keys()) room = all_rooms curses.halfdelay(10) maxDisplayName = 24 displayNamestartingPos = 20 PAD_COMMENTS = True pause = False client.add_listener(processMessage) client.start_listener_thread() curses.echo() stdscr.keypad(True) inputBuffer = "" lastEventRoom = all_rooms the_room_to_post_to = None # store the last room we saw before we started typing while (True): size = stdscr.getmaxyx() maxChars = size[1] - 1 - len(username) - 3 stdscr.clear() line = "redpill v0.7" line += " · screen size: " + str(size) if isinstance(rooms[room], Room): line += " · chat size: " + str(len(rooms[room].events)) line += " · room: " # we want NAME aka ALIAS[0] (ROOM_ID) # or 2nd choice: ALIAS[0] (ROOM_ID) # or fallback: ROOM_ID if room == all_rooms: line += str(room) elif rooms[room].name: if len(rooms[room].aliases) > 0 and rooms[room].aliases[0] != room: line += rooms[room].name + " aka " + getFirstRoomAlias( rooms[room]) + " (" + str(room) + ")" elif rooms[room].name != room: line += rooms[room].name + " (" + str(room) + ")" else: line += str(room) elif len(rooms[room].aliases) > 0 and rooms[room].aliases[0] != room: line += rooms[room].aliases[0] + " (" + str(line) + ")" else: line += str(room) if isinstance(rooms[room], Room) and rooms[room].topic is not None: line += " · topic: " + rooms[room].topic line += " · variables: room: " + room + ", last: " + lastEventRoom stdscr.addstr(0, 0, line, curses.A_UNDERLINE) current = len(rooms[room].events) - 1 if isinstance(rooms[room], Room) else -1 if True: y = 1 if current >= 0: # TODO: something when the first event is a typing event currentLine = size[0] - 1 # input space = "" for i in range(size[1] - 1): space += " " stdscr.addstr(currentLine, 0, space, curses.A_DIM) stdscr.addstr(currentLine, 0, "<" + username + ">", curses.A_DIM) stdscr.addstr(currentLine - 1, 0, space, curses.A_UNDERLINE) for event in reversed(rooms[room].events): log(event) log("maxDisplayName: {}, size: {}, currentLine: {}".format( maxDisplayName, size, currentLine)) if event["type"] == "m.typing": #if True: continue # do something clever elif event["type"] == "m.presence": #if True: continue # do something clever elif event["type"] == "m.roomchange": room_id = event["room_id"] #lin = (str(rooms[room_id].name) + " aka " + getFirstRoomAlias(rooms[room_id]) + " (" + # rooms[room_id].room_id + ")") line = room_id if line == all_rooms: pass elif rooms[line].name is None: if len( rooms[room_id].aliases ) > 0 and rooms[room_id].aliases[0] != room_id: line = rooms[room_id].aliases[ 0] + " (" + line + ")" else: if len( rooms[room_id].aliases ) > 0 and rooms[room_id].aliases[0] != room_id: line = rooms[ room_id].name + " aka " + getFirstRoomAlias( rooms[room_id]) + " (" + line + ")" else: if rooms[room_id].name != room_id: line = rooms[ room_id].name + " (" + line + ")" #if rooms[room].topic is not None: # line += " · topic: " + rooms[room].topic currentLine -= 1 stdscr.addstr(currentLine, 0, "Event(s) from " + line, curses.A_DIM) else: #currentLine = size[0] - y #currentLine -= 1 if currentLine < 3: # how many lines we want to reserve break #if currentLine == 5: # currentLine -= 1 y += 1 if "origin_server_ts" in event: convertedDate = datetime.datetime.fromtimestamp( int(event["origin_server_ts"] / 1000)).strftime('%Y-%m-%d %H:%M:%S') # assumption: body == normal message length = len( event["sender"]) if "sender" in event else 0 log("length: {}, currentLine: {}".format( length, currentLine)) if "body" in event["content"]: rawText = event["content"][ "body"] # .encode('utf-8') if event["content"]["msgtype"] == "m.emote": if len(rawText) > 0 and rawText[0] == " ": rawText = rawText[1:] linesNeeded = 0 buf = "" lineByLineText = [] first = True bufSinceLastWord = "" for char in rawText: if True: #for char in line: bufSinceLastWord += char if char == '\n': linesNeeded += 1 buf += bufSinceLastWord if PAD_COMMENTS or first: linesNeeded += int( (displayNamestartingPos + maxDisplayName + 3 + len(buf)) / size[1]) else: linesNeeded += int( len(buf) / size[1]) first = False lineByLineText.append(buf) buf = "" bufSinceLastWord = "" else: if ((PAD_COMMENTS and (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1) or (not PAD_COMMENTS and (len(buf + bufSinceLastWord)) == size[1] - 1)): #if (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1: if len(buf) == 0: buf += bufSinceLastWord bufSinceLastWord = "" if char.isspace(): buf += bufSinceLastWord lineByLineText.append(buf) bufSinceLastWord = "" buf = "" else: lineByLineText.append(buf) buf = bufSinceLastWord bufSinceLastWord = "" linesNeeded += 1 if char.isspace(): buf += bufSinceLastWord bufSinceLastWord = "" # if (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1: if ((PAD_COMMENTS and (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1) or (not PAD_COMMENTS and (len(buf + bufSinceLastWord)) == size[1] - 1)): buf += bufSinceLastWord bufSinceLastWord = "" lineByLineText.append(buf) linesNeeded += 1 buf = "" #elif char == ' ': # skip all whitespace # self.X += 1 buf += bufSinceLastWord lineByLineText.append(buf) linesNeeded += int( (displayNamestartingPos + maxDisplayName + 3 + len(buf)) / size[1]) buf = "" if currentLine - linesNeeded < 2: # how many lines we want to reserve break if PAD_COMMENTS: pad = displayNamestartingPos + maxDisplayName + 3 linesNeeded += 1 currentLine -= linesNeeded for i in range(linesNeeded): buf = rawText[:size[1] - pad] rawText = rawText[size[1] - pad:] if currentLine + i == size[0] - 2: stdscr.addstr( currentLine + i, displayNamestartingPos + maxDisplayName + 3, lineByLineText[i], curses.A_BOLD + curses.A_UNDERLINE) else: try: stdscr.addstr( currentLine + i, displayNamestartingPos + maxDisplayName + 3, lineByLineText[i], curses.A_BOLD) except: e = sys.exc_info()[0] print( "Error: unable to start thread. " + str(e)) stdscr.addstr(1, 0, str(e)) else: # TODO: need to split this out to get proper underline currentLine -= linesNeeded if currentLine == size[0] - 2: stdscr.addstr( currentLine, displayNamestartingPos + maxDisplayName + 3, rawText, curses.A_BOLD + curses.A_UNDERLINE) else: stdscr.addstr( currentLine, displayNamestartingPos + maxDisplayName + 3, rawText, curses.A_BOLD) usern = event["sender"] if length > maxDisplayName: usern = usern[:maxDisplayName - 3] + "..." if event["content"]["msgtype"] == "m.emote": usern = "* " + usern if currentLine == size[0] - 2: stdscr.addstr( currentLine, displayNamestartingPos + max(0, maxDisplayName - length), str(usern), curses.A_UNDERLINE + curses.A_BOLD) else: stdscr.addstr( currentLine, displayNamestartingPos + max(0, maxDisplayName - length), str(usern), curses.A_BOLD) else: usern = "<" + usern + ">" if currentLine == size[0] - 2: stdscr.addstr( currentLine, displayNamestartingPos + max(0, maxDisplayName - length), str(usern), curses.A_UNDERLINE) else: stdscr.addstr( currentLine, displayNamestartingPos + max(0, maxDisplayName - length), str(usern)) if currentLine == size[0] - 2: stdscr.addstr(currentLine, 0, convertedDate, curses.A_UNDERLINE) else: stdscr.addstr(currentLine, 0, convertedDate) #if currentLine == size[1]: # last line # stdscr.addstr( # currentLine, displayNamestartingPos + # maxDisplayName + 3, buf[:size[1] - # (displayNamestartingPos + maxDisplayName + 4)], # curses.A_BOLD # ) #else: # stdscr.addstr( # currentLine, displayNamestartingPos + # maxDisplayName + 3, buf, # curses.A_BOLD # ) # membership == join/leave events elif "membership" in event["content"]: buf = " invited someone" if event["content"]["membership"] == "invite": if "state_key" in event: buf = " invited " + event["state_key"] elif event["content"]["membership"] == "join": buf = " has joined" elif event["content"]["membership"] == "leave": buf = " has left" currentLine -= 1 if length > maxDisplayName: if currentLine == size[0] - 2: stdscr.addstr( currentLine, displayNamestartingPos + 1, str(event["sender"]), curses.A_DIM + curses.A_UNDERLINE) stdscr.addstr( currentLine, displayNamestartingPos + length + 1, buf, curses.A_DIM + curses.A_UNDERLINE) else: stdscr.addstr(currentLine, displayNamestartingPos + 1, str(event["sender"]), curses.A_DIM) stdscr.addstr( currentLine, displayNamestartingPos + length + 1, buf, curses.A_DIM) else: if currentLine == size[0] - 2: stdscr.addstr( currentLine, displayNamestartingPos + 1 + maxDisplayName - length, str(event["sender"]), curses.A_DIM + curses.A_UNDERLINE) stdscr.addstr( currentLine, displayNamestartingPos + maxDisplayName + 1, buf, curses.A_DIM + curses.A_UNDERLINE) else: stdscr.addstr( currentLine, displayNamestartingPos + 1 + maxDisplayName - length, str(event["sender"]), curses.A_DIM) stdscr.addstr( currentLine, displayNamestartingPos + maxDisplayName + 1, buf, curses.A_DIM) current -= 1 if pause: stdscr.addstr( int(size[0] / 2) - 1, int(size[1] / 2), " ", curses.A_REVERSE) stdscr.addstr(int(size[0] / 2), int(size[1] / 2), " PAUSED ", curses.A_REVERSE) stdscr.addstr( int(size[0] / 2) + 1, int(size[1] / 2), " ", curses.A_REVERSE) try: stdscr.addstr(size[0] - 1, len(username) + 3, inputBuffer[-maxChars:]) except: e = sys.exc_info()[0] print("Error: unable to start thread. " + str(e)) stdscr.addstr(1, 0, str(e)) stdscr.refresh() # getInput(stdscr) #def getInput(stdscr): # if True: try: c = stdscr.getch(size[0] - 1, len(username) + 3) #c = stdscr.getkey(size[0] - 1, len(username) + 3) #stri = stdscr.getstr(size[0] - 1, len(username) + 3, 10) if c == -1: stdscr.addstr(1, 0, "timeout") else: if c <= 256 and c != 10 and c != 9: ## enter and tab inputBuffer += chr(c) if len(inputBuffer) == 1: # e.g. just started typing if lastEventRoom != all_rooms: the_room_to_post_to = lastEventRoom if c == 9: #stdscr.addstr(1, 0, "%s was pressed\n" % c) log("key pressed: 0x{:X}".format(c)) room = room_keys[(room_keys.index(room) + 1) % len(rooms)] the_room_to_post_to = None elif c == 10: # enter with open('redpill-sends.log', 'a') as the_file: the_file.write("the_room_to_post_to:" + str(the_room_to_post_to) + "\n") the_file.write("lastEventRoom: " + str(lastEventRoom) + "\n") the_file.write("room: " + str(room) + "\n") the_file.write("inputBuffer: " + str(inputBuffer) + "\n") the_file.write("---\n") if inputBuffer.startswith("/invite"): user_id = inputBuffer[7:].strip() rooms[room].invite_user(user_id) elif inputBuffer.startswith("/kick"): user_id = inputBuffer[5:].strip() reason = "no reason..." rooms[room].kick_user(user_id, reason) elif inputBuffer.startswith("/power"): user_id = inputBuffer[7:].strip() power_level = 50 rooms[room].set_power_level(user_id, power_level) elif inputBuffer.startswith("/op"): user_id = inputBuffer[2:].strip() rooms[room].set_power_level(user_id) elif inputBuffer.startswith("/ban"): # reason user_id = inputBuffer[4:].strip() reason = "sux" #FIXME rooms[room].ban(user_id, reason) elif inputBuffer.startswith( "/join"): # there's a /join that supports aliases room_alias = inputBuffer[5:].strip() client.join_room(room_alias) elif inputBuffer.startswith("/j"): room_alias = inputBuffer[2:].strip() client.join_room(room_alias) elif inputBuffer.startswith("/leave"): rooms[room].leave_room(room_id) elif inputBuffer.startswith("/create"): # create a new room is_public = True invitees = () # def create_room(self, alias=None, is_public=False, invitees=()): room_alias = inputBuffer[7:].strip() client.create_room(room_alias, is_public, invitees) elif inputBuffer.startswith("/topic"): # get or set topic new_topic = inputBuffer[6:].strip() if len(new_topic) > 0: rooms[room].topic = new_topic else: pass #rooms[room].topic = "fail" else: if room == all_rooms: if the_room_to_post_to is None: if lastEventRoom != all_rooms: the_room_to_post_to = lastEventRoom else: stdscr.addstr(1, 0, "No idea what room to post to!") stdscr.refresh() inputBuffer = "No idea what room to post to!" continue else: the_room_to_post_to = room if inputBuffer.startswith("/me"): rooms[the_room_to_post_to].send_emote(inputBuffer[3:]) else: rooms[the_room_to_post_to].send_text(inputBuffer) inputBuffer = "" the_room_to_post_to = None elif c == curses.KEY_DC: inputBuffer = "" the_room_to_post_to = None elif c == curses.KEY_BACKSPACE: if len(inputBuffer) > 0: inputBuffer = inputBuffer[:-1] if len(inputBuffer) == 0: the_room_to_post_to = None elif c == curses.KEY_IC: pause = not (pause) if pause: curses.nocbreak() curses.cbreak() stdscr.timeout(-1) stdscr.addstr( int(size[0] / 2) - 1, int(size[1] / 2), " ", curses.A_REVERSE) stdscr.addstr(int(size[0] / 2), int(size[1] / 2), " PAUSING ", curses.A_REVERSE) stdscr.addstr( int(size[0] / 2) + 1, int(size[1] / 2), " ", curses.A_REVERSE) stdscr.refresh() else: stdscr.addstr( int(size[0] / 2) - 1, int(size[1] / 2), " ", curses.A_REVERSE) stdscr.addstr(int(size[0] / 2), int(size[1] / 2), " RESUMING ", curses.A_REVERSE) stdscr.addstr( int(size[0] / 2) + 1, int(size[1] / 2), " ", curses.A_REVERSE) stdscr.refresh() curses.halfdelay(10) stdscr.timeout(1) elif c == 27: # need to test for alt combo or ESC curses.cbreak() curses.echo() #curses.curs_set(1) stdscr.keypad(0) curses.endwin() quit() elif c == curses.KEY_F2: PAD_COMMENTS = not PAD_COMMENTS #stdscr.addstr(2, 0, "time() == %s\n" % time.time()) finally: do_nothing = True
class MatrixBackend(ErrBot): def __init__(self, config): super().__init__(config) identity = config.BOT_IDENTITY self.token = identity["token"] self.url = identity["url"] self.user = identity["user"] self._client = None def build_identifier(self, text_representation: str) -> None: """Return an object that idenfifies a matrix person or room.""" pass @staticmethod def parse_identfier_pieces(regex: str, text_rep: str): m = re.match(regex, text_rep) if m: data, domain = m.group() return data, domain return None, None @staticmethod def parse_identfier(text_rep): """Parse matrix identifiers into usable types. Expected formats are as follows: !<room>:<domain> #<room>:<domain> @<user>:<domain> """ room, domain, user = None, None, None room, domain = MatrixBackend.parse_identfier_pieces( r"[!#](.*):(.*)", text_rep) if not room or not domain: user, domain = MatrixBackend.parse_identfier_pieces( r"@:(.*):(.*)", text_rep) return room, domain, user def send_msg(self, room_id, msg): room = self._client.join_room(room_id) return room.send_text(msg) def send_file(self, room_id, file_path, filename): with open(file_path, "rb") as f: content = f.read() return self.send_stream_content(room_id, content, filename) def send_stream_content(self, room_id, content, filename): res = self._client.upload(content, 'application/octet-stream', filename) room = self._client.join_room(room_id) room.send_file(res, filename) return res def build_reply(self, msg, text=None, private=False, threaded=False): response = self.build_message(text) response.frm = self.bot_identifier self.send_msg(msg.extras['room_id'], text) if private: response.to = msg.frm else: response.to = msg.frm.room if isinstance(msg.frm, RoomOccupant) else msg.frm return response def change_presence(self): pass def mode(self): pass def query_room(self): pass def rooms(self): pass def invite_callback(self, *args, **kwargs): print(args, kwargs) def ephemeral_callback(self, *args, **kwargs): print(args, kwargs) def leave_callback(self, *args, **kwargs): print(args, kwargs) def presence_callback(self, *args, **kwargs): print(args, kwargs) def callback(self, *events): for event in events: log.debug("Saw event %s.", event) if event["type"] == "m.room.message": content = event["content"] sender = event["sender"] if content["msgtype"] == "m.text": msg = Message(content["body"], extras={'room_id': event["room_id"]}) msg.frm = MatrixPerson(self._client, sender) msg.to = self.bot_identifier self.callback_message(msg) def serve_once(self): self._client = MatrixClient(self.url, token=self.token, user_id=self.user) self._client.add_listener(self.callback) self._client.add_invite_listener(self.invite_callback) self._client.add_ephemeral_listener(self.ephemeral_callback) self._client.add_leave_listener(self.leave_callback) self._client.add_presence_listener(self.presence_callback) self.connect_callback() self.bot_identifier = MatrixPerson(self._client, self.user) self._client.listen_forever()
class JokeBot: bot_startcmd = '!joke' bot_display_name = 'JokeBot' auto_join_invited_rooms = True mcl = None init_done = False admin_ids = set() def __init__(self, filename=CONFIG_FILENAME): logging.debug('load config') config_dic = load_yaml_config(filename) matrix_server = config_dic['matrix_server'] login_with_token = False if matrix_server.get('token', ''): if not matrix_server.get('user_id', ''): matrix_server['user_id'] = config_dic['matrix_user'][ 'username'] login_with_token = True else: matrix_user = config_dic['matrix_user'] bot_startcmd = config_dic.get('bot_startcmd') if bot_startcmd: self.bot_startcmd = bot_startcmd bot_display_name = config_dic.get('bot_display_name') if bot_display_name: self.bot_display_name = bot_display_name self.auto_join_invited_rooms = config_dic.get( 'auto_join_invited_rooms', True) self.admin_ids = set(config_dic.get('admin_ids', [])) logging.debug('init bot') if login_with_token: logging.debug('init bot with token') self.mcl = MatrixClient(**matrix_server) else: logging.debug('init bot with password') self.mcl = MatrixClient(**matrix_server) self.mcl.login_with_password_no_sync(**matrix_user) m_user = self.mcl.get_user(self.mcl.user_id) if m_user.get_display_name() != self.bot_display_name: m_user.set_display_name(self.bot_display_name) self.mcl.add_invite_listener(self.process_invite) self.mcl.add_listener(self.process_message, 'm.room.message') self.init_done = True logging.info('bot initialization successful') def run(self): if self.init_done: logging.debug('run listen_forever') self.mcl.listen_forever() else: logging.warning('bot not initialized successful') def join_room(self, room_id): self.ignore_room_temporary( room_id ) # necessary while joining room because otherwise old messages would be processed try: logging.info('joining new room {}'.format(room_id)) room = self.mcl.join_room(room_id) room.send_text( "Welcome! I'm a joke bot. Type '{}' and I will tell you a joke." .format(self.bot_startcmd)) return True except: logging.exception( 'Exception while joining room {}'.format(room_id)) return False temp_ignored_rooms = set() def temp_ignore_room_thread(self, room_id): logging.debug('temporary ignoring room {}'.format(room_id)) self.temp_ignored_rooms.add(room_id) time.sleep(10) self.temp_ignored_rooms.remove(room_id) logging.debug('not ignoring room {} any more'.format(room_id)) def ignore_room_temporary(self, room_id): threading.Thread(target=self.temp_ignore_room_thread, args=[room_id], daemon=True).start() def leave_room(self, room_id): logging.debug('trying to leave room with id {}'.format(room_id)) leave_room = self.mcl.get_rooms().get(room_id, '') if not leave_room: logging.debug('bot not in room with id {}'.format(room_id)) return False if leave_room.leave(): logging.debug('leaving room {} was successful'.format(room_id)) return True else: logging.debug('failed to leave known room with id {}'.format( leave_room.room_id)) return False def process_invite(self, room_id, state=None): logging.debug('received invitation of {}'.format(room_id)) if self.auto_join_invited_rooms: self.join_room(room_id) def evaluate_bot_message(self, room, sender, msg): if msg.startswith('ctl'): logging.debug("received control message '{}' in room '{}'".format( msg, room.room_id)) if sender not in self.admin_ids: logging.debug( '{} has no permissions to send a ctl-message'.format( sender)) room.send_notice( '{} has no permissions to send a ctl-message'.format( sender)) return data = msg.split(' ')[1:] if len(data) == 2: if data[0] == 'join': if not self.join_room(data[1]): room.send_notice( 'something went wrong while joining room') elif data[0] == 'leave': if data[1] == 'this': data[1] = room.room_id if not self.leave_room(data[1]): room.send_notice('room could not be left') return logging.info('sending joke to room {}'.format(room.room_id)) answer = '...' data = msg.split(' ')[1:] # remove first empty string if len(data) == 0: answer = get_joke() elif len(data) == 1: answer = get_joke(data[0]) elif len(data) == 2: answer = get_joke(data[0], data[1]) logging.debug('starting room send text') room.send_text(answer) logging.debug('done room send text') def process_message(self, roomchunk): if roomchunk['sender'] == self.mcl.user_id: return if roomchunk['room_id'] in self.temp_ignored_rooms: logging.debug('ignoring room {} temporary'.format( roomchunk['room_id'])) return content = roomchunk['content'] if content['msgtype'] == 'm.text': msg = content['body'] if msg.startswith(self.bot_startcmd): room = self.mcl.get_rooms()[roomchunk['room_id']] msg = msg[len(self.bot_startcmd):] self.evaluate_bot_message(room, roomchunk['sender'], msg)
class MatrixBackend(ErrBot): def __init__(self, config): super().__init__(config) if not hasattr(config, 'MATRIX_HOMESERVER'): log.fatal(""" You need to specify a homeserver to connect to in config.MATRIX_HOMESERVER. For example: MATRIX_HOMESERVER = "https://matrix.org" """) sys.exit(1) self._homeserver = config.MATRIX_HOMESERVER self._username = config.BOT_IDENTITY['username'] self._password = config.BOT_IDENTITY['password'] self._api = None self._token = None def serve_once(self): def dispatch_event(event): log.info("Received event: %s" % event) if event['type'] == "m.room.member": if event['membership'] == "invite" and event[ 'state_key'] == self._client.user_id: room_id = event['room_id'] self._client.join_room(room_id) log.info("Auto-joined room: %s" % room_id) if event['type'] == "m.room.message" and event[ 'sender'] != self._client.user_id: sender = event['sender'] room_id = event['room_id'] body = event['content']['body'] log.info("Received message from %s in room %s" % (sender, room_id)) # msg = Message(body) # msg.frm = MatrixPerson(self._client, sender, room_id) # msg.to = MatrixPerson(self._client, self._client.user_id, room_id) # self.callback_message(msg) msg = self.build_message(body) room = MatrixRoom(room_id) msg.frm = MatrixRoomOccupant(self._api, room, sender) msg.to = room self.callback_message(msg) self.reset_reconnection_count() self.connect_callback() self._client = MatrixClient(self._homeserver) try: self._token = self._client.register_with_password( self._username, self._password, ) except MatrixRequestError as e: if e.code == 400: try: self._token = self._client.login_with_password( self._username, self._password, ) except MatrixRequestError: log.fatal(""" Incorrect username or password specified in config.BOT_IDENTITY['username'] or config.BOT_IDENTITY['password']. """) sys.exit(1) self._api = MatrixHttpApi(self._homeserver, self._token) self.bot_identifier = MatrixPerson(self._api) self._client.add_listener(dispatch_event) try: while True: self._client.listen_for_events() except KeyboardInterrupt: log.info("Interrupt received, shutting down...") return True finally: self.disconnect_callback() def rooms(self): rooms = [] raw_rooms = self._client.get_rooms() for rid, robject in raw_rooms: # TODO: Get the canonical alias rather than the first one from # `Room.aliases`. log.debug('Found room %s (aka %s)' % (rid, rid.aliases[0])) def send_message(self, mess): super().send_message(mess) room_id = mess.to.room.id text_content = item_url = mess.body if item_url.startswith("http://") or item_url.startswith("https://"): if item_url.endswith("gif"): self._api.send_content(room_id, item_url, "image", "m.image") return # text_content = Markdown().convert(mess.body) self._api.send_message(room_id, text_content) def connect_callback(self): super().connect_callback() def build_identifier(self, txtrep): raise Exception("XXX") def build_reply(self, mess, text=None, private=False): log.info("build_reply") response = self.build_message(text) response.frm = self.bot_identifier response.to = mess.frm return response def change_presence(self, status: str = '', message: str = ''): raise Exception("XXX") @property def mode(self): return 'matrix' def query_room(self, room): raise Exception("XXX")
from matrix_client.client import MatrixClient import time client = MatrixClient("http://167.99.238.80:8008") # New user #token = client.register_with_password(username="******", password="******") def listener(event): print(event) # Existing user token = client.login_with_password(username="******", password="******") client.add_listener(listener) #room = client.create_room("my_room_alias") room = client.join_room("#my_room_alias:teamspeak.sigmapenguinplace.net") room.send_text("Hello!") while True: client.listen_for_events()
class Matrix(): def __init__(self): self.rooms = {} self.matrixClient = {} def login(self): global theBot config = bot.theBot.config #variables self.matrixClient = MatrixClient(config.matrix["clienturl"]) if config.matrix["password"] == "": config.matrix["password"] = getpass.getpass(prompt='Password: '******'t find room:"+room) sys.exit(12) def handleException( self, exception ): print ( exception ) print ("An Error occured, ignoring") return def listen(self): global theBot config = bot.theBot.config #if success, start the command listener. for i in self.rooms: self.rooms[i].add_listener( self.listener ) self.matrixClient.add_listener( self.clientEvent ); self.matrixClient.add_leave_listener( self.leftRoom ); self.matrixClient.start_listener_thread( exception_handler = self.handleException ) def clientEvent( self, event ): if event["type"] == "m.room.power_levels": print("Damn it Hellbacon!") def leftRoom(self, room_id, event): #room_id, not to be mistaken for room name. #this shouldn't happen, so rejoin. room = "" time.sleep(10) try: for i in self.rooms: if self.rooms[i].room_id == room_id: room = i self.rooms[room].leave();#remove the room, before rejoining. self.rooms[room] = self.matrixClient.join_room(room) self.rooms[room].add_listener( self.listener ) 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:"+room) sys.exit(12) def listener(self, room, event): global theBot config = bot.theBot.config if event['type'] == "m.room.message": if event['content']['msgtype'] == "m.text": #print("{0}: {1}".format(event['sender'], event['content']['body'])) # ignore anything the bot might send to itself if(event['sender'] == "@"+config.matrix["username"]+":cclub.cs.wmich.edu"): return #built in auto response to mention. if ( config.matrix["username"] + ":" in event['content']['body']): room.send_text("Hi! I am a bot. If you want to know my commands type \""+config.prefix+"commands\" for available commands") # split the string to commands input = event['content']['body'].split(" ") # create responses for messages starting with prefix if len(input) > 0: if len(input[0]) > 0: ep = commandcenter.EventPackage() ep.body = input ep.room_id = event["room_id"] ep.sender = event["sender"] ep.event = event ep.command = input[0] if ep.command == config.prefix+"purge": self.purge(ep) else: output = bot.theBot.cc.run(ep) # if the command is in our dictionary of functions, use it (from commands.py) if len(output) > 0: room.send_text(output) else: print(event['type']) def purge(self, event ): return