diff --git a/hvicorn/bot/async_client.py b/hvicorn/bot/async_client.py index c25fea7..668fe30 100644 --- a/hvicorn/bot/async_client.py +++ b/hvicorn/bot/async_client.py @@ -17,6 +17,9 @@ class AsyncCommandContext: + """ + Represents the context in which a command is executed. + """ def __init__( self, bot: "AsyncBot", @@ -26,6 +29,17 @@ def __init__( args: str, event: Union[WhisperPackage, ChatPackage], ) -> None: + """ + Initialize a CommandContext instance. + + Args: + bot (Bot): The bot instance. + sender (User): The user who triggered the command. + triggered_via (Literal["chat", "whisper"]): The method by which the command was triggered. + text (str): The full text of the command. + args (str): The arguments passed to the command. + event (Union[WhisperPackage, ChatPackage]): The event that triggered the command. + """ self.bot: "AsyncBot" = bot self.sender: User = sender self.triggered_via: Literal["chat", "whisper"] = triggered_via @@ -34,6 +48,13 @@ def __init__( self.event: Union[WhisperPackage, ChatPackage] = event async def respond(self, text, at_sender=True): + """ + Respond to the command. + + Args: + text (str): The text to respond with. + at_sender (bool, optional): Whether to mention the sender in the response. Defaults to True. + """ if self.triggered_via == "chat": await self.bot.send_message( ("@" + self.sender.nick + " " if at_sender else "") + str(text) @@ -45,7 +66,18 @@ async def respond(self, text, at_sender=True): class AsyncBot: + """ + Represents a hack.chat bot. + """ def __init__(self, nick: str, channel: str, password: Optional[str] = None) -> None: + """ + Initialize a Bot instance. + + Args: + nick (str): The bot's nickname. + channel (str): The channel to join. + password (Optional[str], optional): The channel password. Defaults to None. + """ self.nick = nick self.channel = channel self.password = password @@ -61,6 +93,12 @@ def __init__(self, nick: str, channel: str, password: Optional[str] = None) -> N self.optional_features: OptionalFeatures = OptionalFeatures() async def _send_model(self, model: BaseModel) -> None: + """ + Send a model to the websocket. + + Args: + model (BaseModel): The model to send. + """ if type(model) == CustomRequest: payload = model.rawjson else: @@ -94,6 +132,16 @@ def get_users_by( ], matches: Union[str, Callable], ) -> List[User]: + """ + Get users by a specific attribute or custom function. + + Args: + by (Literal): The attribute to match by. + matches (Union[str, Callable]): The value to match or a custom function. + + Returns: + List[User]: A list of matching users. + """ results = [] for user in self.users: if by != "function": @@ -122,13 +170,38 @@ def get_user_by( ], matches: Union[str, Callable], ) -> Optional[User]: + """ + Get a single user by a specific attribute or custom function. + + Args: + by (Literal): The attribute to match by. + matches (Union[str, Callable]): The value to match or a custom function. + + Returns: + Optional[User]: The matching user, if found. + """ result = self.get_users_by(by, matches) return result[0] if result else None def get_user_by_nick(self, nick: str) -> Optional[User]: + """ + Get a user by their nickname. + + Args: + nick (str): The nickname to search for. + + Returns: + Optional[User]: The matching user, if found. + """ return self.get_user_by("nick", nick) async def _internal_handler(self, event: BaseModel) -> None: + """ + Internal event handler for processing various types of events. + + Args: + event (BaseModel): The event to process. + """ if isinstance(event, OnlineSetPackage): self.users = event.users elif isinstance(event, OnlineAddPackage): @@ -212,6 +285,9 @@ async def _internal_handler(self, event: BaseModel) -> None: target_user.__setattr__(k, v) async def _connect(self) -> None: + """ + Connect to the websocket server. + """ debug(f"Connecting to {WS_ADDRESS}, Websocket options: {self.wsopt}") if ( WS_ADDRESS == "wss://hack.chat/chat-ws" @@ -239,6 +315,13 @@ async def _connect(self) -> None: async def _run_events( self, event_type: Any, args: list, taskgroup: asyncio.TaskGroup ): + """ + Run event handlers for a specific event type. + + Args: + event_type (Any): The type of event to run handlers for. + args (list): Arguments to pass to the event handlers. + """ for function in self.event_functions.get(event_type, []): try: if asyncio.iscoroutinefunction(function): @@ -249,6 +332,9 @@ async def _run_events( warn(f"Ignoring exception in event: \n{format_exc()}") async def join(self) -> None: + """ + Join the specified channel. + """ debug(f"Sending join package") await self._send_model( JoinRequest(nick=self.nick, channel=self.channel, password=self.password) @@ -257,6 +343,16 @@ async def join(self) -> None: debug(f"Done!") async def send_message(self, text, editable=False) -> AsyncMessage: + """ + Send a message to the channel. + + Args: + text (str): The message text. + editable (bool, optional): Whether the message should be editable. Defaults to False. + + Returns: + Message: The sent message object. + """ customId = generate_customid() if editable else None await self._send_model(ChatRequest(text=text, customId=customId)) @@ -269,29 +365,76 @@ async def wrapper(*args, **kwargs): return msg async def whisper(self, nick: str, text: str) -> None: + """ + Send a whisper (private message) to a user. + + Args: + nick (str): The nickname of the recipient. + text (str): The message text. + """ await self._send_model(WhisperRequest(nick=nick, text=text)) async def emote(self, text: str) -> None: + """ + Send an emote message to the channel. + + Args: + text (str): The emote text. + """ await self._send_model(EmoteRequest(text=text)) async def change_color(self, color: str = "reset") -> None: + """ + Change the bot's color. + + Args: + color (str, optional): The new color. Defaults to "reset". + """ await self._send_model(ChangeColorRequest(color=color)) async def change_nick(self, nick: str) -> None: + """ + Change the bot's nickname. + + Args: + nick (str): The new nickname. + + Raises: + ValueError: If the nickname is invalid. + """ if not verifyNick(nick): raise ValueError("Invalid Nickname") await self._send_model(ChangeNickRequest(nick=nick)) self.nick = nick async def invite(self, nick: str, channel: Optional[str] = None) -> None: + """ + Invite a user to a channel. + + Args: + nick (str): The nickname of the user to invite. + channel (Optional[str], optional): The channel to invite to. Defaults to None. + """ await self._send_model(InviteRequest(nick=nick, to=channel)) async def ping(self) -> None: + """ + Send a ping request to the server. + """ await self._send_model(PingRequest()) def on( self, event_type: Optional[Any] = None ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + """ + Decorator for registering event handlers. + + Args: + event_type (Optional[Any], optional): The type of event to handle. Defaults to None. + + Returns: + Callable[[Callable[..., Any]], Callable[..., Any]]: A decorator function. + """ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]: nonlocal event_type if event_type is None: @@ -307,6 +450,12 @@ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]: return wrapper def startup(self, function: Callable) -> None: + """ + Register a startup function. + + Args: + function (Callable): The function to run at startup. + """ self.startup_functions.append(function) debug(f"Added startup function: {function}") return None @@ -316,6 +465,15 @@ def command( ) -> Callable[ [Callable[[AsyncCommandContext], Any]], Callable[[AsyncCommandContext], Any] ]: + """ + Decorator for registering command handlers. + + Args: + prefix (str): The command prefix. + + Returns: + Callable[[Callable[[CommandContext], Any]], Callable[[CommandContext], Any]]: A decorator function. + """ def wrapper(func: Callable[[AsyncCommandContext], Any]): if prefix in self.commands.keys(): warn( @@ -327,6 +485,13 @@ def wrapper(func: Callable[[AsyncCommandContext], Any]): return wrapper def register_event_function(self, event_type: Any, function: Callable): + """ + Register an event handler function. + + Args: + event_type (Any): The type of event to handle. + function (Callable): The function to handle the event. + """ if event_type in self.event_functions.keys(): self.event_functions[event_type].append(function) debug(f"Added handler for {event_type}: {function}") @@ -335,13 +500,32 @@ def register_event_function(self, event_type: Any, function: Callable): debug(f"Set handler for {event_type} to {function}") def register_global_function(self, function: Callable): + """ + Register a global event handler function. + + Args: + function (Callable): The function to handle all events. + """ self.register_event_function("__GLOBAL__", function) def register_startup_function(self, function: Callable): + """ + Register a startup function. + + Args: + function (Callable): The function to run at startup. + """ self.startup_functions.append(function) debug(f"Added startup function: {function}") def register_command(self, prefix: str, function: Callable): + """ + Register a command handler function. + + Args: + prefix (str): The command prefix. + function (Callable): The function to handle the command. + """ if prefix in self.commands.keys(): warn( f"Overriding function {self.commands[prefix]} for command prefix {prefix}" @@ -349,6 +533,12 @@ def register_command(self, prefix: str, function: Callable): self.commands[prefix] = function def kill(self) -> None: + """ + Kill the bot and close the websocket connection. + + Raises: + ConnectionError: If the websocket is already closed or not open. + """ self.killed = True debug("Killing ws") if not self.websocket: @@ -356,6 +546,12 @@ def kill(self) -> None: asyncio.create_task(self.websocket.close()) async def close_ws(self) -> None: + """ + Close the websocket connection. + + Raises: + ConnectionError: If the websocket is already closed or not open. + """ debug("Closing ws") if not self.websocket: raise ConnectionError("Websocket is already closed / not open") @@ -368,6 +564,15 @@ async def load_plugin( *args, **kwargs, ) -> None: + """ + Load a plugin. + + Args: + plugin_name (str): The name of the plugin to load. + init_function (Optional[Callable], optional): Custom initialization function. Defaults to None. + *args: Additional positional arguments to pass to the init function. + **kwargs: Additional keyword arguments to pass to the init function. + """ if not init_function: try: plugin = __import__(plugin_name) @@ -400,6 +605,16 @@ async def load_plugin( debug(f"Loaded plugin {plugin_name}") async def run(self, ignore_self: bool = True, wsopt: Dict = {}) -> None: + """ + Run the bot. + + Args: + ignore_self (bool, optional): Whether to ignore messages from the bot itself. Defaults to True. + wsopt (Dict, optional): Additional websocket options. Defaults to {}. + + Raises: + RuntimeError: If there's a websocket connection error. + """ self.wsopt = wsopt if wsopt != {} else self.wsopt await self._connect() await self.join() diff --git a/hvicorn/bot/optional_features.py b/hvicorn/bot/optional_features.py index 18ad548..4796d74 100644 --- a/hvicorn/bot/optional_features.py +++ b/hvicorn/bot/optional_features.py @@ -2,4 +2,13 @@ class OptionalFeatures(BaseModel): + """ + Optional features configuration. + """ + bypass_gfw_dns_poisoning: bool = False + """ + Flag to enable bypassing of GFW DNS poisoning. + + Defaults to False. + """ diff --git a/hvicorn/bot/sync_client.py b/hvicorn/bot/sync_client.py index f5abfe2..a508572 100644 --- a/hvicorn/bot/sync_client.py +++ b/hvicorn/bot/sync_client.py @@ -18,7 +18,15 @@ def threaded(func): + """ + Decorator to run a function in a separate thread. + Args: + func (Callable): The function to be run in a thread. + + Returns: + Callable: A wrapper function that starts a new thread. + """ def wrapper(*args, **kwargs): Thread(target=func, args=tuple(args), kwargs=kwargs).start() @@ -26,6 +34,9 @@ def wrapper(*args, **kwargs): class CommandContext: + """ + Represents the context in which a command is executed. + """ def __init__( self, bot: "Bot", @@ -35,6 +46,17 @@ def __init__( args: str, event: Union[WhisperPackage, ChatPackage], ) -> None: + """ + Initialize a CommandContext instance. + + Args: + bot (Bot): The bot instance. + sender (User): The user who triggered the command. + triggered_via (Literal["chat", "whisper"]): The method by which the command was triggered. + text (str): The full text of the command. + args (str): The arguments passed to the command. + event (Union[WhisperPackage, ChatPackage]): The event that triggered the command. + """ self.bot: "Bot" = bot self.sender: User = sender self.triggered_via: Literal["chat", "whisper"] = triggered_via @@ -43,6 +65,13 @@ def __init__( self.event: Union[WhisperPackage, ChatPackage] = event def respond(self, text, at_sender=True): + """ + Respond to the command. + + Args: + text (str): The text to respond with. + at_sender (bool, optional): Whether to mention the sender in the response. Defaults to True. + """ if self.triggered_via == "chat": self.bot.send_message( ("@" + self.sender.nick + " " if at_sender else "") + str(text) @@ -54,7 +83,18 @@ def respond(self, text, at_sender=True): class Bot: + """ + Represents a hack.chat bot. + """ def __init__(self, nick: str, channel: str, password: Optional[str] = None) -> None: + """ + Initialize a Bot instance. + + Args: + nick (str): The bot's nickname. + channel (str): The channel to join. + password (Optional[str], optional): The channel password. Defaults to None. + """ self.nick = nick self.channel = channel self.password = password @@ -70,6 +110,12 @@ def __init__(self, nick: str, channel: str, password: Optional[str] = None) -> N self.optional_features: OptionalFeatures = OptionalFeatures() def _send_model(self, model: BaseModel) -> None: + """ + Send a model to the websocket. + + Args: + model (BaseModel): The model to send. + """ if type(model) == CustomRequest: payload = model.rawjson else: @@ -103,6 +149,16 @@ def get_users_by( ], matches: Union[str, Callable], ) -> List[User]: + """ + Get users by a specific attribute or custom function. + + Args: + by (Literal): The attribute to match by. + matches (Union[str, Callable]): The value to match or a custom function. + + Returns: + List[User]: A list of matching users. + """ results = [] for user in self.users: if by != "function": @@ -131,13 +187,38 @@ def get_user_by( ], matches: Union[str, Callable], ) -> Optional[User]: + """ + Get a single user by a specific attribute or custom function. + + Args: + by (Literal): The attribute to match by. + matches (Union[str, Callable]): The value to match or a custom function. + + Returns: + Optional[User]: The matching user, if found. + """ result = self.get_users_by(by, matches) return result[0] if result else None def get_user_by_nick(self, nick: str) -> Optional[User]: + """ + Get a user by their nickname. + + Args: + nick (str): The nickname to search for. + + Returns: + Optional[User]: The matching user, if found. + """ return self.get_user_by("nick", nick) def _internal_handler(self, event: BaseModel) -> None: + """ + Internal event handler for processing various types of events. + + Args: + event (BaseModel): The event to process. + """ if isinstance(event, OnlineSetPackage): self.users = event.users elif isinstance(event, OnlineAddPackage): @@ -221,6 +302,9 @@ def _internal_handler(self, event: BaseModel) -> None: target_user.__setattr__(k, v) def _connect(self) -> None: + """ + Connect to the websocket server. + """ debug(f"Connecting to {WS_ADDRESS}, Websocket options: {self.wsopt}") if ( WS_ADDRESS == "wss://hack.chat/chat-ws" @@ -245,6 +329,13 @@ def _connect(self) -> None: sleep(1) def _run_events(self, event_type: Any, args: list): + """ + Run event handlers for a specific event type. + + Args: + event_type (Any): The type of event to run handlers for. + args (list): Arguments to pass to the event handlers. + """ for function in self.event_functions.get(event_type, []): try: function(*args) @@ -252,6 +343,9 @@ def _run_events(self, event_type: Any, args: list): warn(f"Ignoring exception in event: \n{format_exc()}") def join(self) -> None: + """ + Join the specified channel. + """ debug(f"Sending join package") self._send_model( JoinRequest(nick=self.nick, channel=self.channel, password=self.password) @@ -260,6 +354,16 @@ def join(self) -> None: debug(f"Done!") def send_message(self, text, editable=False) -> Message: + """ + Send a message to the channel. + + Args: + text (str): The message text. + editable (bool, optional): Whether the message should be editable. Defaults to False. + + Returns: + Message: The sent message object. + """ customId = generate_customid() if editable else None self._send_model(ChatRequest(text=text, customId=customId)) @@ -272,29 +376,76 @@ def wrapper(*args, **kwargs): return msg def whisper(self, nick: str, text: str) -> None: + """ + Send a whisper (private message) to a user. + + Args: + nick (str): The nickname of the recipient. + text (str): The message text. + """ self._send_model(WhisperRequest(nick=nick, text=text)) def emote(self, text: str) -> None: + """ + Send an emote message to the channel. + + Args: + text (str): The emote text. + """ self._send_model(EmoteRequest(text=text)) def change_color(self, color: str = "reset") -> None: + """ + Change the bot's color. + + Args: + color (str, optional): The new color. Defaults to "reset". + """ self._send_model(ChangeColorRequest(color=color)) def change_nick(self, nick: str) -> None: + """ + Change the bot's nickname. + + Args: + nick (str): The new nickname. + + Raises: + ValueError: If the nickname is invalid. + """ if not verifyNick(nick): raise ValueError("Invaild Nickname") self._send_model(ChangeNickRequest(nick=nick)) self.nick = nick def invite(self, nick: str, channel: Optional[str] = None) -> None: + """ + Invite a user to a channel. + + Args: + nick (str): The nickname of the user to invite. + channel (Optional[str], optional): The channel to invite to. Defaults to None. + """ self._send_model(InviteRequest(nick=nick, to=channel)) def ping(self) -> None: + """ + Send a ping request to the server. + """ self._send_model(PingRequest()) def on( self, event_type: Optional[Any] = None ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + """ + Decorator for registering event handlers. + + Args: + event_type (Optional[Any], optional): The type of event to handle. Defaults to None. + + Returns: + Callable[[Callable[..., Any]], Callable[..., Any]]: A decorator function. + """ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]: nonlocal event_type if event_type is None: @@ -310,6 +461,12 @@ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]: return wrapper def startup(self, function: Callable) -> None: + """ + Register a startup function. + + Args: + function (Callable): The function to run at startup. + """ self.startup_functions.append(function) debug(f"Added startup function: {function}") return None @@ -317,6 +474,15 @@ def startup(self, function: Callable) -> None: def command( self, prefix: str ) -> Callable[[Callable[[CommandContext], Any]], Callable[[CommandContext], Any]]: + """ + Decorator for registering command handlers. + + Args: + prefix (str): The command prefix. + + Returns: + Callable[[Callable[[CommandContext], Any]], Callable[[CommandContext], Any]]: A decorator function. + """ def wrapper(func: Callable[[CommandContext], Any]): if prefix in self.commands.keys(): warn( @@ -328,6 +494,13 @@ def wrapper(func: Callable[[CommandContext], Any]): return wrapper def register_event_function(self, event_type: Any, function: Callable): + """ + Register an event handler function. + + Args: + event_type (Any): The type of event to handle. + function (Callable): The function to handle the event. + """ if event_type in self.event_functions.keys(): self.event_functions[event_type].append(function) debug(f"Added handler for {event_type}: {function}") @@ -336,13 +509,32 @@ def register_event_function(self, event_type: Any, function: Callable): debug(f"Set handler for {event_type} to {function}") def register_global_function(self, function: Callable): + """ + Register a global event handler function. + + Args: + function (Callable): The function to handle all events. + """ self.register_event_function("__GLOBAL__", function) def register_startup_function(self, function: Callable): + """ + Register a startup function. + + Args: + function (Callable): The function to run at startup. + """ self.startup_functions.append(function) debug(f"Added startup function: {function}") def register_command(self, prefix: str, function: Callable): + """ + Register a command handler function. + + Args: + prefix (str): The command prefix. + function (Callable): The function to handle the command. + """ if prefix in self.commands.keys(): warn( f"Overriding function {self.commands[prefix]} for command prefix {prefix}" @@ -350,6 +542,12 @@ def register_command(self, prefix: str, function: Callable): self.commands[prefix] = function def kill(self) -> None: + """ + Kill the bot and close the websocket connection. + + Raises: + ConnectionError: If the websocket is already closed or not open. + """ self.killed = True debug("Killing ws") if not self.websocket: @@ -357,6 +555,12 @@ def kill(self) -> None: self.websocket.close() def close_ws(self) -> None: + """ + Close the websocket connection. + + Raises: + ConnectionError: If the websocket is already closed or not open. + """ debug("Closing ws") if not self.websocket: raise ConnectionError("Websocket is already closed / not open") @@ -369,6 +573,15 @@ def load_plugin( *args, **kwargs, ) -> None: + """ + Load a plugin. + + Args: + plugin_name (str): The name of the plugin to load. + init_function (Optional[Callable], optional): Custom initialization function. Defaults to None. + *args: Additional positional arguments to pass to the init function. + **kwargs: Additional keyword arguments to pass to the init function. + """ if not init_function: try: plugin = __import__(plugin_name) @@ -395,6 +608,16 @@ def load_plugin( debug(f"Loaded plugin {plugin_name}") def run(self, ignore_self: bool = True, wsopt: Dict = {}) -> None: + """ + Run the bot. + + Args: + ignore_self (bool, optional): Whether to ignore messages from the bot itself. Defaults to True. + wsopt (Dict, optional): Additional websocket options. Defaults to {}. + + Raises: + RuntimeError: If there's a websocket connection error. + """ self.wsopt = wsopt if wsopt != {} else self.wsopt self._connect() self.join() diff --git a/hvicorn/models/client/chat.py b/hvicorn/models/client/chat.py index 8737b21..be8a9fc 100644 --- a/hvicorn/models/client/chat.py +++ b/hvicorn/models/client/chat.py @@ -5,7 +5,23 @@ class Message: + """ + A class representing a message that can be edited. + + Attributes: + text (str): The content of the message. + customId (Optional[str]): A unique identifier for the message. If provided, the message is editable. + editable (bool): Indicates whether the message can be edited. + """ + def __init__(self, text: str, customId: Optional[str] = None) -> None: + """ + Initialize a Message instance. + + Args: + text (str): The content of the message. + customId (Optional[str], optional): A unique identifier for the message. Defaults to None. + """ self.text = text self.customId = customId self.editable = customId != None @@ -13,6 +29,19 @@ def __init__(self, text: str, customId: Optional[str] = None) -> None: def _generate_edit_request( self, mode: Literal["overwrite", "prepend", "append", "complete"], text: str ): + """ + Generate an edit request for the message. + + Args: + mode (Literal["overwrite", "prepend", "append", "complete"]): The edit mode. + text (str): The text to be used in the edit. + + Returns: + UpdateMessageRequest: An object representing the edit request. + + Raises: + SyntaxError: If the message isn't editable or if customId is missing. + """ if not self.editable: raise SyntaxError("This message isn't editable.") if self.customId: @@ -25,34 +54,104 @@ def _edit( ) -> None: ... def edit(self, text): + """ + Edit the message by overwriting its content. + + Args: + text (str): The new content for the message. + + Returns: + The result of the _edit method call. + """ self.text = text return self._edit("overwrite", text) def prepend(self, text): + """ + Prepend text to the beginning of the message. + + Args: + text (str): The text to prepend. + + Returns: + The result of the _edit method call. + """ self.text = text + self.text return self._edit("prepend", text) def append(self, text): + """ + Append text to the end of the message. + + Args: + text (str): The text to append. + + Returns: + The result of the _edit method call. + """ self.text += text return self._edit("append", text) def complete(self): + """ + Mark the message as complete, making it non-editable. + + Returns: + UpdateMessageRequest: An object representing the completion request. + + Raises: + SyntaxError: If customId is missing. + """ if not self.customId: raise SyntaxError("Missing customId") self.editable = False return UpdateMessageRequest(customId=self.customId, mode="complete") def __add__(self, string: str): + """ + Implement the addition operator to append text to the message. + + Args: + string (str): The text to append. + + Returns: + Message: The updated Message instance. + """ self.append(string) return self def __radd__(self, string: str): + """ + Implement the right addition operator to prepend text to the message. + + Args: + string (str): The text to prepend. + + Returns: + Message: The updated Message instance. + """ self.prepend(string) return self class AsyncMessage: + """ + An asynchronous version of the Message class. + + Attributes: + text (str): The content of the message. + customId (Optional[str]): A unique identifier for the message. If provided, the message is editable. + editable (bool): Indicates whether the message can be edited. + """ + def __init__(self, text: str, customId: Optional[str] = None) -> None: + """ + Initialize an AsyncMessage instance. + + Args: + text (str): The content of the message. + customId (Optional[str], optional): A unique identifier for the message. Defaults to None. + """ self.text = text self.customId = customId self.editable = customId != None @@ -60,6 +159,19 @@ def __init__(self, text: str, customId: Optional[str] = None) -> None: def _generate_edit_request( self, mode: Literal["overwrite", "prepend", "append", "complete"], text: str ): + """ + Generate an edit request for the message. + + Args: + mode (Literal["overwrite", "prepend", "append", "complete"]): The edit mode. + text (str): The text to be used in the edit. + + Returns: + UpdateMessageRequest: An object representing the edit request. + + Raises: + SyntaxError: If the message isn't editable or if customId is missing. + """ if not self.editable: raise SyntaxError("This message isn't editable.") if self.customId: @@ -72,28 +184,82 @@ async def _edit( ) -> None: ... async def edit(self, text): + """ + Asynchronously edit the message by overwriting its content. + + Args: + text (str): The new content for the message. + + Returns: + The result of the _edit method call. + """ self.text = text return await self._edit("overwrite", text) async def prepend(self, text): + """ + Asynchronously prepend text to the beginning of the message. + + Args: + text (str): The text to prepend. + + Returns: + The result of the _edit method call. + """ self.text = text + self.text return await self._edit("prepend", text) async def append(self, text): + """ + Asynchronously append text to the end of the message. + + Args: + text (str): The text to append. + + Returns: + The result of the _edit method call. + """ self.text += text return await self._edit("append", text) async def complete(self): + """ + Asynchronously mark the message as complete, making it non-editable. + + Returns: + UpdateMessageRequest: An object representing the completion request. + + Raises: + SyntaxError: If customId is missing. + """ self.editable = False if not self.customId: raise SyntaxError("Missing customId") return UpdateMessageRequest(customId=self.customId, mode="complete") def __add__(self, string: str): + """ + Implement the addition operator to append text to the message. + + Args: + string (str): The text to append. + + Returns: + AsyncMessage: The updated AsyncMessage instance. + """ run(self.append(string)) return self def __radd__(self, string: str): + """ + Implement the right addition operator to prepend text to the message. + + Args: + string (str): The text to prepend. + + Returns: + AsyncMessage: The updated AsyncMessage instance. + """ run(self.prepend(string)) return self