diff --git a/yaboli/TestBot.py b/TestBot.py similarity index 92% rename from yaboli/TestBot.py rename to TestBot.py index 5ef772f..f774af0 100644 --- a/yaboli/TestBot.py +++ b/TestBot.py @@ -1,12 +1,11 @@ import asyncio -#from controller import Bot -from controller import Controller -from utils import * +import yaboli +from yaboli.utils import * #class TestBot(Bot): -class TestBot(Controller): +class TestBot(yaboli.Controller): def __init__(self, roomname): super().__init__(roomname) diff --git a/yaboli/__init__.py b/yaboli/__init__.py index 8aed845..3abbecc 100644 --- a/yaboli/__init__.py +++ b/yaboli/__init__.py @@ -1,10 +1,6 @@ -from .bot import Bot -from .botmanager import BotManager -from .callbacks import Callbacks -from .connection import Connection -from .exceptions import * -from .session import Session -from .message import Message -from .sessions import Sessions -from .messages import Messages -from .room import Room +from .connection import * +from .room import * +from .controller import * +from .utils import * + +__all__ = connection.__all__ + room.__all__ + controller.__all__ + utils.__all__ diff --git a/yaboli/asynciotest.py b/yaboli/asynciotest.py deleted file mode 100644 index 7d619a0..0000000 --- a/yaboli/asynciotest.py +++ /dev/null @@ -1,25 +0,0 @@ -import asyncio - -async def create(): - await asyncio.sleep(3.0) - print("(1) create file") - -async def write(): - await asyncio.sleep(1.0) - print("(2) write into file") - -async def close(): - print("(3) close file") - -async def test(): - await create() - await write() - await close() - await asyncio.sleep(2.0) - loop.stop() - -loop = asyncio.get_event_loop() -asyncio.ensure_future(test()) -loop.run_forever() -print("Pending tasks at exit: %s" % asyncio.Task.all_tasks(loop)) -loop.close() diff --git a/yaboli/bot.py b/yaboli/bot.py deleted file mode 100644 index 68ee2f8..0000000 --- a/yaboli/bot.py +++ /dev/null @@ -1,600 +0,0 @@ -import time - -from . import callbacks -from . import exceptions -from . import room - -class Bot(): - """ - Empty bot class that can be built upon. - Takes care of extended botrulez. - """ - - def __init__(self, roomname, nick="yaboli", password=None, manager=None, - created_in=None, created_by=None): - """ - roomname - name of the room to connect to - nick - nick to assume, None -> no nick - password - room password (in case the room is private) - created_in - room the bot was created in - created_by - nick of the person the bot was created by - """ - - self.start_time = time.time() - - self.created_by = created_by - self.created_in = created_in - - self.manager = manager - - # modify/customize this in your __init__() function (or anywhere else you want, for that matter) - self.bot_description = ("This bot complies with the botrulez™ (https://github.com/jedevc/botrulez),\n" - "plus a few extra commands.") - - self.helptexts = {} - self.detailed_helptexts = {} - - self.room = room.Room(roomname, nick=nick, password=password) - self.room.add_callback("message", self.on_message) - - self.commands = callbacks.Callbacks() - self.bot_specific_commands = [] - - self.add_command("clone", self.clone_command, "Clone this bot to another room.", # possibly add option to set nick? - ("!clone @bot [ [ --pw= ] ]\n" - " : the name of the room to clone the bot to\n" - "--pw : the room's password\n\n" - "Clone this bot to the room specified.\n" - "If the target room is passworded, you can use the --pw option to set\n" - "a password for the bot to use.\n" - "If no room is specified, this will use the current room and password."), - bot_specific=False) - - self.add_command("help", self.help_command, "Show help information about the bot.", - ("!help @bot [ -s | -c | ]\n" - "-s : general syntax help\n" - "-c : only list the commands\n" - " : any command from !help @bot -c\n\n" - "Shows detailed help for a command if you specify a command name.\n" - "Shows a list of commands and short description if no arguments are given.")) - - self.add_command("kill", self.kill_command, "Kill (stop) the bot.", - ("!kill @bot [ -r ]\n" - "-r : restart the bot (will change the id)\n\n" - "The bot disconnects from the room and stops.")) - - self.add_command("ping", self.ping_command, "Replies 'Pong!'.", - ("!ping @bot\n\n" - "This command was originally used to help distinguish bots from\n" - "people. Since the Great UI Change, this is no longer necessary as\n" - "bots and people are displayed separately.")) - - self.add_command("restart", self.restart_command, "Restart the bot (shorthand for !kill @bot -r).", - ("!restart @bot\n\n" - "Restart the bot.\n" - "Short for !kill @bot -r")) - - self.add_command("send", self.send_command, "Send the bot to another room.", - ("!send @bot [ --pw= ]\n" - "--pw : the room's password\n\n" - "Sends this bot to the room specified. If the target room is passworded,\n" - "you can use the --pw option to set a password for the bot to use.")) - - self.add_command("uptime", self.uptime_command, "Show bot uptime since last (re-)start.", - ("!uptime @bot [ -i s]\n" - "-i : show more detailed information\n\n" - "Shows the bot's uptime since the last start or restart.\n" - "Shows additional information (i.e. id) if the -i flag is set.")) - - - self.add_command("show", self.show_command, detailed_helptext="You've found a hidden command! :)") - - self.room.launch() - - def stop(self): - """ - stop() -> None - - Kill this bot. - """ - - self.room.stop() - - def add_command(self, command, function, helptext=None, detailed_helptext=None, - bot_specific=True): - """ - add_command(command, function, helptext, detailed_helptext, bot_specific) -> None - - Subscribe a function to a command and add a help text. - If no help text is provided, the command won't be displayed by the !help command. - This overwrites any previously added command. - - You can "hide" commands by specifying only the detailed helptext, - or no helptext at all. - - If the command is not bot specific, no id has to be specified if there are multiple bots - with the same nick in a room. - """ - - command = command.lower() - - self.commands.remove(command) - self.commands.add(command, function) - - if helptext and not command in self.helptexts: - self.helptexts[command] = helptext - elif not helptext and command in self.helptexts: - self.helptexts.pop(command) - - if detailed_helptext and not command in self.detailed_helptexts: - self.detailed_helptexts[command] = detailed_helptext - elif not detailed_helptext and command in self.detailed_helptexts: - self.detailed_helptexts.pop(command) - - if bot_specific and not command in self.bot_specific_commands: - self.bot_specific_commands.append(command) - elif not bot_specific and command in self.bot_specific_commands: - self.bot_specific_commands.remove(command) - - def call_command(self, message): - """ - call_command(message) -> None - - Calls all functions subscribed to the command with the arguments supplied in the message. - Deals with the situation that multiple bots of the same type and nick are in the same room. - """ - - try: - command, bot_id, nick, arguments, flags, options = self.parse(message.content) - except exceptions.ParseMessageException: - return - else: - command = command.lower() - nick = self.room.mentionable(nick).lower() - - if not self.commands.exists(command): - return - - if not nick == self.mentionable().lower(): - return - - if bot_id is not None: # id specified - if self.manager.get(bot_id) == self: - self.commands.call(command, message, arguments, flags, options) - else: - return - - else: # no id specified - bots = self.manager.get_similar(self.roomname(), nick) - if self.manager.get_id(self) == min(bots): # only one bot should act - # either the bot is unique or the command is not bot-specific - if not command in self.bot_specific_commands or len(bots) == 1: - self.commands.call(command, message, arguments, flags, options) - - else: # user has to select a bot - msg = ("There are multiple bots with that nick in this room. To select one,\n" - "please specify its id (from the list below) as follows:\n" - "!{} @{} [your arguments...]\n").format(command, nick) - - for bot_id in sorted(bots): - bot = bots[bot_id] - msg += "\n{} - @{} ({})".format(bot_id, bot.mentionable(), bot.creation_info()) - - self.room.send_message(msg, parent=message.id) - - def roomname(self): - """ - roomname() -> roomname - - The room the bot is connected to. - """ - - return self.room.room - - def password(self): - """ - password() -> password - - The current room's password. - """ - - return self.room.password - - def nick(self): - """ - nick() -> nick - - The bot's full nick. - """ - - return self.room.nick - - def mentionable(self): - """ - mentionable() -> nick - - The bot's nick in a mentionable format. - """ - - return self.room.mentionable() - - def creation_info(self): - """ - creation_info() -> str - - Formatted info about the bot's creation - """ - - info = "created {}".format(self.format_date()) - - if self.created_by: - info += " by @{}".format(self.room.mentionable(self.created_by)) - - if self.created_in: - info += " in &{}".format(self.created_in) - - return info - - def format_date(self, seconds=None): - """ - format_date(seconds) -> str - - Format a time in epoch format to the format specified in self.date_format. - Defaults to self.start_time. - """ - - if seconds is None: - seconds = self.start_time - - return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(seconds)) - - def format_delta(self, delta=None): - """ - format_delta(delta) -> str - - Format a difference in seconds to the following format: - [- ][d ][h ][m ]s - Defaults to the current uptime if no delta is specified. - """ - - if not delta: - delta = time.time() - self.start_time - - delta = int(delta) - uptime = "" - - if delta < 0: - uptime += "- " - delta = -delta - - if delta >= 24*60*60: - uptime +="{}d ".format(delta//(24*60*60)) - delta %= 24*60*60 - - if delta >= 60*60: - uptime += "{}h ".format(delta//(60*60)) - delta %= 60*60 - - if delta >= 60: - uptime += "{}m ".format(delta//60) - delta %= 60 - - uptime += "{}s".format(delta) - - return uptime - - def parse_command(self, message): - """ - parse_command(message_content) -> command, bot_id, nick, argpart - - Parse the "!command[ bot_id] @botname[ argpart]" part of a command. - """ - - # command name (!command) - split = message.split(maxsplit=1) - - if len(split) < 2: - raise exceptions.ParseMessageException("Not enough arguments") - elif split[0][:1] != "!": - raise exceptions.ParseMessageException("Not a command") - - command = split[0][1:] - message = split[1] - split = message.split(maxsplit=1) - - # bot id - try: - bot_id = int(split[0]) - except ValueError: - bot_id = None - else: - if len(split) <= 1: - raise exceptions.ParseMessageException("No bot nick") - - message = split[1] - split = message.split(maxsplit=1) - - # bot nick (@mention) - if split[0][:1] != "@": - raise exceptions.ParseMessageException("No bot nick") - - nick = split[0][1:] - - # arguments to the command - if len(split) > 1: - argpart = split[1] - else: - argpart = None - - return command, bot_id, nick, argpart - - def parse_arguments(self, argstr): - """ - parse_arguments(argstr) -> arguments, flags, options - - Parse the argument part of a command. - """ - - argstr += " " # so the last argument will also be captured - - escaping = False - quot_marks = None - type_signs = 0 - option = None - word = "" - - arguments = [] - flags = "" - options = {} - - for char in argstr: - - # backslash-escaping - if escaping: - word += char - escaping = False - elif char == "\\": - escaping = True - - # quotation mark escaped strings - elif quot_marks: - if char == quot_marks: - quot_marks = None - else: - word += char - elif char in ['"', "'"]: - quot_marks = char - - # type signs - elif char == "-": - if type_signs < 2 and not word: - type_signs += 1 - else: - word += char - - # "=" in options - elif char == "=" and type_signs == 2 and word and not option: - option = word - word = "" - - # space - evaluate information collected so far - elif char == " ": - if word: - if type_signs == 0: # argument - arguments.append(word) - elif type_signs == 1: # flag(s) - for flag in word: - if not flag in flags: - flags += flag - elif type_signs == 2: # option - if option: - options[option] = word - else: - options[word] = True - - # reset all state variables - escaping = False - quot_marks = None - type_signs = 0 - option = None - word = "" - - # all other chars and situations - else: - word += char - - return arguments, flags, options - - def parse(self, message): - """ - parse(message_content) -> bool - - Parse a message. - """ - - command, bot_id, nick, argpart = self.parse_command(message) - - if argpart: - arguments, flags, options = self.parse_arguments(argpart) - else: - arguments = [] - flags = "" - options = {} - - return command, bot_id, nick, arguments, flags, options - - # ----- HANDLING OF EVENTS ----- - - def on_message(self, message): - """ - on_message(message) -> None - - Gets called when a message is received (see __init__). - If you want to add a command to your bot, consider using add_command instead of overwriting - this function. - """ - - self.call_command(message) - - # ----- COMMANDS ----- - - def clone_command(self, message, arguments, flags, options): - """ - clone_command(message, *arguments, flags, options) -> None - - Create a new bot. - """ - - if not arguments: - room = self.roomname() - password = self.room.password - else: - room = arguments[0] - - if room[:1] == "&": - room = room[1:] - - if "pw" in options and options["pw"] is not True: - password = options["pw"] - else: - password = None - - try: - bot = self.manager.create(room, password=password, created_in=self.roomname(), - created_by=message.sender.name) - except exceptions.CreateBotException: - self.room.send_message("Bot could not be cloned.", parent=message.id) - else: - self.room.send_message("Cloned @{} to &{}.".format(bot.mentionable(), room), - parent=message.id) - - def help_command(self, message, arguments, flags, options): - """ - help_command(message, *arguments, flags, options) -> None - - Show help about the bot. - """ - - if arguments: # detailed help for one command - command = arguments[0] - if command[:1] == "!": - command = command[1:] - - if command in self.detailed_helptexts: - msg = "Detailed help for !{}:\n".format(command) - msg += self.detailed_helptexts[command] - else: - msg = "No detailed help text found for !{}.".format(command) - if command in self.helptexts: - msg += "\n\n" + self.helptexts[command] - - elif "s" in flags: # detailed syntax help - msg = ("SYNTAX HELP PLACEHOLDER") - - else: # just list all commands - msg = "" - - if not "c" in flags: - msg += self.bot_description + "\n\n" - - msg += "This bot supports the following commands:" - for command in sorted(self.helptexts): - helptext = self.helptexts[command] - msg += "\n!{} - {}".format(command, helptext) - - if not "c" in flags: - msg += ("\n\nFor help on the command syntax, try: !help @{0} -s\n" - "For detailed help on a command, try: !help @{0} \n" - "(Hint: Most commands have extra functionality, which is listed in their detailed help.)") - msg = msg.format(self.mentionable()) - - self.room.send_message(msg, parent=message.id) - - def kill_command(self, message, arguments, flags, options): - """ - kill_command(message, *arguments, flags, options) -> None - - stop the bot. - """ - - if "r" in flags: - bot = self.manager.create(self.roomname()) - bot.created_by = self.created_by - bot.created_in = self.created_in - - self.room.send_message("/me exits.", message.id) - - self.manager.remove(self.manager.get_id(self)) - - def ping_command(self, message, arguments, flags, options): - """ - ping_command(message, *arguments, flags, options) -> None - - Send a "Pong!" reply on a !ping command. - """ - - self.room.send_message("Pong!", parent=message.id) - - def restart_command(self, message, arguments, flags, options): - """ - restart_command(message, *arguments, flags, options) -> None - - Restart the bot (shorthand for !kill @bot -r). - """ - - self.commands.call("kill", message, [], "r", {}) - - def send_command(self, message, arguments, flags, options): - """ - _command(message, *arguments, flags, options) -> None - - Send this bot to another room. - """ - - if not arguments: - return - else: - room = arguments[0] - - if room[:1] == "&": - room = room[1:] - - if "pw" in options and options["pw"] is not True: - password = options["pw"] - else: - password = None - - self.room.send_message("/me moves to &{}.".format(room), parent=message.id) - - self.room.change(room, password=password) - self.room.launch() - - def show_command(self, message, arguments, flags, options): - """ - show_command(message, arguments, flags, options) -> None - - Show arguments, flags and options. - """ - - msg = "arguments: {}\nflags: {}\noptions: {}".format(arguments, repr(flags), options) - self.room.send_message(msg, parent=message.id) - - def uptime_command(self, message, arguments, flags, options): - """ - uptime_command(message, arguments, flags, options) -> None - - Show uptime and other info. - """ - - stime = self.format_date() - utime = self.format_delta() - - if "i" in flags: - msg = "uptime: {} ({})".format(stime, utime) - msg += "\nid: {}".format(self.manager.get_id(self)) - msg += "\n{}".format(self.creation_info()) - - else: - msg = "/me is up since {} ({}).".format(stime, utime) - - self.room.send_message(msg, message.id) diff --git a/yaboli/botmanager.py b/yaboli/botmanager.py deleted file mode 100644 index 1a91b91..0000000 --- a/yaboli/botmanager.py +++ /dev/null @@ -1,149 +0,0 @@ -import json - -from . import bot -from . import exceptions - -class BotManager(): - """ - Keep track of multiple bots in different rooms. - """ - - def __init__(self, bot_class, default_nick="yaboli", max_bots=100, - bots_file="bots.json", data_file="data.json"): - """ - bot_class - class to create instances of - default_nick - default nick for all bots to assume when no nick is specified - max_bots - maximum number of bots allowed to exist simultaneously - None or 0 - no limit - bots_file - file the bot backups are saved to - None - no bot backups - data_file - file the bot data is saved to - - None - bot data isn't saved - """ - - self.bot_class = bot_class - self.max_bots = max_bots - self.default_nick = default_nick - - self.bots_file = bots_file - self.data_file = data_file - - self._bots = {} - self._bot_id = 0 - self._bot_data = {} - - self._load_bots() - - def create(self, room, password=None, nick=None, created_in=None, created_by=None): - """ - create(room, password, nick) -> bot - - Create a new bot in room. - """ - - if nick is None: - nick = self.default_nick - - if self.max_bots and len(self._bots) >= self.max_bots: - raise exceptions.CreateBotException("max_bots limit hit") - else: - bot = self.bot_class(room, nick=nick, password=password, manager=self, - created_in=created_in, created_by=created_by) - self._bots[self._bot_id] = bot - self._bot_id += 1 - - self._save_bots() - - return bot - - def remove(self, bot_id): - """ - remove(bot_id) -> None - - Kill a bot and remove it from the list of bots. - """ - - if bot_id in self._bots: - self._bots[bot_id].stop() - self._bots.pop(bot_id) - - self._save_bots() - - def get(self, bot_id): - """ - get(self, bot_id) -> bot - - Return bot with that id, if found. - """ - - if bot_id in self._bots: - return self._bots[bot_id] - - def get_id(self, bot): - """ - get_id(bot) -> bot_id - - Return the bot id, if the bot is known. - """ - - for bot_id, own_bot in self._bots.items(): - if bot == own_bot: - return bot_id - - def get_similar(self, room, nick): - """ - get_by_room(room, nick) -> dict - - Collect all bots that are connected to the room and have that nick. - """ - - return {bot_id: bot for bot_id, bot in self._bots.items() - if bot.roomname() == room and bot.mentionable().lower() == nick.lower()} - - def _load_bots(self): - """ - _load_bots() -> None - - Load and create bots from self.bots_file. - """ - - if not self.bots_file: - return - - try: - with open(self.bots_file) as f: - bots = json.load(f) - except FileNotFoundError: - pass - else: - for bot_info in bots: - bot = self.create(bot_info["room"], password=bot_info["password"], - nick=bot_info["nick"]) - bot.created_in = bot_info["created_in"] - bot.created_by = bot_info["created_by"] - - def _save_bots(self): - """ - _save_bots() -> None - - Save all current bots to self.bots_file. - """ - - if not self.bots_file: - return - - bots = [] - - for bot_id, bot in self._bots.items(): - bot_info = {} - - bot_info["room"] = bot.roomname() - bot_info["password"] = bot.password() - bot_info["nick"] = bot.nick() - bot_info["created_in"] = bot.created_in - bot_info["created_by"] = bot.created_by - - bots.append(bot_info) - - with open(self.bots_file, "w") as f: - json.dump(bots, f) diff --git a/yaboli/connection.py b/yaboli/connection.py index 8488651..c8a4ecb 100644 --- a/yaboli/connection.py +++ b/yaboli/connection.py @@ -5,7 +5,9 @@ asyncio.get_event_loop().set_debug(True) import json import websockets -from websockets import ConnectionClosed +#from websockets import ConnectionClosed + +__all__ = ["Connection"] diff --git a/yaboli/controller.py b/yaboli/controller.py index 2a98b72..6684de0 100644 --- a/yaboli/controller.py +++ b/yaboli/controller.py @@ -1,4 +1,6 @@ -from room import Room +from .room import Room + +__all__ = ["Controller"] diff --git a/yaboli/exceptions.py b/yaboli/exceptions.py deleted file mode 100644 index f48993d..0000000 --- a/yaboli/exceptions.py +++ /dev/null @@ -1,41 +0,0 @@ -class YaboliException(Exception): - """ - Generic yaboli exception class. - """ - - pass - -class BotManagerException(YaboliException): - """ - Generic BotManager exception class. - """ - - pass - -class CreateBotException(BotManagerException): - """ - This exception will be raised when BotManager could not create a bot. - """ - - pass - -class BotNotFoundException(BotManagerException): - """ - This exception will be raised when BotManager could not find a bot. - """ - - pass - -class BotException(YaboliException): - """ - Generic Bot exception class. - """ - - pass - -class ParseMessageException(BotException): - """ - This exception will be raised when a failure parsing a message occurs. - """ - - pass diff --git a/yaboli/message.py b/yaboli/message.py deleted file mode 100644 index 7e64bc5..0000000 --- a/yaboli/message.py +++ /dev/null @@ -1,99 +0,0 @@ -import time - -from . import session - -class Message(): - """ - This class keeps track of message details. - """ - - def __init__(self, id, time, sender, content, parent=None, edited=None, deleted=None, - truncated=None): - """ - id - message id - time - time the message was sent (epoch) - sender - session of the sender - content - content of the message - parent - id of the parent message, or None - edited - time of last edit (epoch) - deleted - time of deletion (epoch) - truncated - message was truncated - """ - - self.id = id - self.time = time - self.sender = sender - self.content = content - self.parent = parent - self.edited = edited - self.deleted = deleted - self.truncated = truncated - - @classmethod - def from_data(self, data): - """ - Creates and returns a message created from the data. - NOTE: This also creates a session object using the data in "sender". - - data - a euphoria message: http://api.euphoria.io/#message - """ - - sender = session.Session.from_data(data["sender"]) - parent = data["parent"] if "parent" in data else None - edited = data["edited"] if "edited" in data else None - deleted = data["deleted"] if "deleted" in data else None - truncated = data["truncated"] if "truncated" in data else None - - return self( - data["id"], - data["time"], - sender, - data["content"], - parent=parent, - edited=edited, - deleted=deleted, - truncated=truncated - ) - - def time_formatted(self, date=False): - """ - time_formatted(date=False) -> str - - date - include date in format - - Time in a readable format: - With date: YYYY-MM-DD HH:MM:SS - Without date: HH:MM:SS - """ - - if date: - return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(self.time)) - else: - return time.strftime("%H:%M:%S", time.gmtime(self.time)) - - def is_edited(self): - """ - is_edited() -> bool - - Has this message been edited? - """ - - return True if self.edited is not None else False - - def is_deleted(self): - """ - is_deleted() -> bool - - Has this message been deleted? - """ - - return True if self.deleted is not None else False - - def is_truncated(self): - """ - is_truncated() -> bool - - Has this message been truncated? - """ - - return True if self.truncated is not None else False diff --git a/yaboli/messages.py b/yaboli/messages.py deleted file mode 100644 index 8382a69..0000000 --- a/yaboli/messages.py +++ /dev/null @@ -1,154 +0,0 @@ -import operator - -from . import message - -class Messages(): - """ - Message storage class which preserves thread hierarchy. - """ - - def __init__(self, message_limit=500): - """ - message_limit - maximum amount of messages that will be stored at a time - None - no limit - """ - - self.message_limit = message_limit - - self._by_id = {} - self._by_parent = {} - - def _sort(self, msgs): - """ - _sort(messages) -> None - - Sorts a list of messages by their id, in place. - """ - - msgs.sort(key=operator.attrgetter("id")) - - def add_from_data(self, data): - """ - add_from_data(data) -> None - - Create a message from "raw" data and add it. - """ - - mes = message.Message.from_data(data) - - self.add(mes) - - def add(self, mes): - """ - add(message) -> None - - Add a message to the structure. - """ - - self.remove(mes.id) - - self._by_id[mes.id] = mes - - if mes.parent: - if not mes.parent in self._by_parent: - self._by_parent[mes.parent] = [] - self._by_parent[mes.parent].append(mes) - - if self.message_limit and len(self._by_id) > self.message_limit: - self.remove(self.get_oldest().id) - - def remove(self, mid): - """ - remove(message_id) -> None - - Remove a message from the structure. - """ - - mes = self.get(mid) - if mes: - if mes.id in self._by_id: - self._by_id.pop(mes.id) - - parent = self.get_parent(mes.id) - if parent and mes in self.get_children(parent.id): - self._by_parent[mes.parent].remove(mes) - - def remove_all(self): - """ - remove_all() -> None - - Removes all messages. - """ - - self._by_id = {} - self._by_parent = {} - - def get(self, mid): - """ - get(message_id) -> Message - - Returns the message with the given id, if found. - """ - - if mid in self._by_id: - return self._by_id[mid] - - def get_oldest(self): - """ - get_oldest() -> Message - - Returns the oldest message, if found. - """ - - oldest = None - for mid in self._by_id: - if oldest is None or mid < oldest: - oldest = mid - return self.get(oldest) - - def get_youngest(self): - """ - get_youngest() -> Message - - Returns the youngest message, if found. - """ - - youngest = None - for mid in self._by_id: - if youngest is None or mid > youngest: - youngest = mid - return self.get(youngest) - - def get_parent(self, mid): - """ - get_parent(message_id) -> str - - Returns the message's parent, if found. - """ - - mes = self.get(mid) - if mes: - return self.get(mes.parent) - - def get_children(self, mid): - """ - get_children(message_id) -> list - - Returns a sorted list of children of the given message, if found. - """ - - if mid in self._by_parent: - children = self._by_parent[mid][:] - self._sort(children) - return children - - def get_top_level(self): - """ - get_top_level() -> list - - Returns a sorted list of top-level messages. - """ - - msgs = [self.get(mid) for mid in self._by_id if not self.get(mid).parent] - self._sort(msgs) - return msgs diff --git a/yaboli/room.py b/yaboli/room.py index cc7ef4f..782085b 100644 --- a/yaboli/room.py +++ b/yaboli/room.py @@ -1,6 +1,10 @@ import asyncio -from connection import Connection -import utils +from .connection import * +from .utils import * + +__all__ = ["Room"] + + class Room: ROOM_FORMAT = "wss://euphoria.io/room/{}/ws" @@ -17,7 +21,7 @@ class Room: # If you need to keep track of messages, use utils.Log. self.session = None self.account = None - self.listing = utils.Listing() + self.listing = Listing() # Various room information self.account_has_access = None @@ -118,7 +122,7 @@ class Room: response = await self._conn.send("send", data) self._check_for_errors(response) - message = utils.Message.from_dict(response.get("data")) + message = Message.from_dict(response.get("data")) return message async def who(self): @@ -170,7 +174,7 @@ class Room: # TODO: log throttled if "error" in packet: - raise utils.ResponseError(response.get("error")) + raise ResponseError(response.get("error")) async def _handle_bounce(self, packet): """ @@ -210,7 +214,7 @@ class Room: """ data = packet.get("data") - self.session = utils.Session.from_dict(data.get("session")) + self.session = Session.from_dict(data.get("session")) self.room_is_private = data.get("room_is_private") self.version = data.get("version") self.account = data.get("account", None) @@ -234,7 +238,7 @@ class Room: """ data = packet.get("data") - session = utils.Session.from_dict(data) + session = Session.from_dict(data) # update self.listing self.listing.add(session) @@ -282,7 +286,7 @@ class Room: """ data = packet.get("data") - session = utils.Session.from_dict(data) + session = Session.from_dict(data) # update self.listing self.listing.remove(session.session_id) @@ -315,7 +319,7 @@ class Room: """ data = packet.get("data") - message = utils.Message.from_dict(data) + message = Message.from_dict(data) await self.controller.on_send(message) @@ -328,8 +332,8 @@ class Room: data = packet.get("data") - sessions = [utils.Session.from_dict(d) for d in data.get("listing")] - messages = [utils.Message.from_dict(d) for d in data.get("log")] + sessions = [Session.from_dict(d) for d in data.get("listing")] + messages = [Message.from_dict(d) for d in data.get("log")] # update self.listing for session in sessions: diff --git a/yaboli/session.py b/yaboli/session.py deleted file mode 100644 index e649655..0000000 --- a/yaboli/session.py +++ /dev/null @@ -1,71 +0,0 @@ -class Session(): - """ - This class keeps track of session details. - """ - - def __init__(self, id, name, server_id, server_era, session_id, is_staff=None, is_manager=None): - """ - id - agent/account id - name - name of the client when the SessionView was captured - server_id - id of the server - server_era - era of the server - session_id - session id (unique across euphoria) - is_staff - client is staff - is_manager - client is manager - """ - - self.id = id - self.name = name - self.server_id = server_id - self.server_era = server_era - self.session_id = session_id - self.staff = is_staff - self.manager = is_manager - - @classmethod - def from_data(self, data): - """ - Creates and returns a session created from the data. - - data - a euphoria SessionView: http://api.euphoria.io/#sessionview - """ - - is_staff = data["is_staff"] if "is_staff" in data else None - is_manager = data["is_manager"] if "is_manager" in data else None - - return self( - data["id"], - data["name"], - data["server_id"], - data["server_era"], - data["session_id"], - is_staff, - is_manager - ) - - def session_type(self): - """ - session_type() -> str - - The session's type (bot, account, agent). - """ - - return self.id.split(":")[0] - - def is_staff(self): - """ - is_staff() -> bool - - Is a user staff? - """ - - return self.staff and True or False - - def is_manager(self): - """ - is_manager() -> bool - - Is a user manager? - """ - - return self.staff and True or False diff --git a/yaboli/sessions.py b/yaboli/sessions.py deleted file mode 100644 index b00cf75..0000000 --- a/yaboli/sessions.py +++ /dev/null @@ -1,146 +0,0 @@ -from . import session - -class Sessions(): - """ - Keeps track of sessions. - """ - - def __init__(self): - """ - TODO - """ - self._sessions = {} - - def add_from_data(self, data): - """ - add_raw(data) -> None - - Create a session from "raw" data and add it. - """ - - ses = session.Session.from_data(data) - - self._sessions[ses.session_id] = ses - - def add(self, ses): - """ - add(session) -> None - - Add a session. - """ - - self._sessions[ses.session_id] = ses - - def get(self, sid): - """ - get(session_id) -> Session - - Returns the session with that id. - """ - - if sid in self._sessions: - return self._sessions[sid] - - def remove(self, sid): - """ - remove(session) -> None - - Remove a session. - """ - - if sid in self._sessions: - self._sessions.pop(sid) - - def remove_on_network_partition(self, server_id, server_era): - """ - remove_on_network_partition(server_id, server_era) -> None - - Removes all sessions matching the server_id/server_era combo. - http://api.euphoria.io/#network-event - """ - - sesnew = {} - for sid in self._sessions: - ses = self.get(sid) - if not (ses.server_id == server_id and ses.server_era == server_era): - sesnew[sid] = ses - self._sessions = sesnew - - def remove_all(self): - """ - remove_all() -> None - - Removes all sessions. - """ - - self._sessions = {} - - def get_all(self): - """ - get_all() -> list - - Returns the full list of sessions. - """ - - return [ses for sid, ses in self._sessions.items()] - - def get_people(self): - """ - get_people() -> list - - Returns a list of all non-bot and non-lurker sessions. - """ - - # not a list comprehension because that would span several lines too - people = [] - for sid in self._sessions: - ses = self.get(sid) - if ses.session_type() in ["agent", "account"] and ses.name: - people.append(ses) - return people - - def _get_by_type(self, tp): - """ - _get_by_type(session_type) -> list - - Returns a list of all non-lurker sessions with that type. - """ - - return [ses for sid, ses in self._sessions.items() - if ses.session_type() == tp and ses.name] - - def get_accounts(self): - """ - get_accounts() -> list - - Returns a list of all logged-in sessions. - """ - - return self._get_by_type("account") - - def get_agents(self): - """ - get_agents() -> list - - Returns a list of all sessions who are not signed into an account and not bots or lurkers. - """ - - return self._get_by_type("agent") - - def get_bots(self): - """ - get_bots() -> list - - Returns a list of all bot sessions. - """ - - return self._get_by_type("bot") - - def get_lurkers(self): - """ - get_lurkers() -> list - - Returns a list of all lurker sessions. - """ - - return [ses for sid, ses in self._sessions.items() if not ses.name] diff --git a/yaboli/utils.py b/yaboli/utils.py index 3401d4c..9ec3682 100644 --- a/yaboli/utils.py +++ b/yaboli/utils.py @@ -1,6 +1,9 @@ -import re - -__all__ = ["mention", "mention_reduced", "similar", "Session", "Listing", "Message", "Log"] +__all__ = [ + "mention", "mention_reduced", "similar", + "Session", "Listing", + "Message", "Log", + "ResponseError" +] @@ -111,3 +114,6 @@ class Message(): class Log: pass # TODO + +class ResponseError(Exception): + pass