From 50143748473a590c82ecb23ebc602307fed5ad8f Mon Sep 17 00:00:00 2001 From: DirectiveAthena Date: Wed, 15 Jun 2022 13:54:36 +0200 Subject: [PATCH 1/8] Feat: Message emote-only attribute --- .../functions/twitch_message_constructors.py | 33 ++++++++++--------- src/AthenaTwitchBot/models/twitch_message.py | 1 + 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/AthenaTwitchBot/functions/twitch_message_constructors.py b/src/AthenaTwitchBot/functions/twitch_message_constructors.py index 3943865..e65bfe2 100644 --- a/src/AthenaTwitchBot/functions/twitch_message_constructors.py +++ b/src/AthenaTwitchBot/functions/twitch_message_constructors.py @@ -32,22 +32,23 @@ def _find_bot_only(content:list[str],message:str, bot_name:str) -> TwitchMessage return False TAG_MAPPING:dict[str:Callable] = { - "@badge-info": lambda tm, tag_value: setattr(tm, "badge_info", tag_value), - "badges": lambda tm, tag_value: setattr(tm, "badges", tag_value), - "client-nonce": lambda tm, tag_value: setattr(tm, "client_nonce", tag_value), - "color": lambda tm, tag_value: setattr(tm, "color", HEX(tag_value)), - "display-name": lambda tm, tag_value: setattr(tm, "display_name", tag_value), - "emotes": lambda tm, tag_value: setattr(tm, "emotes", tag_value), - "first-msg": lambda tm, tag_value: setattr(tm, "first_msg", bool(tag_value)), - "flags": lambda tm, tag_value: setattr(tm, "flags", tag_value), - "id": lambda tm, tag_value: setattr(tm, "message_id", tag_value), - "mod": lambda tm, tag_value: setattr(tm, "mod", bool(tag_value)), - "room-id": lambda tm, tag_value: setattr(tm, "room_id", tag_value), - "subscriber": lambda tm, tag_value: setattr(tm, "subscriber", bool(tag_value)), - "tmi-sent-ts": lambda tm, tag_value: setattr(tm, "tmi_sent_ts", int(tag_value)), - "turbo": lambda tm, tag_value: setattr(tm, "turbo", bool(tag_value)), - "user-id": lambda tm, tag_value: setattr(tm, "user_id", int(tag_value)), - "user-type": lambda tm, tag_value: setattr(tm, "user_type", tag_value), + "@badge-info": lambda tm, tag_value: setattr(tm, "badge_info", tag_value), + "badges": lambda tm, tag_value: setattr(tm, "badges", tag_value), + "client-nonce": lambda tm, tag_value: setattr(tm, "client_nonce", tag_value), + "color": lambda tm, tag_value: setattr(tm, "color", HEX(tag_value) if tag_value else HEX()), + "display-name": lambda tm, tag_value: setattr(tm, "display_name", tag_value), + "emotes": lambda tm, tag_value: setattr(tm, "emotes", tag_value), + "first-msg": lambda tm, tag_value: setattr(tm, "first_msg", bool(tag_value)), + "flags": lambda tm, tag_value: setattr(tm, "flags", tag_value), + "id": lambda tm, tag_value: setattr(tm, "message_id", tag_value), + "mod": lambda tm, tag_value: setattr(tm, "mod", bool(tag_value)), + "room-id": lambda tm, tag_value: setattr(tm, "room_id", tag_value), + "subscriber": lambda tm, tag_value: setattr(tm, "subscriber", bool(tag_value)), + "tmi-sent-ts": lambda tm, tag_value: setattr(tm, "tmi_sent_ts", int(tag_value)), + "turbo": lambda tm, tag_value: setattr(tm, "turbo", bool(tag_value)), + "user-id": lambda tm, tag_value: setattr(tm, "user_id", int(tag_value)), + "user-type": lambda tm, tag_value: setattr(tm, "user_type", tag_value), + "emote-only": lambda tm, tag_value: setattr(tm, "emote_only", bool(tag_value)), } # ---------------------------------------------------------------------------------------------------------------------- diff --git a/src/AthenaTwitchBot/models/twitch_message.py b/src/AthenaTwitchBot/models/twitch_message.py index 3fc9129..5e6accd 100644 --- a/src/AthenaTwitchBot/models/twitch_message.py +++ b/src/AthenaTwitchBot/models/twitch_message.py @@ -41,6 +41,7 @@ class TwitchMessage: emotes:str=EMPTY_STR flags:str=EMPTY_STR user_type:str=EMPTY_STR + emote_only:bool=False def __post_init__(self): self.username = self.user.split("!")[0][1:] From 5af8c879394ed663e67a6a69ecb200c6818cb66f Mon Sep 17 00:00:00 2001 From: DirectiveAthena Date: Wed, 15 Jun 2022 13:58:07 +0200 Subject: [PATCH 2/8] Rename to task_schedule_method --- src/AthenaTwitchBot/__init__.py | 2 +- .../decorators/{frequentoutput.py => task_schedule.py} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/AthenaTwitchBot/decorators/{frequentoutput.py => task_schedule.py} (94%) diff --git a/src/AthenaTwitchBot/__init__.py b/src/AthenaTwitchBot/__init__.py index 22d1246..6a2cb3e 100644 --- a/src/AthenaTwitchBot/__init__.py +++ b/src/AthenaTwitchBot/__init__.py @@ -2,7 +2,7 @@ # - Package Imports - # ---------------------------------------------------------------------------------------------------------------------- from AthenaTwitchBot.decorators.command import command_method -from AthenaTwitchBot.decorators.frequentoutput import frequent_output_method +from AthenaTwitchBot.decorators.task_schedule import task_schedule_method from AthenaTwitchBot.models.twitch_bot import TwitchBot from AthenaTwitchBot.models.twitch_bot_protocol import TwitchBotProtocol diff --git a/src/AthenaTwitchBot/decorators/frequentoutput.py b/src/AthenaTwitchBot/decorators/task_schedule.py similarity index 94% rename from src/AthenaTwitchBot/decorators/frequentoutput.py rename to src/AthenaTwitchBot/decorators/task_schedule.py index 4f738f4..952e2db 100644 --- a/src/AthenaTwitchBot/decorators/frequentoutput.py +++ b/src/AthenaTwitchBot/decorators/task_schedule.py @@ -11,7 +11,7 @@ # ---------------------------------------------------------------------------------------------------------------------- # - Code - # ---------------------------------------------------------------------------------------------------------------------- -def frequent_output_method(delay:int=3600): # default is every hour +def task_schedule_method(delay:int=3600): # default is every hour """ Create a method that runs every couple of seconds. The delay parameter is defined in seconds From 2f6a5db08561d14562390c9e786f72833cdff48a Mon Sep 17 00:00:00 2001 From: DirectiveAthena Date: Wed, 15 Jun 2022 14:26:50 +0200 Subject: [PATCH 3/8] Feat: command_method now creates a `TwitchCommand` object --- src/AthenaTwitchBot/decorators/command.py | 16 ++++++++++---- src/AthenaTwitchBot/models/twitch_bot.py | 5 +++-- .../models/twitch_bot_protocol.py | 18 ++++++++++----- src/AthenaTwitchBot/models/twitch_command.py | 22 +++++++++++++++++++ 4 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 src/AthenaTwitchBot/models/twitch_command.py diff --git a/src/AthenaTwitchBot/decorators/command.py b/src/AthenaTwitchBot/decorators/command.py index 7ed2ed7..b89b1c8 100644 --- a/src/AthenaTwitchBot/decorators/command.py +++ b/src/AthenaTwitchBot/decorators/command.py @@ -7,18 +7,26 @@ # Custom Library # Custom Packages +from AthenaTwitchBot.models.twitch_command import TwitchCommand # ---------------------------------------------------------------------------------------------------------------------- # - Code - # ---------------------------------------------------------------------------------------------------------------------- -def command_method(name:str): +def command_method(*args, name:str, force_capitalization:bool=False, **kwargs): def decorator(fnc): - def wrapper(*args, **kwargs): - return fnc(*args, **kwargs) + def wrapper(*args_, **kwargs_): + return fnc(*args_, **kwargs_) # store attributes for later use by the bot wrapper.is_command = True - wrapper.command_name = name + # store some information + wrapper.cmd = TwitchCommand( + name=name, + force_capitalization=force_capitalization, + callback=wrapper, + args=args, + kwargs = kwargs, + ) return wrapper return decorator \ No newline at end of file diff --git a/src/AthenaTwitchBot/models/twitch_bot.py b/src/AthenaTwitchBot/models/twitch_bot.py index 1e526e5..4f8a8e3 100644 --- a/src/AthenaTwitchBot/models/twitch_bot.py +++ b/src/AthenaTwitchBot/models/twitch_bot.py @@ -10,6 +10,7 @@ # Custom Library # Custom Packages +from AthenaTwitchBot.models.twitch_command import TwitchCommand # ---------------------------------------------------------------------------------------------------------------------- # - Code - @@ -29,7 +30,7 @@ class TwitchBot: predefined_commands:InitVar[dict[str: Callable]]=None # made part of init if someone wants to feel the pain of adding commands manually # noinspection PyDataclass - commands:dict[str: Callable]=field(init=False) + commands:dict[str: TwitchCommand]=field(init=False) frequent_outputs:list[tuple[Callable, int]]=field(init=False) # non init slots @@ -50,7 +51,7 @@ def __new__(cls, *args, **kwargs): for k,v in cls.__dict__.items(): if inspect.isfunction(v): if "is_command" in (attributes := [attribute for attribute in dir(v) if not attribute.startswith("__")]): - cls.commands[v.command_name] = v + cls.commands[v.cmd.name.lower()] = v.cmd elif "is_frequent_output" in attributes: cls.frequent_outputs.append((v,v.delay)) diff --git a/src/AthenaTwitchBot/models/twitch_bot_protocol.py b/src/AthenaTwitchBot/models/twitch_bot_protocol.py index 4e91cac..dcfa4c3 100644 --- a/src/AthenaTwitchBot/models/twitch_bot_protocol.py +++ b/src/AthenaTwitchBot/models/twitch_bot_protocol.py @@ -17,6 +17,7 @@ from AthenaTwitchBot.models.twitch_message import TwitchMessage, TwitchMessagePing from AthenaTwitchBot.models.twitch_bot import TwitchBot from AthenaTwitchBot.models.twitch_message_context import TwitchMessageContext +from AthenaTwitchBot.models.twitch_command import TwitchCommand # ---------------------------------------------------------------------------------------------------------------------- # - Code - @@ -66,6 +67,8 @@ async def frequent_output_call(self, callback,delay:int): def data_received(self, data: bytearray) -> None: for message in data.split(b"\r\n"): + if message == bytearray(b''): # if the bytearray is empty, just skip to the next one + continue match (twitch_message := self.message_constructor(message, bot_name=self.bot.nickname)): # Keepalive messages : https://dev.twitch.tv/docs/irc#keepalive-messages case TwitchMessagePing(): @@ -74,12 +77,15 @@ def data_received(self, data: bytearray) -> None: # catch a message which starts with a command: case TwitchMessage(text=str(user_message)) if user_message.startswith(f"{self.bot.prefix}"): - print(ForeNest.ForestGreen("COMMAND CAUGHT")) + user_message:str + print(ForeNest.ForestGreen("POSSIBLE COMMAND CAUGHT")) try: - user_cmd = user_message.replace(f"{self.bot.prefix}", "") - # tuple unpacking because we have a callback - # and the object instance where the command is placed in - self.bot.commands[user_cmd]( + user_cmd_str = user_message.replace(f"{self.bot.prefix}", "") + twitch_command:TwitchCommand = self.bot.commands[user_cmd_str.lower()] + if twitch_command.force_capitalization and user_cmd_str != twitch_command.name: + raise KeyError # the check to make the force capitalization work + + twitch_command.callback( self=self.bot, # Assign a context so the user doesn't need to write the transport messages themselves # A user only has to write the text @@ -88,7 +94,9 @@ def data_received(self, data: bytearray) -> None: transport=self.transport ) ) + print(ForeNest.ForestGreen("COMMAND EXECUTED")) except KeyError: + print(ForeNest.Crimson("COMMAND COULD NOT BE PARSED")) pass def connection_lost(self, exc: Exception | None) -> None: diff --git a/src/AthenaTwitchBot/models/twitch_command.py b/src/AthenaTwitchBot/models/twitch_command.py new file mode 100644 index 0000000..cec9519 --- /dev/null +++ b/src/AthenaTwitchBot/models/twitch_command.py @@ -0,0 +1,22 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# - Package Imports - +# ---------------------------------------------------------------------------------------------------------------------- +# General Packages +from __future__ import annotations +from dataclasses import dataclass +from typing import Callable + +# Custom Library + +# Custom Packages + +# ---------------------------------------------------------------------------------------------------------------------- +# - Code - +# ---------------------------------------------------------------------------------------------------------------------- +@dataclass(kw_only=True, match_args=True, slots=True) +class TwitchCommand: + name:str + force_capitalization:bool + callback:Callable + args:tuple + kwargs:dict From 7eb23abc2ac824c58f2f7aa288c0392fb967733f Mon Sep 17 00:00:00 2001 From: DirectiveAthena Date: Wed, 15 Jun 2022 15:32:15 +0200 Subject: [PATCH 4/8] Feat: Output abc class and scheduled_task rename --- src/AthenaTwitchBot/__init__.py | 2 +- src/AthenaTwitchBot/decorators/command.py | 8 +-- .../{task_schedule.py => scheduled_task.py} | 12 +++- src/AthenaTwitchBot/functions/launch.py | 68 ++++++++++++------- .../functions/twitch_message_constructors.py | 40 ++++++----- .../models/outputs/__init__.py | 13 ++++ src/AthenaTwitchBot/models/outputs/output.py | 27 ++++++++ .../models/outputs/output_console.py | 43 ++++++++++++ src/AthenaTwitchBot/models/twitch_bot.py | 11 +-- .../models/twitch_bot_protocol.py | 55 ++++++++++----- src/AthenaTwitchBot/models/twitch_message.py | 5 ++ .../models/twitch_message_context.py | 2 + .../models/wrapper_helpers/__init__.py | 13 ++++ .../command.py} | 4 +- .../models/wrapper_helpers/scheduled_task.py | 20 ++++++ 15 files changed, 246 insertions(+), 77 deletions(-) rename src/AthenaTwitchBot/decorators/{task_schedule.py => scheduled_task.py} (73%) create mode 100644 src/AthenaTwitchBot/models/outputs/__init__.py create mode 100644 src/AthenaTwitchBot/models/outputs/output.py create mode 100644 src/AthenaTwitchBot/models/outputs/output_console.py create mode 100644 src/AthenaTwitchBot/models/wrapper_helpers/__init__.py rename src/AthenaTwitchBot/models/{twitch_command.py => wrapper_helpers/command.py} (93%) create mode 100644 src/AthenaTwitchBot/models/wrapper_helpers/scheduled_task.py diff --git a/src/AthenaTwitchBot/__init__.py b/src/AthenaTwitchBot/__init__.py index 6a2cb3e..d33c6c6 100644 --- a/src/AthenaTwitchBot/__init__.py +++ b/src/AthenaTwitchBot/__init__.py @@ -2,7 +2,7 @@ # - Package Imports - # ---------------------------------------------------------------------------------------------------------------------- from AthenaTwitchBot.decorators.command import command_method -from AthenaTwitchBot.decorators.task_schedule import task_schedule_method +from AthenaTwitchBot.decorators.scheduled_task import scheduled_task_method from AthenaTwitchBot.models.twitch_bot import TwitchBot from AthenaTwitchBot.models.twitch_bot_protocol import TwitchBotProtocol diff --git a/src/AthenaTwitchBot/decorators/command.py b/src/AthenaTwitchBot/decorators/command.py index b89b1c8..f1a46eb 100644 --- a/src/AthenaTwitchBot/decorators/command.py +++ b/src/AthenaTwitchBot/decorators/command.py @@ -7,12 +7,12 @@ # Custom Library # Custom Packages -from AthenaTwitchBot.models.twitch_command import TwitchCommand +from AthenaTwitchBot.models.wrapper_helpers.command import Command # ---------------------------------------------------------------------------------------------------------------------- # - Code - # ---------------------------------------------------------------------------------------------------------------------- -def command_method(*args, name:str, force_capitalization:bool=False, **kwargs): +def command_method(name:str, force_capitalization:bool=False): def decorator(fnc): def wrapper(*args_, **kwargs_): return fnc(*args_, **kwargs_) @@ -20,12 +20,10 @@ def wrapper(*args_, **kwargs_): # store attributes for later use by the bot wrapper.is_command = True # store some information - wrapper.cmd = TwitchCommand( + wrapper.cmd = Command( name=name, force_capitalization=force_capitalization, callback=wrapper, - args=args, - kwargs = kwargs, ) return wrapper diff --git a/src/AthenaTwitchBot/decorators/task_schedule.py b/src/AthenaTwitchBot/decorators/scheduled_task.py similarity index 73% rename from src/AthenaTwitchBot/decorators/task_schedule.py rename to src/AthenaTwitchBot/decorators/scheduled_task.py index 952e2db..7b48e1d 100644 --- a/src/AthenaTwitchBot/decorators/task_schedule.py +++ b/src/AthenaTwitchBot/decorators/scheduled_task.py @@ -7,14 +7,16 @@ # Custom Library # Custom Packages +from AthenaTwitchBot.models.wrapper_helpers.scheduled_task import ScheduledTask # ---------------------------------------------------------------------------------------------------------------------- # - Code - # ---------------------------------------------------------------------------------------------------------------------- -def task_schedule_method(delay:int=3600): # default is every hour +def scheduled_task_method(*,delay:int=3600,before:bool=True): # default is every hour """ Create a method that runs every couple of seconds. The delay parameter is defined in seconds + :param before: :param delay: :return: """ @@ -25,7 +27,11 @@ def wrapper(*args, **kwargs): # store attributes for later use by the bot # to be used by the protocol to assign it top an async call loop - wrapper.is_frequent_output = True # typo caught by NoirPi - wrapper.delay = delay + wrapper.is_task = True # typo caught by NoirPi + wrapper.tsk = ScheduledTask( + delay=delay, + before=before, + callback=wrapper + ) return wrapper return decorator \ No newline at end of file diff --git a/src/AthenaTwitchBot/functions/launch.py b/src/AthenaTwitchBot/functions/launch.py index 6d82ca4..3999c2a 100644 --- a/src/AthenaTwitchBot/functions/launch.py +++ b/src/AthenaTwitchBot/functions/launch.py @@ -11,37 +11,57 @@ # Custom Packages from AthenaTwitchBot.models.twitch_bot import TwitchBot from AthenaTwitchBot.models.twitch_bot_protocol import TwitchBotProtocol +from AthenaTwitchBot.models.outputs.output import Output +from AthenaTwitchBot.models.outputs.output_console import OutputConsole # ---------------------------------------------------------------------------------------------------------------------- # - Code - # ---------------------------------------------------------------------------------------------------------------------- def launch( + *, # after this, keywords only bot:TwitchBot=None, protocol_factory:Callable=None, - *, + output:Output=None, host:str='irc.chat.twitch.tv', - port:int=6667 #todo make this into the ssl port + port:int=6667, #todo make this into the ssl port + + auto_restart:bool=False ): - # a bot always has to be defined - if bot is None or not isinstance(bot, TwitchBot): - raise SyntaxError("a proper bot has not been defined") - - loop = asyncio.get_event_loop() - - # assemble the protocol if a custom hasn't been defined - if protocol_factory is None: - protocol_factory = lambda: TwitchBotProtocol( - bot=bot, - main_loop=loop, - ) - - loop.run_until_complete( - loop.create_connection( - protocol_factory=protocol_factory, - host=host, - port=port, - ) - ) - loop.run_forever() - loop.close() + + if output is None: + output=OutputConsole() + output.pre_launch() + + while True: + try: + # a bot always has to be defined + if bot is None or not isinstance(bot, TwitchBot): + raise SyntaxError("a proper bot has not been defined") + + loop = asyncio.get_event_loop() + + # assemble the protocol if a custom hasn't been defined + if protocol_factory is None: + protocol_factory = lambda: TwitchBotProtocol(bot=bot,output=output) + + loop.run_until_complete( + loop.create_connection( + protocol_factory=protocol_factory, + host=host, + port=port, + ) + ) + loop.run_forever() + loop.close() + except ConnectionResetError: + print("not connection") + if auto_restart: + loop = asyncio.get_running_loop() + loop.stop() + continue + else: + break + + except : # make sure everything else is caught else the loop will continue indefinitely + raise diff --git a/src/AthenaTwitchBot/functions/twitch_message_constructors.py b/src/AthenaTwitchBot/functions/twitch_message_constructors.py index e65bfe2..1ef958a 100644 --- a/src/AthenaTwitchBot/functions/twitch_message_constructors.py +++ b/src/AthenaTwitchBot/functions/twitch_message_constructors.py @@ -32,30 +32,34 @@ def _find_bot_only(content:list[str],message:str, bot_name:str) -> TwitchMessage return False TAG_MAPPING:dict[str:Callable] = { - "@badge-info": lambda tm, tag_value: setattr(tm, "badge_info", tag_value), - "badges": lambda tm, tag_value: setattr(tm, "badges", tag_value), - "client-nonce": lambda tm, tag_value: setattr(tm, "client_nonce", tag_value), - "color": lambda tm, tag_value: setattr(tm, "color", HEX(tag_value) if tag_value else HEX()), - "display-name": lambda tm, tag_value: setattr(tm, "display_name", tag_value), - "emotes": lambda tm, tag_value: setattr(tm, "emotes", tag_value), - "first-msg": lambda tm, tag_value: setattr(tm, "first_msg", bool(tag_value)), - "flags": lambda tm, tag_value: setattr(tm, "flags", tag_value), - "id": lambda tm, tag_value: setattr(tm, "message_id", tag_value), - "mod": lambda tm, tag_value: setattr(tm, "mod", bool(tag_value)), - "room-id": lambda tm, tag_value: setattr(tm, "room_id", tag_value), - "subscriber": lambda tm, tag_value: setattr(tm, "subscriber", bool(tag_value)), - "tmi-sent-ts": lambda tm, tag_value: setattr(tm, "tmi_sent_ts", int(tag_value)), - "turbo": lambda tm, tag_value: setattr(tm, "turbo", bool(tag_value)), - "user-id": lambda tm, tag_value: setattr(tm, "user_id", int(tag_value)), - "user-type": lambda tm, tag_value: setattr(tm, "user_type", tag_value), - "emote-only": lambda tm, tag_value: setattr(tm, "emote_only", bool(tag_value)), + "@badge-info": lambda tm, tag_value: setattr(tm, "badge_info", tag_value), + "badges": lambda tm, tag_value: setattr(tm, "badges", tag_value), + "client-nonce": lambda tm, tag_value: setattr(tm, "client_nonce", tag_value), + "color": lambda tm, tag_value: setattr(tm, "color", HEX(tag_value) if tag_value else HEX()), + "display-name": lambda tm, tag_value: setattr(tm, "display_name", tag_value), + "emotes": lambda tm, tag_value: setattr(tm, "emotes", tag_value), + "first-msg": lambda tm, tag_value: setattr(tm, "first_msg", bool(int(tag_value))), + "flags": lambda tm, tag_value: setattr(tm, "flags", tag_value), + "id": lambda tm, tag_value: setattr(tm, "message_id", tag_value), + "mod": lambda tm, tag_value: setattr(tm, "mod", bool(int(tag_value))), + "room-id": lambda tm, tag_value: setattr(tm, "room_id", tag_value), + "subscriber": lambda tm, tag_value: setattr(tm, "subscriber", bool(int(tag_value))), + "tmi-sent-ts": lambda tm, tag_value: setattr(tm, "tmi_sent_ts", int(tag_value)), + "turbo": lambda tm, tag_value: setattr(tm, "turbo", bool(int(tag_value))), + "user-id": lambda tm, tag_value: setattr(tm, "user_id", int(tag_value)), + "user-type": lambda tm, tag_value: setattr(tm, "user_type", tag_value), + "reply-parent-display-name":lambda tm, tag_value: setattr(tm, "reply_parent_display_name", tag_value), + "reply-parent-msg-body": lambda tm, tag_value: setattr(tm, "reply_parent_msg_body", tag_value), + "reply-parent-msg-id": lambda tm, tag_value: setattr(tm, "reply_parent_msg_id", int(tag_value)), + "reply-parent-user-id": lambda tm, tag_value: setattr(tm, "reply_parent_user_id", int(tag_value)), + "reply-parent-user-login": lambda tm, tag_value: setattr(tm, "reply_parent_user_login", tag_value), + "emote-only": lambda tm, tag_value: setattr(tm, "emote_only", bool(int(tag_value))), } # ---------------------------------------------------------------------------------------------------------------------- # - Code - # ---------------------------------------------------------------------------------------------------------------------- def twitch_message_constructor_tags(message_bytes:bytearray, bot_name:str) -> TwitchMessage: - print(message_bytes) message = message_bytes.decode("UTF_8") content = message.split(" ") diff --git a/src/AthenaTwitchBot/models/outputs/__init__.py b/src/AthenaTwitchBot/models/outputs/__init__.py new file mode 100644 index 0000000..8d6d09b --- /dev/null +++ b/src/AthenaTwitchBot/models/outputs/__init__.py @@ -0,0 +1,13 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# - Package Imports - +# ---------------------------------------------------------------------------------------------------------------------- +# General Packages +from __future__ import annotations + +# Custom Library + +# Custom Packages + +# ---------------------------------------------------------------------------------------------------------------------- +# - Code - +# ---------------------------------------------------------------------------------------------------------------------- diff --git a/src/AthenaTwitchBot/models/outputs/output.py b/src/AthenaTwitchBot/models/outputs/output.py new file mode 100644 index 0000000..8f63b56 --- /dev/null +++ b/src/AthenaTwitchBot/models/outputs/output.py @@ -0,0 +1,27 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# - Package Imports - +# ---------------------------------------------------------------------------------------------------------------------- +# General Packages +from __future__ import annotations +from abc import ABC, abstractmethod + +# Custom Library + +# Custom Packages +from AthenaTwitchBot.models.twitch_message import TwitchMessage + +# ---------------------------------------------------------------------------------------------------------------------- +# - Code - +# ---------------------------------------------------------------------------------------------------------------------- +class Output(ABC): + + @abstractmethod + def pre_launch(self): + """Output the state of the application before anything is run""" + @abstractmethod + def message(self, message:TwitchMessage): + """Output of a received message""" + + @abstractmethod + def undefined(self,message=None): + """Output anything that is supposed to be undefined (this should eventually not be present anymore)""" \ No newline at end of file diff --git a/src/AthenaTwitchBot/models/outputs/output_console.py b/src/AthenaTwitchBot/models/outputs/output_console.py new file mode 100644 index 0000000..a42d532 --- /dev/null +++ b/src/AthenaTwitchBot/models/outputs/output_console.py @@ -0,0 +1,43 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# - Package Imports - +# ---------------------------------------------------------------------------------------------------------------------- +# General Packages +from __future__ import annotations + +# Custom Library +from AthenaColor import StyleNest, ForeNest, BackNest + +# Custom Packages +from AthenaTwitchBot.models.outputs.output import Output +from AthenaTwitchBot.models.twitch_message import TwitchMessage, TwitchMessagePing, TwitchMessageOnlyForBot +# noinspection PyProtectedMember +from AthenaTwitchBot._info._v import _version + +# ---------------------------------------------------------------------------------------------------------------------- +# - Code - +# ---------------------------------------------------------------------------------------------------------------------- +class OutputConsole(Output): + + def pre_launch(self): + print( + ForeNest.SlateGray( + f"- AthenaTwitchBot {ForeNest.HotPink('v', _version(), sep='')} -", + f" made by Andreas Sas", + "", + sep="\n" + ), + ) + + def message(self, message:TwitchMessage): + match message: + case TwitchMessagePing(): + print(ForeNest.SlateGray(message.text), ForeNest.ForestGreen("PING RECEIVED")) + case TwitchMessageOnlyForBot(): + print(ForeNest.SlateGray(message.text)) + case TwitchMessage(first_msg=True): + print(ForeNest.BlueViolet(message.username), ForeNest.SlateGray(":"),ForeNest.White(message.text)) + case TwitchMessage(): + print(ForeNest.SlateGray(message.username, ":"),ForeNest.White(message.text)) + + def undefined(self,message=None): + print(ForeNest.SlateGray(message)) diff --git a/src/AthenaTwitchBot/models/twitch_bot.py b/src/AthenaTwitchBot/models/twitch_bot.py index 4f8a8e3..62b400a 100644 --- a/src/AthenaTwitchBot/models/twitch_bot.py +++ b/src/AthenaTwitchBot/models/twitch_bot.py @@ -10,7 +10,8 @@ # Custom Library # Custom Packages -from AthenaTwitchBot.models.twitch_command import TwitchCommand +from AthenaTwitchBot.models.wrapper_helpers.command import Command +from AthenaTwitchBot.models.wrapper_helpers.scheduled_task import ScheduledTask # ---------------------------------------------------------------------------------------------------------------------- # - Code - @@ -30,8 +31,8 @@ class TwitchBot: predefined_commands:InitVar[dict[str: Callable]]=None # made part of init if someone wants to feel the pain of adding commands manually # noinspection PyDataclass - commands:dict[str: TwitchCommand]=field(init=False) - frequent_outputs:list[tuple[Callable, int]]=field(init=False) + commands:dict[str: Command]=field(init=False) + scheduled_tasks:list[ScheduledTask]=field(init=False) # non init slots @@ -41,7 +42,7 @@ class TwitchBot: def __new__(cls, *args, **kwargs): # Loop over own functions to see if any is decorated with the command setup cls.commands = {} - cls.frequent_outputs = [] + cls.scheduled_tasks = [] # create the actual instance # Which is to be used in the commands tuple @@ -53,7 +54,7 @@ def __new__(cls, *args, **kwargs): if "is_command" in (attributes := [attribute for attribute in dir(v) if not attribute.startswith("__")]): cls.commands[v.cmd.name.lower()] = v.cmd elif "is_frequent_output" in attributes: - cls.frequent_outputs.append((v,v.delay)) + cls.scheduled_tasks.append(v.tsk) return obj diff --git a/src/AthenaTwitchBot/models/twitch_bot_protocol.py b/src/AthenaTwitchBot/models/twitch_bot_protocol.py index dcfa4c3..45e4087 100644 --- a/src/AthenaTwitchBot/models/twitch_bot_protocol.py +++ b/src/AthenaTwitchBot/models/twitch_bot_protocol.py @@ -8,7 +8,6 @@ from typing import Callable # Custom Library -from AthenaColor import ForeNest # Custom Packages import AthenaTwitchBot.functions.twitch_irc_messages as messages @@ -17,7 +16,9 @@ from AthenaTwitchBot.models.twitch_message import TwitchMessage, TwitchMessagePing from AthenaTwitchBot.models.twitch_bot import TwitchBot from AthenaTwitchBot.models.twitch_message_context import TwitchMessageContext -from AthenaTwitchBot.models.twitch_command import TwitchCommand +from AthenaTwitchBot.models.wrapper_helpers.command import Command +from AthenaTwitchBot.models.wrapper_helpers.scheduled_task import ScheduledTask +from AthenaTwitchBot.models.outputs.output import Output # ---------------------------------------------------------------------------------------------------------------------- # - Code - @@ -25,7 +26,7 @@ @dataclass(kw_only=True, slots=True, eq=False, order=False) class TwitchBotProtocol(asyncio.Protocol): bot:TwitchBot - main_loop:asyncio.AbstractEventLoop + output:Output # non init slots transport:asyncio.transports.Transport = field(init=False) @@ -50,38 +51,49 @@ def connection_made(self, transport: asyncio.transports.Transport) -> None: # add frequent_output methods to the coroutine loop loop = asyncio.get_running_loop() - for callback, delay in self.bot.frequent_outputs: - coro = loop.create_task(self.frequent_output_call(callback,delay)) + for tsk in self.bot.scheduled_tasks: + coro = loop.create_task(self.frequent_output_call(tsk)) asyncio.ensure_future(coro, loop=loop) - async def frequent_output_call(self, callback,delay:int): + async def frequent_output_call(self, tsk:ScheduledTask): context = TwitchMessageContext( message=TwitchMessage(channel=f"#{self.bot.channel}"), transport=self.transport ) - while True: - await asyncio.sleep(delay) - callback( - self=self.bot, - context=context) + if tsk.before: # the before attribute handles if we sleep before or after the task has been called + while True: + await asyncio.sleep(tsk.delay) + tsk.callback( + self=self.bot, + context=context) + else: + while True: + tsk.callback( + self=self.bot, + context=context) + await asyncio.sleep(tsk.delay) + def data_received(self, data: bytearray) -> None: for message in data.split(b"\r\n"): - if message == bytearray(b''): # if the bytearray is empty, just skip to the next one + # if the bytearray is empty, just skip to the next one + if message == bytearray(b''): continue + match (twitch_message := self.message_constructor(message, bot_name=self.bot.nickname)): # Keepalive messages : https://dev.twitch.tv/docs/irc#keepalive-messages case TwitchMessagePing(): - print(ForeNest.ForestGreen("PINGED BY TWITCH")) + self.output.message(twitch_message) self.transport.write(messages.pong(message=twitch_message.text)) + continue # catch a message which starts with a command: case TwitchMessage(text=str(user_message)) if user_message.startswith(f"{self.bot.prefix}"): + self.output.message(twitch_message) user_message:str - print(ForeNest.ForestGreen("POSSIBLE COMMAND CAUGHT")) try: user_cmd_str = user_message.replace(f"{self.bot.prefix}", "") - twitch_command:TwitchCommand = self.bot.commands[user_cmd_str.lower()] + twitch_command:Command = self.bot.commands[user_cmd_str.lower()] if twitch_command.force_capitalization and user_cmd_str != twitch_command.name: raise KeyError # the check to make the force capitalization work @@ -94,11 +106,18 @@ def data_received(self, data: bytearray) -> None: transport=self.transport ) ) - print(ForeNest.ForestGreen("COMMAND EXECUTED")) except KeyError: - print(ForeNest.Crimson("COMMAND COULD NOT BE PARSED")) pass + continue + + # if the message wasn't able to be handled by the parser above + # it will just be outputted as undefined + self.output.undefined(message) def connection_lost(self, exc: Exception | None) -> None: - self.main_loop.stop() + loop = asyncio.get_running_loop() + loop.stop() + + if exc is not None: + raise exc diff --git a/src/AthenaTwitchBot/models/twitch_message.py b/src/AthenaTwitchBot/models/twitch_message.py index 5e6accd..9f878a5 100644 --- a/src/AthenaTwitchBot/models/twitch_message.py +++ b/src/AthenaTwitchBot/models/twitch_message.py @@ -42,6 +42,11 @@ class TwitchMessage: flags:str=EMPTY_STR user_type:str=EMPTY_STR emote_only:bool=False + reply_parent_display_name:str="" + reply_parent_msg_body:str="" + reply_parent_msg_id:int=0 + reply_parent_user_id:int=0 + reply_parent_user_login:str="" def __post_init__(self): self.username = self.user.split("!")[0][1:] diff --git a/src/AthenaTwitchBot/models/twitch_message_context.py b/src/AthenaTwitchBot/models/twitch_message_context.py index d5adaa2..addeafc 100644 --- a/src/AthenaTwitchBot/models/twitch_message_context.py +++ b/src/AthenaTwitchBot/models/twitch_message_context.py @@ -29,6 +29,7 @@ def reply(self, text:str): """ a "reply" method does reply to the user's message that invoked the command """ + text = text.replace('\n', '') self.transport.write( f"@reply-parent-msg-id={self.message.message_id} PRIVMSG {self.message.channel} :{text}\r\n".encode("UTF_8") ) @@ -37,6 +38,7 @@ def write(self, text:str): """ a "write" method does not reply to the message that invoked the command, but simply writes the text to the channel """ + text = text.replace('\n', '') self.transport.write( f"PRIVMSG {self.message.channel} :{text}\r\n".encode("UTF_8") ) \ No newline at end of file diff --git a/src/AthenaTwitchBot/models/wrapper_helpers/__init__.py b/src/AthenaTwitchBot/models/wrapper_helpers/__init__.py new file mode 100644 index 0000000..8d6d09b --- /dev/null +++ b/src/AthenaTwitchBot/models/wrapper_helpers/__init__.py @@ -0,0 +1,13 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# - Package Imports - +# ---------------------------------------------------------------------------------------------------------------------- +# General Packages +from __future__ import annotations + +# Custom Library + +# Custom Packages + +# ---------------------------------------------------------------------------------------------------------------------- +# - Code - +# ---------------------------------------------------------------------------------------------------------------------- diff --git a/src/AthenaTwitchBot/models/twitch_command.py b/src/AthenaTwitchBot/models/wrapper_helpers/command.py similarity index 93% rename from src/AthenaTwitchBot/models/twitch_command.py rename to src/AthenaTwitchBot/models/wrapper_helpers/command.py index cec9519..f55cda2 100644 --- a/src/AthenaTwitchBot/models/twitch_command.py +++ b/src/AthenaTwitchBot/models/wrapper_helpers/command.py @@ -14,9 +14,7 @@ # - Code - # ---------------------------------------------------------------------------------------------------------------------- @dataclass(kw_only=True, match_args=True, slots=True) -class TwitchCommand: +class Command: name:str force_capitalization:bool callback:Callable - args:tuple - kwargs:dict diff --git a/src/AthenaTwitchBot/models/wrapper_helpers/scheduled_task.py b/src/AthenaTwitchBot/models/wrapper_helpers/scheduled_task.py new file mode 100644 index 0000000..e150922 --- /dev/null +++ b/src/AthenaTwitchBot/models/wrapper_helpers/scheduled_task.py @@ -0,0 +1,20 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# - Package Imports - +# ---------------------------------------------------------------------------------------------------------------------- +# General Packages +from __future__ import annotations +from dataclasses import dataclass +from typing import Callable + +# Custom Library + +# Custom Packages + +# ---------------------------------------------------------------------------------------------------------------------- +# - Code - +# ---------------------------------------------------------------------------------------------------------------------- +@dataclass(kw_only=True, match_args=True, slots=True) +class ScheduledTask: + delay:int + before:bool + callback:Callable \ No newline at end of file From 74f6b00bad7121b287b8616e03230a43662a078e Mon Sep 17 00:00:00 2001 From: DirectiveAthena Date: Wed, 15 Jun 2022 15:45:59 +0200 Subject: [PATCH 5/8] Change: Better wording for Command attributes --- src/AthenaTwitchBot/data/emotes.py | 14 ++++++++++++++ src/AthenaTwitchBot/decorators/command.py | 4 ++-- src/AthenaTwitchBot/models/twitch_bot.py | 2 +- src/AthenaTwitchBot/models/twitch_bot_protocol.py | 2 +- .../models/wrapper_helpers/command.py | 2 +- 5 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 src/AthenaTwitchBot/data/emotes.py diff --git a/src/AthenaTwitchBot/data/emotes.py b/src/AthenaTwitchBot/data/emotes.py new file mode 100644 index 0000000..f552dda --- /dev/null +++ b/src/AthenaTwitchBot/data/emotes.py @@ -0,0 +1,14 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# - Package Imports - +# ---------------------------------------------------------------------------------------------------------------------- +# General Packages +from __future__ import annotations + +# Custom Library + +# Custom Packages + +# ---------------------------------------------------------------------------------------------------------------------- +# - Code - +# ---------------------------------------------------------------------------------------------------------------------- +four_head = "4head" diff --git a/src/AthenaTwitchBot/decorators/command.py b/src/AthenaTwitchBot/decorators/command.py index f1a46eb..06e243a 100644 --- a/src/AthenaTwitchBot/decorators/command.py +++ b/src/AthenaTwitchBot/decorators/command.py @@ -12,7 +12,7 @@ # ---------------------------------------------------------------------------------------------------------------------- # - Code - # ---------------------------------------------------------------------------------------------------------------------- -def command_method(name:str, force_capitalization:bool=False): +def command_method(name:str, case_sensitive:bool=False): def decorator(fnc): def wrapper(*args_, **kwargs_): return fnc(*args_, **kwargs_) @@ -22,7 +22,7 @@ def wrapper(*args_, **kwargs_): # store some information wrapper.cmd = Command( name=name, - force_capitalization=force_capitalization, + case_sensitive=case_sensitive, callback=wrapper, ) diff --git a/src/AthenaTwitchBot/models/twitch_bot.py b/src/AthenaTwitchBot/models/twitch_bot.py index 62b400a..fe3af84 100644 --- a/src/AthenaTwitchBot/models/twitch_bot.py +++ b/src/AthenaTwitchBot/models/twitch_bot.py @@ -53,7 +53,7 @@ def __new__(cls, *args, **kwargs): if inspect.isfunction(v): if "is_command" in (attributes := [attribute for attribute in dir(v) if not attribute.startswith("__")]): cls.commands[v.cmd.name.lower()] = v.cmd - elif "is_frequent_output" in attributes: + elif "is_task" in attributes: cls.scheduled_tasks.append(v.tsk) return obj diff --git a/src/AthenaTwitchBot/models/twitch_bot_protocol.py b/src/AthenaTwitchBot/models/twitch_bot_protocol.py index 45e4087..8d7c880 100644 --- a/src/AthenaTwitchBot/models/twitch_bot_protocol.py +++ b/src/AthenaTwitchBot/models/twitch_bot_protocol.py @@ -94,7 +94,7 @@ def data_received(self, data: bytearray) -> None: try: user_cmd_str = user_message.replace(f"{self.bot.prefix}", "") twitch_command:Command = self.bot.commands[user_cmd_str.lower()] - if twitch_command.force_capitalization and user_cmd_str != twitch_command.name: + if twitch_command.case_sensitive and user_cmd_str != twitch_command.name: raise KeyError # the check to make the force capitalization work twitch_command.callback( diff --git a/src/AthenaTwitchBot/models/wrapper_helpers/command.py b/src/AthenaTwitchBot/models/wrapper_helpers/command.py index f55cda2..0eee1da 100644 --- a/src/AthenaTwitchBot/models/wrapper_helpers/command.py +++ b/src/AthenaTwitchBot/models/wrapper_helpers/command.py @@ -16,5 +16,5 @@ @dataclass(kw_only=True, match_args=True, slots=True) class Command: name:str - force_capitalization:bool + case_sensitive:bool callback:Callable From aa7403f0251f409c982ed338f15e1b56e3e75e38 Mon Sep 17 00:00:00 2001 From: DirectiveAthena Date: Wed, 15 Jun 2022 15:56:17 +0200 Subject: [PATCH 6/8] Feat: in_text_commands --- src/AthenaTwitchBot/models/twitch_bot.py | 2 ++ .../models/twitch_bot_protocol.py | 29 +++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/AthenaTwitchBot/models/twitch_bot.py b/src/AthenaTwitchBot/models/twitch_bot.py index fe3af84..7ded711 100644 --- a/src/AthenaTwitchBot/models/twitch_bot.py +++ b/src/AthenaTwitchBot/models/twitch_bot.py @@ -23,6 +23,8 @@ class TwitchBot: channel:str prefix:str + in_text_commands:bool=False # allows for commands to be placed within text + # Twitch-specific capabilities : https://dev.twitch.tv/docs/irc/capabilities twitch_capability_commands:bool=False twitch_capability_membership:bool=False diff --git a/src/AthenaTwitchBot/models/twitch_bot_protocol.py b/src/AthenaTwitchBot/models/twitch_bot_protocol.py index 8d7c880..f9fd096 100644 --- a/src/AthenaTwitchBot/models/twitch_bot_protocol.py +++ b/src/AthenaTwitchBot/models/twitch_bot_protocol.py @@ -88,11 +88,11 @@ def data_received(self, data: bytearray) -> None: continue # catch a message which starts with a command: - case TwitchMessage(text=str(user_message)) if user_message.startswith(f"{self.bot.prefix}"): + case TwitchMessage(text=str(user_message)) if user_message.startswith(f"{self.bot.prefix}") and not self.bot.in_text_commands: self.output.message(twitch_message) user_message:str try: - user_cmd_str = user_message.replace(f"{self.bot.prefix}", "") + user_cmd_str = user_message.replace(self.bot.prefix, "") twitch_command:Command = self.bot.commands[user_cmd_str.lower()] if twitch_command.case_sensitive and user_cmd_str != twitch_command.name: raise KeyError # the check to make the force capitalization work @@ -110,6 +110,31 @@ def data_received(self, data: bytearray) -> None: pass continue + # catch a message if a command is within the text: (if the bot has that setting enabled) + case TwitchMessage(text=str(user_message)) if self.bot.in_text_commands: + self.output.message(twitch_message) + for s in user_message.split(" "): + try: + if not s.startswith(self.bot.prefix): + continue + user_cmd_str = s.replace(f"{self.bot.prefix}", "") + twitch_command: Command = self.bot.commands[user_cmd_str.lower()] + if twitch_command.case_sensitive and user_cmd_str != twitch_command.name: + raise KeyError # the check to make the force capitalization work + + twitch_command.callback( + self=self.bot, + # Assign a context so the user doesn't need to write the transport messages themselves + # A user only has to write the text + context=TwitchMessageContext( + message=twitch_message, + transport=self.transport + ) + ) + except KeyError: + pass + continue + # if the message wasn't able to be handled by the parser above # it will just be outputted as undefined self.output.undefined(message) From 6b3baf7780d790b9e77e9288fac10a247d381c27 Mon Sep 17 00:00:00 2001 From: DirectiveAthena Date: Wed, 15 Jun 2022 16:09:14 +0200 Subject: [PATCH 7/8] Revert of in_text_commands --- src/AthenaTwitchBot/models/twitch_bot.py | 2 -- .../models/twitch_bot_protocol.py | 27 +------------------ 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/AthenaTwitchBot/models/twitch_bot.py b/src/AthenaTwitchBot/models/twitch_bot.py index 7ded711..fe3af84 100644 --- a/src/AthenaTwitchBot/models/twitch_bot.py +++ b/src/AthenaTwitchBot/models/twitch_bot.py @@ -23,8 +23,6 @@ class TwitchBot: channel:str prefix:str - in_text_commands:bool=False # allows for commands to be placed within text - # Twitch-specific capabilities : https://dev.twitch.tv/docs/irc/capabilities twitch_capability_commands:bool=False twitch_capability_membership:bool=False diff --git a/src/AthenaTwitchBot/models/twitch_bot_protocol.py b/src/AthenaTwitchBot/models/twitch_bot_protocol.py index f9fd096..06265b7 100644 --- a/src/AthenaTwitchBot/models/twitch_bot_protocol.py +++ b/src/AthenaTwitchBot/models/twitch_bot_protocol.py @@ -88,7 +88,7 @@ def data_received(self, data: bytearray) -> None: continue # catch a message which starts with a command: - case TwitchMessage(text=str(user_message)) if user_message.startswith(f"{self.bot.prefix}") and not self.bot.in_text_commands: + case TwitchMessage(text=str(user_message)) if user_message.startswith(f"{self.bot.prefix}"): self.output.message(twitch_message) user_message:str try: @@ -110,31 +110,6 @@ def data_received(self, data: bytearray) -> None: pass continue - # catch a message if a command is within the text: (if the bot has that setting enabled) - case TwitchMessage(text=str(user_message)) if self.bot.in_text_commands: - self.output.message(twitch_message) - for s in user_message.split(" "): - try: - if not s.startswith(self.bot.prefix): - continue - user_cmd_str = s.replace(f"{self.bot.prefix}", "") - twitch_command: Command = self.bot.commands[user_cmd_str.lower()] - if twitch_command.case_sensitive and user_cmd_str != twitch_command.name: - raise KeyError # the check to make the force capitalization work - - twitch_command.callback( - self=self.bot, - # Assign a context so the user doesn't need to write the transport messages themselves - # A user only has to write the text - context=TwitchMessageContext( - message=twitch_message, - transport=self.transport - ) - ) - except KeyError: - pass - continue - # if the message wasn't able to be handled by the parser above # it will just be outputted as undefined self.output.undefined(message) From c2b5ed66cda9d5c698146aca4524ad0a7ce0a3fd Mon Sep 17 00:00:00 2001 From: DirectiveAthena Date: Wed, 15 Jun 2022 16:11:23 +0200 Subject: [PATCH 8/8] Feat v0.2.0 version strings --- setup.py | 2 +- src/AthenaTwitchBot/_info/_v.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ab33603..c285f8b 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ # - Code - # ---------------------------------------------------------------------------------------------------------------------- def version_handler() -> str: - version = 0,1,1 + version = 0,2,0 version_str = ".".join(str(i) for i in version) with open("src/AthenaTwitchBot/_info/_v.py", "w") as file: diff --git a/src/AthenaTwitchBot/_info/_v.py b/src/AthenaTwitchBot/_info/_v.py index 85c1c73..cbe40e8 100644 --- a/src/AthenaTwitchBot/_info/_v.py +++ b/src/AthenaTwitchBot/_info/_v.py @@ -1,2 +1,2 @@ def _version(): - return '0.1.1' \ No newline at end of file + return '0.2.0' \ No newline at end of file