class Server(metaclass=ABCMeta): """ Generic server object. An interface for ServerIRC or ServerSkype or whatever objects. """ # Constants TYPE_IRC = "irc" TYPE_MOCK = "mock" TYPE_TELEGRAM = "telegram" STATE_CLOSED = "disconnected" STATE_OPEN = "connected" STATE_CONNECTING = "connecting" STATE_DISCONNECTING = "disconnecting" type = None def __init__(self, hallo): """ Constructor for server object :param hallo: Hallo Instance of hallo that contains this server object :type hallo: hallo.Hallo """ self.hallo = hallo # The hallo object that created this server # Persistent/saved class variables self.name = None # Server name self.auto_connect = ( True # Whether to automatically connect to this server when hallo starts ) self.channel_list = ( [] ) # List of channels on this server (which may or may not be currently active) """ :type : list[Destination.Channel]""" self.user_list = [ ] # Users on this server (not all of which are online) """ :type : list[Destination.User]""" self.nick = None # Nickname to use on this server self.prefix = None # Prefix to use with functions on this server self.full_name = None # Full name to use on this server self.permission_mask = PermissionMask( ) # PermissionMask for the server """ :type : PermissionMask""" # Dynamic/unsaved class variables self.state = Server.STATE_CLOSED # Current state of the server, replacing open def __eq__(self, other): return (isinstance(other, Server) and self.hallo == other.hallo and self.type == other.type and self.name.lower() == other.name.lower()) def __hash__(self): return hash((self.hallo, self.type, self.name.lower())) def start(self): """ Starts the new server, launching new thread as appropriate. """ raise NotImplementedError def disconnect(self, force=False): """ Disconnects from the server, shutting down remaining threads """ raise NotImplementedError def reconnect(self): """ Disconnects and reconnects from the server """ self.disconnect() self.start() def send(self, event): """ Sends a message to the server, or a specific channel in the server :param event: Event to send, should be outbound. :type event: events.ServerEvent :rtype : events.ServerEvent | None """ raise NotImplementedError def reply(self, old_event, new_event): """ Sends a message as a reply to another message, such as a response to a function call :param old_event: The event which was received, to reply to :type old_event: events.ChannelUserTextEvent :param new_event: The event to be sent :type new_event: events.ChannelUserTextEvent """ # This method will just do some checks, implementations will have to actually send events if not old_event.is_inbound or new_event.is_inbound: raise ServerException( "Cannot reply to outbound event, or send inbound one") if old_event.channel != new_event.channel: raise ServerException( "Cannot send reply to a different channel than original message came from" ) if new_event.user is not None and old_event.user != new_event.user: raise ServerException( "Cannot send reply to a different private chat than original message came from" ) if old_event.server != new_event.server: raise ServerException( "Cannot send reply to a different server than the original message came from" ) return def to_json(self): """ Returns a dict formatted so it may be serialised into json configuration data :return: dict """ raise NotImplementedError def get_nick(self): """Nick getter""" if self.nick is None: return self.hallo.default_nick return self.nick def set_nick(self, nick): """ Nick setter :param nick: New nick for hallo to use on this server :type nick: str """ self.nick = nick def get_prefix(self): """Prefix getter""" if self.prefix is None: return self.hallo.default_prefix return self.prefix def set_prefix(self, prefix): """ Prefix setter :param prefix: Prefix for hallo to use for function calls on this server :type prefix: str | bool | None """ self.prefix = prefix def get_full_name(self): """Full name getter""" if self.full_name is None: return self.hallo.default_full_name return self.full_name def set_full_name(self, full_name): """ Full name setter :param full_name: Full name for Hallo to use on this server :type full_name: str """ self.full_name = full_name def get_auto_connect(self): """AutoConnect getter""" return self.auto_connect def set_auto_connect(self, auto_connect): """ AutoConnect setter :param auto_connect: Whether or not to autoconnect to the server :type auto_connect: bool """ self.auto_connect = auto_connect def is_connected(self): """Returns boolean representing whether the server is connected or not.""" return self.state == Server.STATE_OPEN def get_channel_by_name(self, channel_name): """ Returns a Channel object with the specified channel name. :param channel_name: Name of the channel which is being searched for :type channel_name: str :rtype: Optional[Destination.Channel] """ channel_name = channel_name.lower() for channel in self.channel_list: if channel.name == channel_name: return channel return None def get_channel_by_address(self, address, channel_name=None): """ Returns a Channel object with the specified channel name. :param address: Address of the channel :type address: str :param channel_name: Name of the channel which is being searched for :type channel_name: str :rtype: destination.Channel """ for channel in self.channel_list: if channel.address == address: return channel if channel_name is None: channel_name = self.get_name_by_address(address) new_channel = Channel(self, address, channel_name) self.add_channel(new_channel) return new_channel def get_name_by_address(self, address): """ Returns the name of a destination, based on the address :param address: str :return: str """ raise NotImplementedError() def add_channel(self, channel_obj): """ Adds a channel to the channel list :param channel_obj: Adds a channel to the list, without joining it :type channel_obj: destination.Channel """ self.channel_list.append(channel_obj) def join_channel(self, channel_obj): """ Joins a specified channel :param channel_obj: Channel to join :type channel_obj: destination.Channel """ raise NotImplementedError def leave_channel(self, channel_obj): """ Leaves a specified channel :param channel_obj: Channel for hallo to leave :type channel_obj: destination.Channel """ # If channel isn't in channel list, do nothing if channel_obj not in self.channel_list: return # Set channel to not AutoJoin, for the future channel_obj.auto_join = False # Set not in channel channel_obj.set_in_channel(False) def get_user_by_name(self, user_name): """ Returns a User object with the specified user name. :param user_name: Name of user which is being searched for :type user_name: str :rtype: destination.User | None """ user_name = user_name.lower() for user in self.user_list: if user.name == user_name: return user # No user by that name exists, return None return None def get_user_by_address(self, address, user_name=None): """ Returns a User object with the specified user name. :param address: address of the user which is being searched for or added :type address: str :param user_name: Name of user which is being searched for :type user_name: str :return: Destination.User | None """ for user in self.user_list: if user.address == address: return user if user_name is None: user_name = self.get_name_by_address(address) # No user by that name exists, so create one new_user = User(self, address, user_name) self.add_user(new_user) return new_user def get_user_list(self): """Returns the full list of users on this server.""" return self.user_list def add_user(self, user_obj): """ Adds a user to the user list :param user_obj: User to add to user list :type user_obj: destination.User """ self.user_list.append(user_obj) def rights_check(self, right_name: str) -> bool: """ Checks the value of the right with the specified name. Returns boolean :param right_name: Name of the right to check default server value for :type right_name: str """ if self.permission_mask is not None: right_value = self.permission_mask.get_right(right_name) # If PermissionMask contains that right, return it. if right_value in [True, False]: return right_value # Fallback to the parent Hallo's decision. return self.hallo.rights_check(right_name) def check_user_identity(self, user_obj): """ Check if a user is identified and verified :param user_obj: User to check identity of :type user_obj: destination.User """ raise NotImplementedError
class UserGroup: """ UserGroup object, mostly exists for a speedy way to apply a PermissionsMask to a large amount of users at once """ def __init__(self, name, hallo): """ Constructor :param name: Name of the user group :type name: str :param hallo: Hallo object which owns the user group :type hallo: hallo.Hallo """ self.user_list = set() # Dynamic userlist of this group """:type : set[Destination.User]""" self.hallo = hallo # Hallo instance that owns this UserGroup """:type : Hallo.Hallo""" self.name = name # Name of the UserGroup """:type : str""" self.permission_mask = PermissionMask() # PermissionMask for the UserGroup """:type : PermissionMask""" def __eq__(self, other): return (self.hallo, self.name) == (self.hallo, other.name) def __hash__(self): return (self.hallo, self.name).__hash__() def rights_check(self, right_name, user_obj, channel_obj=None): """Checks the value of the right with the specified name. Returns boolean :param right_name: Name of the right to check :type right_name: str :param user_obj: User which is having rights checked :type user_obj: destination.User :param channel_obj: Channel in which rights are being checked, None for private messages :type channel_obj: destination.Channel | None :rtype: bool """ right_value = self.permission_mask.get_right(right_name) # PermissionMask contains that right, return it. if right_value in [True, False]: return right_value # Fall back to channel, if defined if channel_obj is not None: return channel_obj.rights_check(right_name) # Fall back to the parent Server's decision. return user_obj.server.rights_check(right_name) def get_name(self): return self.name def get_permission_mask(self): return self.permission_mask def set_permission_mask(self, new_permission_mask): """ Sets the permission mask of the user group :param new_permission_mask: Permission mask to set for user group :type new_permission_mask: PermissionMask.PermissionMask """ self.permission_mask = new_permission_mask def get_hallo(self): return self.hallo def add_user(self, new_user): """ Adds a new user to this group :param new_user: User to add to group :type new_user: destination.User """ self.user_list.add(new_user) def remove_user(self, remove_user): self.user_list.remove(remove_user) def to_json(self): """ Returns the user group configuration as a dict for serialisation into json :return: dict """ json_obj = dict() json_obj["name"] = self.name if not self.permission_mask.is_empty(): json_obj["permission_mask"] = self.permission_mask.to_json() return json_obj @staticmethod def from_json(json_obj, hallo): """ Creates a UserGroup object from json object dictionary :param json_obj: json object dictionary :type json_obj: dict :param hallo: root hallo object :type hallo: hallo.Hallo :return: new user group :rtype: UserGroup """ new_group = UserGroup(json_obj["name"], hallo) if "permission_mask" in json_obj: new_group.permission_mask = PermissionMask.from_json( json_obj["permission_mask"] ) return new_group