Clean up module structure

This commit is contained in:
Joscha 2017-09-02 12:58:39 +00:00
parent dfad3241fb
commit 6cc8094e0d
14 changed files with 39 additions and 1315 deletions

View file

@ -1,47 +0,0 @@
import asyncio
#from controller import Bot
from controller import Controller
from utils import *
#class TestBot(Bot):
class TestBot(Controller):
def __init__(self, roomname):
super().__init__(roomname)
async def on_snapshot(self, user_id, session_id, version, listing, log, nick=None,
pm_with_nick=None, pm_with_user_id=None):
await self.room.nick("TestBot")
async def on_send(self, message):
await self.room.send("Hey, a message!", message.message_id)
async def on_join(self, session):
if session.nick != "":
await self.room.send(f"Hey, a @{mention(session.nick)}!")
else:
await self.room.send("Hey, a lurker!")
async def on_nick(self, session_id, user_id, from_nick, to_nick):
if from_nick != "" and to_nick != "":
if from_nick == to_nick:
await self.room.send(f"You didn't even change your nick, @{mention(to_nick)} :(")
else:
await self.room.send(f"Bye @{mention(from_nick)}, hi @{mention(to_nick)}")
elif from_nick != "":
await self.room.send(f"Bye @{mention(from_nick)}? This message should never appear...")
elif to_nick != "":
await self.room.send(f"Hey, a @{mention(to_nick)}!")
else:
await self.room.send("I have no idea how you did that. This message should never appear...")
async def on_part(self, session):
if session.nick != "":
await self.room.send(f"Bye, you @{mention(session.nick)}!")
else:
await self.room.send("Bye, you lurker!")
if __name__ == "__main__":
bot = TestBot("test")
asyncio.get_event_loop().run_until_complete(bot.run())

View file

@ -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__

View file

@ -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()

View file

@ -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 [ <room> [ --pw=<password> ] ]\n"
"<room> : 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 | <command> ]\n"
"-s : general syntax help\n"
"-c : only list the commands\n"
"<command> : 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 <room> [ --pw=<password> ]\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"
"!{} <id> @{} [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:
[- ][<days>d ][<hours>h ][<minutes>m ]<seconds>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} <command>\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)

View file

@ -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)

View file

@ -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"]

View file

@ -1,4 +1,6 @@
from room import Room
from .room import Room
__all__ = ["Controller"]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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]

View file

@ -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