async def _handle_account(self, parts: List[str]) -> str: """ Handle account specific commands received from client """ # prepare everything for the actual command handling later acc_id = -1 params = [] if parts[1] == "list": # special case for "list" command command = parts[1] elif parts[1] == "add": # special case for "add" command command = parts[1] params = parts[2:] elif len(parts) >= 3: # account specific commands try: acc_id = int(parts[1]) except ValueError: return Message.error("invalid account ID") command = parts[2] params = parts[3:] # valid account? if acc_id not in self.account_list.get().keys(): return Message.error("invalid account") else: # invalid command, ignore return Message.error("invalid command") return await self._handle_account_command(command, acc_id, params)
def get_buddies(self, online: bool) -> None: """ Get roster/buddy list """ # if we are offline, there are no buddies if self.client.status == "offline": return # if only online wanted, skip because no matrix room is "online" if online: return # get buddies/rooms rooms = self.client.get_rooms() for room in rooms.values(): name = escape_name(room.display_name) # use special status for group chats status = "GROUP_CHAT" # send buddy message msg = Message.buddy(self.account, room.room_id, name, status) self.account.receive_msg(msg) # handle pending room invites as temporary buddies invites = self.client.get_invites() for invite in invites.values(): room_id, room_name, _sender, _sender_name, _tstamp = invite status = "GROUP_CHAT_INVITE" # send buddy message msg = Message.buddy(self.account, room_id, room_name, status) self.account.receive_msg(msg)
async def handle_account_list(self) -> str: """ List all accounts """ replies = [] accounts = self.account_list.get() for acc in accounts.values(): reply = Message.account(acc) replies.append(reply) # inform caller that all accounts have been received replies.append(Message.info("listed accounts.")) # add account add help if there are no accounts if not accounts: replies.append(await self.callbacks.call(Callback.HELP_ACCOUNT_ADD, None, ())) # log event log_msg = f"account list: {replies}" logging.info(log_msg) # return a single string return "".join(replies)
async def _help_welcome(self, _account: Optional["Account"], _cmd: Callback, _params: Tuple) -> str: """ Handle welcome help message event """ welcome = Message.info(f"Welcome to nuqql-matrixd v{VERSION}!") welcome += Message.info("Enter \"help\" for a list of available " "commands and their help texts") if self.based.config.get_push_accounts(): welcome += Message.info("Listing your accounts:") return welcome
async def _help_account_add(_account: Optional["Account"], _cmd: Callback, _params: Tuple) -> str: """ Handle account add help event """ add_help = Message.info("You do not have any accounts configured.") add_help += Message.info("You can add a new matrix account with the " "following command: " "account add matrix <username>@<homeserver> " "<password>") add_help += Message.info("Example: account add matrix " "[email protected] MyPassword") return add_help
def _get_status(self) -> None: """ Get the current status of the account """ self.account.receive_msg( Message.status(self.account, self.client.status))
async def _handle_account_collect(self, acc_id: int, params: List[str]) -> str: """ Collect messages for a specific account. Expected format: account <ID> collect [time] params does not include "account <ID> collect" """ # collect all messages since <time>? time = 0 # TODO: change it to time of last collect? if len(params) >= 1: time = int(params[0]) # log event log_msg = f"account {acc_id} collect {time}" logging.info(log_msg) # collect messages accounts = self.account_list.get() acc = accounts[acc_id] history = acc.get_history() # TODO: this expects a list. change to string? document list req? history += await self.callbacks.call(Callback.COLLECT_MESSAGES, acc, ()) # append info message to notify caller that everything was collected history += [Message.info(f"collected messages for account {acc_id}.")] # return history as single string return "".join(history)
async def _handle_account_command(self, command: str, acc_id: int, params: List[str]) -> str: if command == "list": return await self.handle_account_list() if command == "add": # currently this supports "account <ID> add" and "account add <ID>" # if the account ID is valid return await self._handle_account_add(params) if command == "delete": return await self._handle_account_delete(acc_id) # handle other commands with same parameters command_map = { "buddies": self._handle_account_buddies, "collect": self._handle_account_collect, "send": self._handle_account_send, "status": self._handle_account_status, "chat": self._handle_account_chat, } if command in command_map: return await command_map[command](acc_id, params) return Message.error("unknown command")
def _chat_join(self, chat: str) -> None: """ Join chat on account """ error = self.client.join_room(chat) if error != "": self.account.receive_msg(Message.error(error))
def _chat_create(self, name: str) -> None: """ Create a group chat room with name <name> """ error = self.client.create_room(name) if error != "": self.account.receive_msg(Message.error(error))
def _chat_part(self, chat: str) -> None: """ Leave chat on account """ error = self.client.part_room(chat) if error != "": self.account.receive_msg(Message.error(error))
def _chat_invite(self, chat: str, user_id: str) -> None: """ Invite user to chat """ error = self.client.invite_room(chat, user_id) if error != "": self.account.receive_msg(Message.error(error))
async def get_status(acc: Optional["Account"], _cmd: Callback, _params: Tuple) -> str: """ Get the status of the account """ assert acc acc.receive_msg(Message.status(acc, acc.status)) return ""
async def add(self, acc_type: str, acc_user: str, acc_pass: str, acc_id: int = None) -> str: """ Add a new account """ # make sure the account does not exist for acc in self.accounts.values(): if acc.type == acc_type and acc.user == acc_user: return Message.info("account already exists.") # get a free account id if none is given if acc_id is None: acc_id = self._get_free_account_id() # create account and add it to list new_acc = Account(config=self.config, callbacks=self.callbacks, queue=self.queue, aid=acc_id) new_acc.type = acc_type new_acc.user = acc_user new_acc.password = acc_pass self.accounts[new_acc.aid] = new_acc # store updated accounts in file self.store() # log event log_msg = (f"account new: id {new_acc.aid} type {new_acc.type} " f"user {new_acc.user}") logging.info(log_msg) # notify callback (if present) about new account await self.callbacks.call(Callback.ADD_ACCOUNT, new_acc, ()) # return result result = Message.info(f"added account {new_acc.aid}.") if self.config.get_push_accounts(): result += Message.account(new_acc) return result
def _chat_list(self) -> None: """ List active chats of account """ rooms = self.client.get_rooms() for room in rooms.values(): self.account.receive_msg( Message.chat_list(self.account, room.room_id, escape_name(room.display_name), self.user))
def receive_msg(self, msg: str) -> None: """ Receive a message from other users or the backend """ self.queue.put_nowait(msg) if Message.is_message(msg) and self.config.get_history(): # TODO: add timestamp? self._history.append(msg)
async def set_status(acc: Optional["Account"], _cmd: Callback, params: Tuple) -> str: """ Set the status of the account """ assert acc status = params[0] acc.status = status acc.receive_msg(Message.status(acc, status)) return ""
async def _handle_version(self) -> Tuple[str, str]: """ Handle the version command received from client """ msg = await self.callbacks.call(Callback.VERSION, None, ()) if not msg: name = self.config.get_name() version = self.config.get_version() msg = f"version: {name} v{version}" return ("msg", Message.info(msg))
def _chat_users(self, chat: str) -> None: """ Get list of users in chat on account """ user_list = self.client.list_room_users(chat) for user in user_list: user_id, user_name, user_status = user self.account.receive_msg( Message.chat_user(self.account, chat, user_id, user_name, user_status))
async def _handle_account_delete(self, acc_id: int) -> str: """ Delete an existing account Expected format: account <ID> delete """ # delete account result = await self.account_list.delete(acc_id) # inform caller about result return Message.info(result)
def _membership_event(self, *params): """ Handle membership event """ # parse params event_type, tstamp, sender_id, sender_name, room_id, room_name,\ invited_user = params # check membership type if event_type == "invite": user_msg = Message.chat_user(self.account, room_id, invited_user, invited_user, event_type) msg = (f"*** {sender_name} invited {invited_user} " f"to {room_name}. ***") if event_type == "join": user_msg = Message.chat_user(self.account, room_id, sender_id, invited_user, event_type) msg = f"*** {invited_user} joined {room_name}. ***" if event_type == "leave": user_msg = Message.chat_user(self.account, room_id, sender_id, sender_name, event_type) msg = f"*** {sender_name} left {room_name}. ***" # generic event, return as message # TODO: change parsing in nuqql and use char + / + sender here? formatted_msg = Message.CHAT_MSG.format(self.account.aid, room_id, tstamp, sender_id, msg) # add event to event list if self.settings.membership_user_msg: self.account.receive_msg(user_msg) if self.settings.membership_message_msg: self.account.receive_msg(formatted_msg)
async def send_message(acc: Optional["Account"], _cmd: Callback, params: Tuple) -> str: """ Send a message to another user. For testing, this simply modifies the message and returns it to the sender. """ assert acc dest, msg = params # add destination as buddy in the testing buddy list _add_buddy(dest) acc.receive_msg( Message.message(acc, str(int(time.time())), dest, acc.user, msg.upper())) return ""
def _message(self, tstamp, sender, room_id, msg) -> None: """ Message handler """ # if filter_own is set, skip own messages if self.account.config.get_filter_own() and sender == self.user: return # rewrite sender of own messages if sender == self.user: sender = "<self>" # save timestamp and message in messages list and history formatted_msg = Message.chat_msg(self.account, tstamp, sender, room_id, msg) self.account.receive_msg(formatted_msg)
async def _handle_account_buddies(self, acc_id: int, params: List[str]) -> str: """ Get buddies for a specific account. If params contains "online", filter online buddies. Expected format: account <ID> buddies [online] params does not include "account <ID> buddies" Returned messages should look like: buddy: <acc_id> status: <Offline/Available> name: <name> alias: <alias> """ # get account accounts = self.account_list.get() acc = accounts[acc_id] # filter online buddies? online = False if len(params) >= 1 and params[0].lower() == "online": online = True # update buddy list result = await self.callbacks.call(Callback.GET_BUDDIES, acc, (online, )) # add info message that all buddies have been received info = Message.info(f"got buddies for account {acc_id}.") # log event log_msg = f"account {acc_id} buddies: {result}" logging.info(log_msg) # return replies as single string return result + info
async def _get_buddies(acc: Optional["Account"], _cmd: Callback, params: Tuple) -> str: """ Get buddy list. For testing, this simply returns a single buddy list for all accounts. """ # filter online users? online = False if params: online = params[0] # no test buddies are online, return empty list if only online wanted if online: return "" # construct buddy messages result = "" assert acc for buddy in TEST_BUDDIES: result += Message.buddy(acc, buddy, "", "") return result
async def _handle_account_status(self, acc_id: int, params: List[str]) -> str: """ Get or set current status of account Expected format: account <ID> status get account <ID> status set <STATUS> params does not include "account <ID> status" Returned messages for "status get" should look like: status: account <ID> status: <STATUS> """ if not params: return "" # get account accounts = self.account_list.get() acc = accounts[acc_id] # get current status if params[0] == "get": status = await self.callbacks.call(Callback.GET_STATUS, acc, ()) if status: return Message.status(acc, status) # set current status if params[0] == "set": if len(params) < 2: return "" status = params[1] return await self.callbacks.call(Callback.SET_STATUS, acc, (status, )) return ""