From b444fafc3ccc71345ec4f2435c36e9ba134cdffa Mon Sep 17 00:00:00 2001 From: The_CJ Date: Thu, 14 Jan 2021 23:00:41 +0100 Subject: [PATCH 1/4] fixes in types and formats --- .gitignore | 4 +++- LICENSE | 2 +- README.md | 4 ++-- osu_irc/Classes/channel.py | 2 +- osu_irc/__init__.py | 6 +++--- setup.py | 3 +-- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index ed8ebf5..5896758 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -__pycache__ \ No newline at end of file +__pycache__ +venv/ +.idea/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index 2d7e3a8..1ac909e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018-2020 The_CJ +Copyright (c) 2018-2021 The_CJ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9d76225..34ba133 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ but usable to any purpose ## Install -There are many ways. here my "prefered" one: +There are many ways. here my "preferred" one: ``` py -m pip install git+https://github.com/The-CJ/osu_irc.py.git#egg=osu_irc ``` @@ -33,4 +33,4 @@ x = MyBot(token="1234567890", nickname="cool_username") x.run() ``` Get nickname and server password(token) from here: https://osu.ppy.sh/p/irc -:copyright: 2018-2020 The_CJ +:copyright: 2018-2021 The_CJ diff --git a/osu_irc/Classes/channel.py b/osu_irc/Classes/channel.py index 12d6e05..081e077 100644 --- a/osu_irc/Classes/channel.py +++ b/osu_irc/Classes/channel.py @@ -149,7 +149,7 @@ async def sendMessage(self, cls:"OsuClient", content:str) -> None: Send a message to the channel, requires you to give this function the Client class, don't ask why... - this is basicly an alternative to: + this is basically an alternative to: cls.sendMessage(Channel.name, content) makes you think... is this even faster? i dunno, adding it anyways LULW diff --git a/osu_irc/__init__.py b/osu_irc/__init__.py index 64dc4ed..9a2d221 100644 --- a/osu_irc/__init__.py +++ b/osu_irc/__init__.py @@ -5,20 +5,20 @@ osu! IRC wrapper ################## -Simple to use IRC connection for osu optimited for the PhaazeOS project +Simple to use IRC connection for osu optimized for the PhaazeOS project but usable to any purpose :copyright: (c) 2018-2020 The_CJ :license: MIT License - Inspired by the code of Rapptz's Discord library -- Basicly a copy of my twitch_irc.py library, just for osu! +- Basically a copy of my twitch_irc.py library, just for osu! """ __title__ = 'osu_irc' __author__ = 'The_CJ' __license__ = 'MIT' -__copyright__ = 'Copyright 2018-2020 The_CJ' +__copyright__ = 'Copyright 2018-2021 The_CJ' __version__ = "1.1.1" from .Classes.channel import Channel diff --git a/setup.py b/setup.py index 0a527aa..83b01b3 100644 --- a/setup.py +++ b/setup.py @@ -4,9 +4,8 @@ with open(f"{here}/README.md", "r") as rm: long_description = rm.read() -requirements = [] with open(f"{here}/requirements.txt", "r") as req: - requirements = req.read().splitlines() + requirements = req.read().splitlines() try: version = re.findall(r"^__version__\s?=\s?[\'\"](.+)[\'\"]$", open("osu_irc/__init__.py").read(), re.M)[0] From 8db8f49ecc6a24ac59d3c4e2132be9eca191a3ba Mon Sep 17 00:00:00 2001 From: The_CJ Date: Thu, 14 Jan 2021 23:27:41 +0100 Subject: [PATCH 2/4] reworked client class and moved commands functions --- osu_irc/Classes/client.py | 192 ++++++++++++++++++++++++++------------ osu_irc/Utils/commands.py | 88 ----------------- osu_irc/__init__.py | 2 +- 3 files changed, 134 insertions(+), 148 deletions(-) delete mode 100644 osu_irc/Utils/commands.py diff --git a/osu_irc/Classes/client.py b/osu_irc/Classes/client.py index 245370e..d7d769b 100644 --- a/osu_irc/Classes/client.py +++ b/osu_irc/Classes/client.py @@ -1,9 +1,5 @@ -from typing import List, Dict, NewType -ChannelName = NewType("ChannelName", str) -UserName = NewType("UserName", str) - +from typing import List, Dict, NewType, Optional, Union import logging -Log:logging.Logger = logging.getLogger("osu_irc") import time import asyncio @@ -17,27 +13,27 @@ from ..Utils.traffic import addTraffic, trafficQuery from ..Utils.detector import mainEventDetector, garbageDetector -class Client(): +Log:logging.Logger = logging.getLogger("osu_irc") +ChannelName = NewType("ChannelName", str) +UserName = NewType("UserName", str) + + +class Client(object): """ Main class for everything. Init and call .run() Optional Keyword Arguments -------------------------- - * Loop `asyncio.AbstractEventLoop` : Default: asyncio.get_event_loop() - * Main event loop, used for everything - * reconnect `bool` : Default: True - * Should the client automatically try to reconnect - * nickname `str` : Default: None - * User nickname, only lowercase - * token `str` : Default: None - * User oauth token - * request_limit `int` : Default: 15 - * How many requests can be send before the client goes into rate limit protection (request_limit per 60 sec) + * `Loop` - asyncio.AbstractEventLoop : (Default: asyncio.get_event_loop()) [Main event loop, used for everything] + * `reconnect` - bool : (Default: True) [Should the client automatically try to reconnect] + * `nickname` - str` : (Default: None) [User nickname, only lowercase] + * `token` str : (Default: None) [User oauth token] + * `request_limit` int : (Default: 15)[ How many requests can be send before the client goes into rate limit protection (request_limit per 60 sec)] """ - def __init__(self, Loop:asyncio.AbstractEventLoop=None, **kwargs:dict): + def __init__(self, Loop:Optional[asyncio.AbstractEventLoop]=None, **kwargs): - # setable vars + # vars self.Loop:asyncio.AbstractEventLoop = asyncio.get_event_loop() if Loop is None else Loop self.reconnect:bool = kwargs.get("reconnect", True) self.nickname:str = kwargs.get("nickname", None) @@ -52,18 +48,18 @@ def __init__(self, Loop:asyncio.AbstractEventLoop=None, **kwargs:dict): self.running:bool = False self.auth_success:bool = False self.query_running:bool = False - self.last_ping:int = time.time() + self.last_ping:float = time.time() self.traffic:int = 0 - self.stored_traffic:List[str or bytes] = [] + self.stored_traffic:List[str, bytes] = [] # Connection objects - self.ConnectionReader:asyncio.StreamReader = None - self.ConnectionWriter:asyncio.StreamWriter = None + self.ConnectionReader:Optional[asyncio.StreamReader] = None + self.ConnectionWriter:Optional[asyncio.StreamWriter] = None - self.channels:Dict[ChannelName, Channel] = ChannelStore() - self.users:Dict[UserName, User] = UserStore() + self.channels:Dict[Union[ChannelName, str], Channel] = ChannelStore() + self.users:Dict[Union[UserName, str], User] = UserStore() - def stop(self, *x, **xx) -> None: + def stop(self, *_, **__) -> None: """ gracefully shuts down the bot, .start and .run will be no longer blocking """ @@ -83,8 +79,8 @@ def run(self) -> None: raise RuntimeError("already running") Log.debug(f"Client.run() has been called, creating loop and wrapping future") - MainFuture:asyncio.Future = asyncio.ensure_future( self.start(), loop=self.Loop ) - MainFuture.add_done_callback( self.stop ) + MainFuture:asyncio.Future = asyncio.ensure_future(self.start(), loop=self.Loop) + MainFuture.add_done_callback(self.stop) try: Log.debug(f"Client.run() starting Client.start() future") self.Loop.run_forever() @@ -95,7 +91,7 @@ def run(self) -> None: # Client.stop should be called once, if you break out via exceptions, # since Client.stop also called the Loop to stop, we do some cleanup now - MainFuture.remove_done_callback( self.stop ) + MainFuture.remove_done_callback(self.stop) Log.debug(f"Removing MainFuture callback") # gather all task of the loop (that will mostly be stuff like: addTraffic()) @@ -109,7 +105,7 @@ def run(self) -> None: # and now start the loop again, which will result that all tasks are instantly finished and done Log.debug(f"Restarting loop to discard tasks") - self.Loop.run_until_complete( asyncio.gather(*tasks, return_exceptions=True) ) + self.Loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) # then close it... and i dunno, get a coffee or so Log.debug(f"All task discarded, closing loop") @@ -124,9 +120,9 @@ async def start(self) -> None: ## Warning! This function should be ideally handled via .run() because else, there will be no cleanup of futures and task on .stop() - Which actully is totally ok, but its messy and not really intended. + Which actually is totally ok, but its messy and not really intended. If you don't add loop cleanup yourself, - your console will be flooded by `addTraffic` coros waiting to be completed. + your console will be flooded by `addTraffic` coroutines waiting to be completed. """ if self.running: raise RuntimeError("already running") @@ -134,7 +130,7 @@ async def start(self) -> None: self.running = True self.query_running = True - if self.token == None or self.nickname == None: + if self.token is None or self.nickname is None: raise AttributeError("'token' and 'nickname' must be provided") Log.debug(f"Client.start() all required fields found, awaiting Client.main()") @@ -142,14 +138,14 @@ async def start(self) -> None: async def main(self) -> None: """ - a loop that creates the connections and processess all events + a loop that creates the connections and processes all events if self.reconnect is active, it handles critical errors with a restart of the bot will run forever until self.stop() is called or a critical error without reconnect """ while self.running: - #reset bot storage + # reset bot storage self.last_ping = time.time() self.traffic = 0 self.channels = ChannelStore() @@ -160,21 +156,21 @@ async def main(self) -> None: self.ConnectionWriter.close() # not resetting self.stored_traffic, maybe there is something inside - Log.debug("Client resetted main attributes") + Log.debug("Client resettled main attributes") try: - #init connection + # init connection self.ConnectionReader, self.ConnectionWriter = await asyncio.open_connection(host=self.host, port=self.port) - Log.debug("Client successfull create connection Reader/Writer pair") + Log.debug("Client successful create connection Reader/Writer pair") - #login + # login await sendPass(self) await sendNick(self) - #start listen - asyncio.ensure_future( trafficQuery(self) ) - Log.debug("Client sended base data, continue to listen for response...") - await self.listen() # <- that processess stuff + # start listen + asyncio.ensure_future(trafficQuery(self)) + Log.debug("Client sent base data, continue to listen for response...") + await self.listen() # <- that processes stuff except InvalidAuth: Log.error("Invalid Auth for osu!, please check `token` and `nickname`, not trying to reconnect") @@ -196,7 +192,7 @@ async def main(self) -> None: await self.onError(E) continue - except KeyboardInterrupt : + except KeyboardInterrupt: self.stop() continue @@ -209,16 +205,16 @@ async def main(self) -> None: async def listen(self): - #listen to osu + # listen to osu while self.running: Log.debug("Client awaiting response...") payload:bytes = await self.ConnectionReader.readline() Log.debug(f"Client received {len(payload)} bytes of data.") - asyncio.ensure_future( self.onRaw(payload) ) + asyncio.ensure_future(self.onRaw(payload)) payload:str = payload.decode('UTF-8').strip('\n').strip('\r') - #just to be sure + # just to be sure if payload in ["", " ", None] or not payload: if self.auth_success: raise EmptyPayload() @@ -232,45 +228,123 @@ async def listen(self): # check if the content is known garbage garbage:bool = await garbageDetector(self, payload) if garbage: - Log.debug("Client got garbare response, launching: Client.onGarbage") - asyncio.ensure_future( self.onGarbage(payload) ) + Log.debug("Client got garbage response, launching: Client.onGarbage") + asyncio.ensure_future(self.onGarbage(payload)) continue # check if there is something usefully we know processed:bool = await mainEventDetector(self, payload) if not processed: Log.debug("Client got unknown response, launching: Client.onUnknown") - asyncio.ensure_future( self.onUnknown(payload) ) + asyncio.ensure_future(self.onUnknown(payload)) continue async def sendContent(self, content:bytes or str, ignore_limit:bool=False) -> None: """ used to send content of any type to osu - pretty much all content should be sended via a other function like, sendMessage, sendPM or whatever + pretty much all content should be sent via a other function like, sendMessage, sendPM or whatever else that chance that the server understands what you want is near 0 """ if type(content) != bytes: content = bytes(content, 'UTF-8') if (self.traffic <= self.request_limit) or ignore_limit: - asyncio.ensure_future( addTraffic(self) ) - asyncio.ensure_future( self.onSend(content) ) + asyncio.ensure_future(addTraffic(self)) + asyncio.ensure_future(self.onSend(content)) Log.debug(f"Client sending {len(content)} bytes of content to the ConnectionWriter") - self.ConnectionWriter.write( content ) + self.ConnectionWriter.write(content) else: - asyncio.ensure_future( self.onLimit(content) ) - self.stored_traffic.append( content ) + asyncio.ensure_future(self.onLimit(content)) + self.stored_traffic.append(content) # commands - from ..Utils.commands import sendMessage, sendPM, joinChannel, partChannel + async def sendMessage(self, Chan:Union[Channel, str], content: str): + """ + This will send the content/message to a channel. (If you are not timed out, banned or otherwise, that not my fault duh) + 1st arg, `Chan` is the destination, provide a `Channel` object or a string like "osu", where you want to send your 2nd arg `content`. + + All IRC Channel-names start with a '#' you don't have to provide this, we will handle everything. ("#osu" == "osu") + For sending messages to a User (PM) use sendPM() + """ + if not content: + raise AttributeError("Can't send empty content") + + if isinstance(Chan, Channel): + destination: str = Chan.name + elif isinstance(Chan, User): + raise ValueError(f"sendMessage() is meant for channels only, please use sendPM() for messages to a user") + else: + destination: str = str(Chan) + + destination = destination.lower().strip('#').strip(' ') + Log.debug(f"Sending: PRIVMSG #{destination} - {content[:50]}") + await self.sendContent(f"PRIVMSG #{destination} :{content}\r\n") + + async def sendPM(self, Us:Union[User, str], content: str): + """ + This will send the content/message to a user. (If you are not blocked or otherwise) + 1st arg, `Us` is the destination, provide a `User` object or a string like "The_CJ", where you want to send your 2nd arg `content`. + + For sending messages to a channel use sendMessage() + """ + if not content: + raise AttributeError("Can't send empty content") + + if isinstance(Us, User): + destination: str = Us.name + elif isinstance(Us, Channel): + raise ValueError(f"sendPM() is meant for users only, please use sendMessage() for messages to a channel") + else: + destination: str = str(Us) + + destination = destination.lower().strip('#').strip(' ') + Log.debug(f"Sending: PRIVMSG {destination} - {content[:50]}") + await self.sendContent(f"PRIVMSG {destination} :{content}\r\n") + + async def joinChannel(self, Chan:Union[Channel, str]): + """ + Joining a channel allows the client to receive messages from this channel. + `Chan` is the destination, provide a `Channel` object or a string like "osu" or "#lobby" + + All IRC Channel-names start with a '#' you don't have to provide this, we will handle everything. ("#osu" == "osu") + """ + if isinstance(Chan, Channel): + destination: str = Chan.name + elif isinstance(Chan, User): + raise ValueError(f"you can not join a user, just start PM-ing, duh") + else: + destination: str = str(Chan) + + destination = destination.lower().strip('#') + Log.debug(f"Sending: JOIN #{destination}") + await self.sendContent(f"JOIN #{destination}\r\n") + + async def partChannel(self, Chan:Union[Channel, str]): + """ + Parting a channel disables receiving messages from this channel. + `Chan` may is a `Channel` object or a string like "osu" or "#lobby" + + All IRC Channel-names start with a '#' you don't have to provide this, we will handle everything. ("#osu" == "osu") + """ + if isinstance(Chan, Channel): + destination: str = Chan.name + elif isinstance(Chan, User): + raise ValueError(f"you can not part a user, its not a channel, duh") + else: + destination: str = str(Chan) + + destination = destination.lower().strip('#') + Log.debug(f"Sending: PART #{destination}") + await self.sendContent(f"PART #{destination}\r\n") - #events - async def onError(self, Ex:Exception) -> None: + # events + # noinspection PyMethodMayBeStatic + async def onError(self, Ex:BaseException) -> None: """ called every time something goes wrong """ - print(Ex) + Log.error(Ex) traceback.print_exc() async def onLimit(self, payload:bytes) -> None: diff --git a/osu_irc/Utils/commands.py b/osu_irc/Utils/commands.py deleted file mode 100644 index add1736..0000000 --- a/osu_irc/Utils/commands.py +++ /dev/null @@ -1,88 +0,0 @@ -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ..Classes.client import Client - -from ..Classes.channel import Channel -from ..Classes.user import User - -import logging -Log:logging.Logger = logging.getLogger("osu_irc") - -async def sendMessage(cls:"Client", Chan:Channel or str, content:str): - """ - This will send the content/message to a channel. (If you are not timed out, banned or otherwise, that not my fault duh) - 1st arg, `Chan` is the destination, provide a `Channel` object or a string like "osu", where you want to send your 2nd arg `content`. - - All IRC Channel-names start with a '#' you don't have to provide this, we will handle everything. ("#osu" == "osu") - For sending messages to a User (PM) use sendPM() - """ - if not content: - raise AttributeError("Can't send empty content") - - if isinstance(Chan, Channel): - destination:str = Chan.name - elif isinstance(Chan, User): - raise ValueError(f"sendMessage() is meant for channels only, please use sendPM() for messages to a user") - else: - destination:str = str(Chan) - - destination = destination.lower().strip('#').strip(' ') - Log.debug(f"Sending: PRIVMSG #{destination} - {content[:50]}") - await cls.sendContent( f"PRIVMSG #{destination} :{content}\r\n" ) - -async def sendPM(cls:"Client", Us:User or str, content:str): - """ - This will send the content/message to a user. (If you are not blocked or otherwise) - 1st arg, `Us` is the destination, provide a `User` object or a string like "The_CJ", where you want to send your 2nd arg `content`. - - For sending messages to a channel use sendMessage() - """ - if not content: - raise AttributeError("Can't send empty content") - - if isinstance(Us, User): - destination:str = Us.name - elif isinstance(Us, Channel): - raise ValueError(f"sendPM() is meant for users only, please use sendMessage() for messages to a channel") - else: - destination:str = str(Us) - - destination = destination.lower().strip('#').strip(' ') - Log.debug(f"Sending: PRIVMSG {destination} - {content[:50]}") - await cls.sendContent( f"PRIVMSG {destination} :{content}\r\n" ) - -async def joinChannel(cls:"Client", Chan:Channel or str): - """ - Joining a channel allows the client to receive messages from this channel. - `Chan` is the destination, provide a `Channel` object or a string like "osu" or "#lobby" - - All IRC Channel-names start with a '#' you don't have to provide this, we will handle everything. ("#osu" == "osu") - """ - if isinstance(Chan, Channel): - destination:str = Chan.name - elif isinstance(Chan, User): - raise ValueError(f"you can not join a user, just start PM-ing, duh") - else: - destination:str = str(Chan) - - destination = destination.lower().strip('#') - Log.debug(f"Sending: JOIN #{destination}") - await cls.sendContent( f"JOIN #{destination}\r\n" ) - -async def partChannel(cls:"Client", Chan:Channel or str): - """ - Parting a channel disables receiving messages from this channel. - `Chan` may is a `Channel` object or a string like "osu" or "#lobby" - - All IRC Channel-names start with a '#' you don't have to provide this, we will handle everything. ("#osu" == "osu") - """ - if isinstance(Chan, Channel): - destination:str = Chan.name - elif isinstance(Chan, User): - raise ValueError(f"you can not part a user, its not a channel, duh") - else: - destination:str = str(Chan) - - destination = destination.lower().strip('#') - Log.debug(f"Sending: PART #{destination}") - await cls.sendContent( f"PART #{destination}\r\n" ) diff --git a/osu_irc/__init__.py b/osu_irc/__init__.py index 9a2d221..83ed0c6 100644 --- a/osu_irc/__init__.py +++ b/osu_irc/__init__.py @@ -8,7 +8,7 @@ Simple to use IRC connection for osu optimized for the PhaazeOS project but usable to any purpose -:copyright: (c) 2018-2020 The_CJ +:copyright: (c) 2018-2021 The_CJ :license: MIT License - Inspired by the code of Rapptz's Discord library From d56f254a2e7c1056998124e55ec4110c26d29381 Mon Sep 17 00:00:00 2001 From: The_CJ Date: Thu, 14 Jan 2021 23:38:37 +0100 Subject: [PATCH 3/4] reworked all parts for ide and typing --- osu_irc/Classes/channel.py | 27 +++++++++--------- osu_irc/Classes/message.py | 29 ++++++++++--------- osu_irc/Classes/undefined.py | 7 +++-- osu_irc/Classes/user.py | 19 ++++++------- osu_irc/Utils/cmd.py | 8 +++--- osu_irc/Utils/detector.py | 54 ++++++++++++++++++------------------ osu_irc/Utils/errors.py | 8 +++--- osu_irc/Utils/handler.py | 31 ++++++++++----------- osu_irc/Utils/traffic.py | 8 +++--- 9 files changed, 95 insertions(+), 96 deletions(-) diff --git a/osu_irc/Classes/channel.py b/osu_irc/Classes/channel.py index 081e077..a47b6b9 100644 --- a/osu_irc/Classes/channel.py +++ b/osu_irc/Classes/channel.py @@ -1,11 +1,11 @@ -from typing import Dict, NewType, Set, List, TYPE_CHECKING -UserName = NewType("UserName", str) +from typing import Dict, NewType, Set, List, TYPE_CHECKING, Union, Optional if TYPE_CHECKING: from .client import Client as OsuClient from .user import User from .stores import UserStore -from .undefined import UNDEFINED + +UserName = NewType("UserName", str) class Channel(object): """ @@ -20,18 +20,18 @@ def __repr__(self): def __str__(self): return self.name or "" - def __init__(self, *x, **xx): + def __init__(self, *_, **__): # props - self._name:str = UNDEFINED - self._chatters:Dict[UserName, User] = UserStore() + self._name:Optional[str] = None + self._chatters:Dict[Union[UserName, str], User] = UserStore() # other self.motd:str = "" # because of user stores, we save the name of the different user types in a channel - # Update: sooo... em, yeah, in osu there are no IRC- Owner (~), Operator (&) or Helper (%) - # the Owner whould be BanchBot, i guess, but technicly these 3 could be taken out, but i don't give a fuck. REEEEEE + # Update: so... em, yeah, in osu there are no IRC- Owner (~), Operator (&) or Helper (%) + # the Owner would be BanchBot, i guess, but technically these 3 could be taken out, but i don't give a fuck. REEEEEE self._owner:Set[str] = set() # ~ self._admin:Set[str] = set() # @ self._operator:Set[str] = set() # & @@ -39,10 +39,10 @@ def __init__(self, *x, **xx): self._voiced:Set[str] = set() # + # other than twitch, this class doesn't has a build function, - # because it should only be created by a handler (probl. mostly handleJoin) + # because it should only be created by a handler (probably. mostly handleJoin) def compact(self) -> dict: - d:dict = {} + d:dict = dict() d["name"] = self.name d["motd"] = self.motd d["chatters"] = self.chatters @@ -72,7 +72,7 @@ def getViewer(self, **search:dict) -> User or None: def getOwners(self) -> List[User]: """ get all users that are owner (~) of the current channel. - (NOTE: i never find any in thie result... but maybe there are owner... i mean BanchoBot Should be one, right?) + (NOTE: i never find any in the result... but maybe there are owner... i mean BanchoBot Should be one, right?) """ owners:List[User] = [] @@ -158,10 +158,11 @@ async def sendMessage(self, cls:"OsuClient", content:str) -> None: # props @property - def chatters(self) -> Dict[UserName, User]: + def chatters(self) -> Dict[Union[UserName, str], User]: return self._chatters + @property - def users(self) -> Dict[UserName, User]: + def users(self) -> Dict[Union[UserName, str], User]: return self.chatters @property diff --git a/osu_irc/Classes/message.py b/osu_irc/Classes/message.py index 9a16939..5266e04 100644 --- a/osu_irc/Classes/message.py +++ b/osu_irc/Classes/message.py @@ -1,11 +1,10 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: from .client import Client as OsuClient from .user import User as OsuUser from .channel import Channel as OsuChannel import re -from .undefined import UNDEFINED from ..Utils.regex import ( ReUserName, ReRoomName, @@ -28,26 +27,26 @@ class Message(object): ``` """ def __repr__(self): - return f"<{self.__class__.__name__} channel='{self.channel_name}' user='{self.user_name}'>" + return f"<{self.__class__.__name__} channel='{self.room_name}' user='{self.user_name}'>" def __str__(self): return self.content - def __init__(self, raw:str or None): + def __init__(self, raw:Optional[str]): # props - self._user_name:str = UNDEFINED - self._room_name:str = UNDEFINED - self._content:str = UNDEFINED + self._user_name:Optional[str] = None + self._room_name:Optional[str] = None + self._content:Optional[str] = None # classes - self.Author:"OsuUser" = None - self.Channel:"OsuChannel" = None + self.Author:Optional["OsuUser"] = None + self.Channel:["OsuChannel"] = None # other self.is_action:bool = False self._channel_type:int = 0 - if raw != None: + if raw: try: self.messageBuild(raw) @@ -56,7 +55,7 @@ def __init__(self, raw:str or None): # utils def compact(self) -> dict: - d:dict = {} + d:dict = dict() d["user_name"] = self.user_name d["room_name"] = self.room_name d["content"] = self.content @@ -70,17 +69,17 @@ def messageBuild(self, raw:str) -> None: # _user_name search = re.search(ReUserName, raw) - if search != None: + if search: self._user_name = search.group(1) # _room_name search = re.search(ReRoomName, raw) - if search != None: + if search: self._room_name = search.group(1) # _content search = re.search(ReContent, raw) - if search != None: + if search: self._content = search.group(1) self.checkType() @@ -103,7 +102,7 @@ def checkAction(self) -> None: action means its a /me message. If it is, change content and set is_action true """ search:re.Match = re.search(ReAction, self.content) - if search != None: + if search: self.is_action = True self._content = search.group(1) diff --git a/osu_irc/Classes/undefined.py b/osu_irc/Classes/undefined.py index fe99a78..43d5fac 100644 --- a/osu_irc/Classes/undefined.py +++ b/osu_irc/Classes/undefined.py @@ -1,7 +1,7 @@ -class Undefined(): +class Undefined(object): """ This class is never (un)equal, bigger, smaller und else to everything, its nothing - this is btw, stolen from the Phaazebot Projekt LUL + this is btw, stolen from the Phaazebot Project LUL """ def __init__(self): pass @@ -31,6 +31,7 @@ def __bool__(self): return False # if def __iter__(self): return self def __next__(self): raise StopIteration -# a constant class of undefined... so you dont need to generate new objects... + +# a constant class of undefined... so you don't need to generate new objects... # or so? is this saving resources... idk UNDEFINED:Undefined = Undefined() diff --git a/osu_irc/Classes/user.py b/osu_irc/Classes/user.py index 68d6c7f..58d7371 100644 --- a/osu_irc/Classes/user.py +++ b/osu_irc/Classes/user.py @@ -1,20 +1,19 @@ -from typing import TYPE_CHECKING, Set, List +from typing import TYPE_CHECKING, Set, List, Optional if TYPE_CHECKING: from .client import Client as OsuClient from .channel import Channel as OsuChannel import re -from .undefined import UNDEFINED from ..Utils.regex import ReUserName, ReRoomName class User(object): """ This class represents a osu user, - the same user object meight be found in multiple Channel.chatters dict's. + the same user object might be found in multiple Channel.chatters dict's. NOTE: all users from all channels can be found ion Client.users NOTE 2: also there are no ID's because why should it, REEEEEEE (i really like ID's... :c) - NOTE 3*: sooo names with a space (' ') are replaced with a ('_') in IRC, yeah... keep that in mind, nothing i can do + NOTE 3*: so names with a space (' ') are replaced with a ('_') in IRC, yeah... keep that in mind, nothing i can do ``` * Note 3: @@ -33,12 +32,12 @@ def __str__(self): return self.name or "" def __init__(self, raw:str or None): - self._name:str = UNDEFINED - self._generated_via_channel:str = UNDEFINED + self._name:Optional[str] = None + self._generated_via_channel:Optional[str] = None self.found_in:Set[str] = set() - if raw != None: + if raw: try: self.userBuild(raw) @@ -46,7 +45,7 @@ def __init__(self, raw:str or None): raise AttributeError(raw) def compact(self) -> dict: - d:dict = {} + d:dict = dict() d["name"] = self.name d["found_in"] = self.found_in return d @@ -63,12 +62,12 @@ def userBuild(self, raw:str) -> None: # _name search = re.search(ReUserName, raw) - if search != None: + if search: self._name = search.group(1) # _generated_via_channel search = re.search(ReRoomName, raw) - if search != None: + if search: self._generated_via_channel = search.group(1) # funcs diff --git a/osu_irc/Utils/cmd.py b/osu_irc/Utils/cmd.py index 8292754..ad47d50 100644 --- a/osu_irc/Utils/cmd.py +++ b/osu_irc/Utils/cmd.py @@ -1,12 +1,12 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ..Classes.client import Client + from ..Classes.client import Client async def sendPong(cls:"Client"): - await cls.sendContent( "PONG :cho.ppy.sh\r\n", ignore_limit=True ) + await cls.sendContent("PONG :cho.ppy.sh\r\n", ignore_limit=True) async def sendNick(cls:"Client"): - await cls.sendContent( f"NICK {cls.nickname}\r\n", ignore_limit=True ) + await cls.sendContent(f"NICK {cls.nickname}\r\n", ignore_limit=True) async def sendPass(cls:"Client"): - await cls.sendContent( f"PASS {cls.token}\r\n", ignore_limit=True ) + await cls.sendContent(f"PASS {cls.token}\r\n", ignore_limit=True) diff --git a/osu_irc/Utils/detector.py b/osu_irc/Utils/detector.py index 7fab78d..926f9b7 100644 --- a/osu_irc/Utils/detector.py +++ b/osu_irc/Utils/detector.py @@ -19,68 +19,68 @@ ReMOTD, ReMode ) -async def garbageDetector(cls:"Client", payload:str) -> bool: +async def garbageDetector(_cls:"Client", payload:str) -> bool: """ - This detector is suppost to catch all known patterns that are also known as trash. + This detector is suppose to catch all known patterns that are also known as trash. Like this the very beginning where bancho boat is drawn in ASCII """ - if re.match(ReGarbage, payload) != None: + if re.match(ReGarbage, payload) is not None: return True return False async def mainEventDetector(cls:"Client", payload:str) -> bool: """ - This detector is suppost to catch all events we can somehow process, if not, give back False. + This detector is suppose to catch all events we can somehow process, if not, give back False. If that happens the Client `cls` makes additional handling """ - #response to PING - if re.match(RePing, payload) != None: + # response to PING + if re.match(RePing, payload) is not None: cls.last_ping = time.time() await sendPong(cls) return True - # handels events: onJoin - if re.match(ReJoin, payload) != None: + # handles events: onJoin + if re.match(ReJoin, payload) is not None: return await handleJoin(cls, payload) - # handels events: onPart - if re.match(RePart, payload) != None: + # handles events: onPart + if re.match(RePart, payload) is not None: return await handlePart(cls, payload) - # handels events: onQuit - if re.match(ReQuit, payload) != None: + # handles events: onQuit + if re.match(ReQuit, payload) is not None: return await handleQuit(cls, payload) - # handels events: onMessage - if re.match(RePrivMessage, payload) != None: + # handles events: onMessage + if re.match(RePrivMessage, payload) is not None: return await handlePrivMessage(cls, payload) - # handels events: None - if re.match(ReUserList, payload) != None: + # handles events: None + if re.match(ReUserList, payload) is not None: return await handleUserList(cls, payload) - # handels events: None - if re.match(ReMOTD, payload) != None: + # handles events: None + if re.match(ReMOTD, payload) is not None: return await handleMOTDEvent(cls, payload) - # handels events: None - if re.match(ReMode, payload) != None: + # handles events: None + if re.match(ReMode, payload) is not None: return await handleMode(cls, payload) - # handels events: onReady, onReconnect - if re.match(ReOnReady, payload) != None: + # handles events: onReady, onReconnect + if re.match(ReOnReady, payload) is not None: if cls.auth_success: - #means we got a reconnect - asyncio.ensure_future( cls.onReconnect() ) + # means we got a reconnect + asyncio.ensure_future(cls.onReconnect()) cls.auth_success = True - asyncio.ensure_future( cls.onReady() ) + asyncio.ensure_future(cls.onReady()) return True # wrong_auth if not cls.auth_success: - if re.match(ReWrongAuth, payload) != None: - raise InvalidAuth( payload ) + if re.match(ReWrongAuth, payload) is not None: + raise InvalidAuth(payload) return False diff --git a/osu_irc/Utils/errors.py b/osu_irc/Utils/errors.py index b7c59ad..e5ef455 100644 --- a/osu_irc/Utils/errors.py +++ b/osu_irc/Utils/errors.py @@ -1,7 +1,7 @@ class InvalidAuth(Exception): """ Raised when osu gives us an error login response. - Don't get confused with `InvalidCredentials`, where osu don't even talkes with us. + Don't get confused with `InvalidCredentials`, where osu don't even takes with us. """ class InvalidCredentials(Exception): @@ -13,11 +13,11 @@ class InvalidCredentials(Exception): class PingTimeout(Exception): """ Raised when osu didn't responded with a PING in some time. - Most likly means something on osu's site is broken. + Most likely means something on osu's site is broken. """ class EmptyPayload(Exception): """ - Raised when osu sended empty data. - Most likly means we lost connection without getting a event for it. + Raised when osu sent empty data. + Most likely means we lost connection without getting a event for it. """ diff --git a/osu_irc/Utils/handler.py b/osu_irc/Utils/handler.py index f84041d..32d875e 100644 --- a/osu_irc/Utils/handler.py +++ b/osu_irc/Utils/handler.py @@ -1,10 +1,8 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ..Classes.client import Client + from ..Classes.client import Client import logging -Log:logging.Logger = logging.getLogger("osu_irc") - import re import asyncio from ..Classes.message import Message @@ -14,6 +12,7 @@ ReUserListData, ReQuit, ReMOTDInfo, ReModeInfo ) +Log:logging.Logger = logging.getLogger("osu_irc") async def handleJoin(cls:"Client", payload:str) -> bool: """ @@ -55,7 +54,7 @@ async def handleJoin(cls:"Client", payload:str) -> bool: KnownUser.found_in.add(Chan.name) Log.debug(f"Client launching: Client.onMemberJoin: {str(vars(Chan))} {str(vars(KnownUser))}") - asyncio.ensure_future( cls.onMemberJoin(Chan, KnownUser) ) + asyncio.ensure_future(cls.onMemberJoin(Chan, KnownUser)) return True async def handlePart(cls:"Client", payload:str) -> bool: @@ -103,7 +102,7 @@ async def handlePart(cls:"Client", payload:str) -> bool: cls.users.pop(KnownUser.name, None) Log.debug(f"Client launching: Client.onMemberPart: {str(vars(Chan))} {str(vars(KnownUser))}") - asyncio.ensure_future( cls.onMemberPart(Chan, KnownUser) ) + asyncio.ensure_future(cls.onMemberPart(Chan, KnownUser)) return True async def handleQuit(cls:"Client", payload:str) -> bool: @@ -122,7 +121,7 @@ async def handleQuit(cls:"Client", payload:str) -> bool: # name and reason search = re.search(ReQuit, payload) - if search == None: + if search is None: # in case we don't find anything, just ignore it, like you should with all problems in life :3 return True @@ -145,11 +144,11 @@ async def handleQuit(cls:"Client", payload:str) -> bool: Chan._helper.discard(QuitingUser.name) Chan._voiced.discard(QuitingUser.name) - # and also remove it from clients user storage, which then should delete user object completly from memory + # and also remove it from clients user storage, which then should delete user object completely from memory cls.users.pop(QuitingUser.name, None) Log.debug(f"Client launching: Client.onMemberQuit: {str(vars(QuitingUser))} {reason}") - asyncio.ensure_future( cls.onMemberQuit(QuitingUser, reason) ) + asyncio.ensure_future(cls.onMemberQuit(QuitingUser, reason)) return True async def handleUserList(cls:"Client", payload:str) -> bool: @@ -163,7 +162,7 @@ async def handleUserList(cls:"Client", payload:str) -> bool: # e.g.: :cho.ppy.sh 353 Phaazebot = #osu :The_CJ SomeoneElse +SomeoneViaIRC @SomeModerator search:re.Match = re.search(ReUserListData, payload) - if search != None: + if search: room_name:str = search.group(1) ChannelToFill:Channel = cls.channels.get(room_name, None) if not ChannelToFill: return True @@ -171,7 +170,7 @@ async def handleUserList(cls:"Client", payload:str) -> bool: full_user_list:str = search.group(2) for user_name in full_user_list.split(' '): - # for whatever reason, osu! likes giving empty cahrs at the end... thanks i guess? + # for whatever reason, osu! likes giving empty chars at the end... thanks i guess? if user_name.lower() in ['', ' ', cls.nickname.lower()]: continue # check user type and change name, also add to usertype set @@ -214,7 +213,7 @@ async def handlePrivMessage(cls:"Client", payload:str) -> bool: # generate message Msg:Message = Message(payload) - #get Channel + # get Channel Chan:Channel = cls.channels.get(Msg.room_name, None) if Chan: Msg.Channel = Chan @@ -241,14 +240,14 @@ async def handlePrivMessage(cls:"Client", payload:str) -> bool: Msg.Author = Alternative Log.debug(f"Client launching: Client.onMemberJoin: {str(vars(Chan))} {str(vars(Alternative))}") - asyncio.ensure_future( cls.onMemberJoin(Chan, Alternative) ) + asyncio.ensure_future(cls.onMemberJoin(Chan, Alternative)) - # safty step, add author to channels chatter list, and channel to user + # safety step, add author to channels chatter list, and channel to user Msg.Channel.chatters[Msg.Author.name] = Msg.Author Msg.Author.found_in.add(Msg.room_name) Log.debug(f"Client launching: Client.onMessage: {str(vars(Msg))}") - asyncio.ensure_future( cls.onMessage(Msg) ) + asyncio.ensure_future(cls.onMessage(Msg)) return True async def handleMOTDEvent(cls:"Client", payload:str) -> bool: @@ -267,7 +266,7 @@ async def handleMOTDEvent(cls:"Client", payload:str) -> bool: motd:str = Data.group(2) if not motd: return False - #get Channel + # get Channel Chan:Channel = cls.channels.get(room_name, None) if not Chan: return False @@ -296,7 +295,7 @@ async def handleMode(cls:"Client", payload:str) -> bool: if not Chan: return False if operation == 'o': - # well... basicly there should be added to operater, but osu only has admins, because on member list requests we get a: + # well... basically there should be added to operator, but osu only has admins, because on member list requests we get a: # @username and not a &username, so it's an admin... at least for me, if im wrong, well fuck Chan._admin.add(user_name) if state == '+' else Chan._admin.discard(user_name) return True diff --git a/osu_irc/Utils/traffic.py b/osu_irc/Utils/traffic.py index bba381a..b0bf25f 100644 --- a/osu_irc/Utils/traffic.py +++ b/osu_irc/Utils/traffic.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ..Classes.client import Client + from ..Classes.client import Client import asyncio @@ -15,13 +15,13 @@ async def addTraffic(cls:"Client"): async def trafficQuery(cls:"Client"): """ - get started on Cient.start(), - a coro thats takes all requests that would be over the limit + get started on Client.start(), + a coro that's takes all requests that would be over the limit and send them later """ while cls.running and cls.query_running: if cls.traffic <= (cls.request_limit-1) and len(cls.stored_traffic) > 0: req = cls.stored_traffic.pop(0) - await cls.sendContent( req ) + await cls.sendContent(req) else: await asyncio.sleep(0.05) From 1902bfa2eeebcd75dae5555ef66c474785acf9c5 Mon Sep 17 00:00:00 2001 From: The_CJ Date: Thu, 14 Jan 2021 23:39:33 +0100 Subject: [PATCH 4/4] fixes example --- README.md | 2 +- osu_irc/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 34ba133..d0fbb78 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ import osu_irc class MyBot(osu_irc.Client): async def onReady(self): - self.joinChannel("#osu") + await self.joinChannel("#osu") #do something async def onMessage(self, message): diff --git a/osu_irc/__init__.py b/osu_irc/__init__.py index 83ed0c6..32e2a8e 100644 --- a/osu_irc/__init__.py +++ b/osu_irc/__init__.py @@ -19,7 +19,7 @@ __author__ = 'The_CJ' __license__ = 'MIT' __copyright__ = 'Copyright 2018-2021 The_CJ' -__version__ = "1.1.1" +__version__ = "1.2.0" from .Classes.channel import Channel from .Classes.client import Client