Added UI and helper sets, fixed bugs in net socket server, added better comments

This commit is contained in:
aewens 2019-03-11 22:15:16 +01:00
parent 54f4eda307
commit 059187881e
12 changed files with 577 additions and 172 deletions

48
TODO
View File

@ -2,13 +2,16 @@
### socket_server
- [ ] Add comments
- [ ] Abstract default handler out to another file
- [ ] Send heartbeat
- [ ] Better handle clients disconnecting
- [ ] Remove lookup, use clients instead
- [ ] Add alias system for clients to replace using fd
- [x] Add comments
- [x] Abstract default handler out to another file
- [x] Send heartbeat
- [x] Better handle clients disconnecting
- [x] Remove lookup, use clients instead
- [x] Add alias system for clients to replace using fd
- [ ] Add support for cryptography
- [ ] Add data retention verbs to default handler
- [x] Abstract length prefix to message into handler
- [x] Fix comments to be more helpful
### socket_client
@ -17,18 +20,43 @@
- [ ] Respond to heartbeat
- [ ] Respond to alias from server
- [ ] Add support for cryptography
- [ ] Add data retention model to default handler
- [ ] Abstract length prefix to message into handler
### socket_to_websocket
- [ ] Bridges socket_client and websocket_client together
## crypto
- [ ] Add crypto set
- [ ] In crypto add GPG, Diffie-Hellman, and symmetric & asymmetric crypto
- [ ] Add GPG wrapper functions
- [ ] Add Diffie-Hellman functions
- [ ] Add symmetric & asymmetric crypto functions
## helpers
- [ ] Add helpers set
- [ ] In helpers add JSON encoding / decoding
- [x] Add helpers set
- [x] In helpers add JSON encoding / decoding
- [ ] Add helper for generating / reading Twitter's snowflake ID format
- [ ] Add helpers for running shell commands and getting outputs
- [ ] Add wrapper functions to reading / using git repositories
## db
- [ ] Add db set
- [ ] In db add sqlite wrappers
- [ ] In db add sqlite wrappers to create, modify, and delete tables
- [ ] In db add sqlite wrappers to query, add, edit, and remove entries
## ui
- [x] Add ui set
- [ ] Add framework for curses module
## web
- [ ] Add web set
- [ ] Add websocket server compatible with socket_server handlers
- [ ] Add websocket client compatible with socket_client handlers
- [ ] Add Flask that integrates websocket server and databases from db

40
abots/__init__.py Normal file
View File

@ -0,0 +1,40 @@
"""
ABOTS: A Bunch Of Tiny Scripts
==============================
The name of this project explains what it is, a bunch of tiny scripts.
I find myself thinking of many different projects that all require some core
functionality that many other projects can share.
However, it must be laid down first before adding the "unique" code that my
ideas consist of.
The usual approach to this issue is using an existing framework someone else
wrote, but then you need to understand how that framework does things and fit
your application to fit that mindset.
As well, you now have this black box in your application that you do not 100%
understand and adds another layer of abstraction that makes debugging issues
that much harder (we all make bugs, so do framework devs).
With that being said, ideologically I do not like using existing frameworks
since that deprives me of the opportunity to learn how that particular piece of
software works.
So ABOTS is my approach of making a shared library of code that I want to use
in other projects.
Any improvements here can then improve my other projects, as well as give me
something small to work on when I am in-between projects that could eventually
be useful later on.
The ideas of these scripts are to be as modular as possible so that they can be
used in a variety of different projects with little changes needed.
Due to the nature of the project, this will probably not be too useful for
other developers who are not me, but it could be useful to see how a particular
component of ABOTS works since the project is optimized more towards
versitlity and simplicity than being the most efficient way of doing something
at the expense of being harder to understand.
Now that you know what lies here, proceed with caution.
You have been warned.
~aewens
"""

View File

@ -0,0 +1 @@
from abots.helpers.json import jots, jsto

22
abots/helpers/json.py Normal file
View File

@ -0,0 +1,22 @@
from json import dumps, loads
# JSON encoder, converts a python object to a string
def jots(self, data, readable=False):
kwargs = dict()
# If readable is set, it pretty prints the JSON to be more human-readable
if readable:
kwargs["sort_keys"] = True
kwargs["indent"] = 4
kwargs["separators"] = (",", ":")
try:
return json.dumps(data, **kwargs)
except ValueError as e:
return None
# JSON decoder, converts a string to a python object
def jsto(self, data):
try:
return json.loads(data)
except ValueError as e:
return None

View File

@ -1,3 +1,4 @@
from abots.net.socket_server import SocketServer
from abots.net.socket_client import SocketClient
from abots.net.socket_server_handler import SocketServerHandler
from abots.net.socket_server_handler import SocketServerHandler
from abots.net.socket_client_handler import SocketClientHandler

View File

@ -1,22 +1,39 @@
from abots.net.socket_client_handler import SocketClientHandler as handler
from struct import pack, unpack
from multiprocessing import Process, Queue, JoinableQueue
from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
from ssl import wrap_socket
"""
Socket Client
=============
"""
class SocketClient(Process):
def __init__(self, host, port, buffer_size=4096, end_of_line="\r\n",
inbox=JoinableQueue(), outbox=Queue(), handler=lambda x: x):
secure=False, inbox=JoinableQueue(), outbox=Queue(), handler=handler,
**kwargs):
Process.__init__(self)
self.host = host
self.port = port
self.buffer_size = buffer_size
self.end_of_line = end_of_line
self.handler = handler
self.sock = socket(AF_INET, SOCK_STREAM)
self.connection = (self.host, self.port)
self.running = True
self.secure = secure
self.inbox = inbox
self.outbox = outbox
self.handler = handler(self)
self.sock = socket(AF_INET, SOCK_STREAM)
if self.secure:
self.sock = wrap_socket(self.sock, **kwargs)
self.connection = (self.host, self.port)
self.running = True
self.error = None
def _recv_bytes(self, get_bytes, decode=True):
@ -99,7 +116,7 @@ class SocketClient(Process):
if err is not None:
print(err)
return err
print("Ready!")
# print("Ready!")
while self.running:
data = self._get_message()
if data is not None:

View File

View File

@ -1,210 +1,362 @@
from abots.net.socket_server_handler import SocketServerHandler
from abots.net.socket_server_handler import SocketServerHandler as handler
from threading import Thread
from struct import pack, unpack
from select import select
from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
from multiprocessing import Process, Queue, JoinableQueue
from time import time
from ssl import wrap_socket
"""
net\SocketServer
================
The intent behind this script is to provide a simple interface to start up a
TCP socket server in the background, run each of the clients in their own
thread, provide a simple system to handle server events, and provide simple
functions to send/receive messages from the server.
"""
# Inherits Process so that server can be run as a daemon
class SocketServer(Process):
def __init__(self, host, port, listeners=5, buffer_size=4096,
max_message_size=26214400, end_of_line="\r\n", inbox=JoinableQueue(),
outbox=Queue(), handler=None):
# There are many parameters here, but that is so that any constant used can
# be easily tweaked and not remain hard-coded without an easy way to change
def __init__(self, host, port, listeners=5, buffer_size=4096, secure=False,
max_message_size=-1, end_of_line="\r\n", heartbeat=60,
inbox=JoinableQueue(), outbox=Queue(), handler=handler, **kwargs):
Process.__init__(self)
# The connection information for server, the clients will use this to
# connect to the server
self.host = host
self.port = port
# The number of unaccepted connections that the system will allow
# before refusing new connections
self.listeners = listeners
# Size of buffer pulled by `receive_bytes` when not specified
self.buffer_size = buffer_size
# If max_message_size is -1, it allows any message size
self.max_message_size = max_message_size
# Which character(s) will terminate a message
self.end_of_line = end_of_line
# Determines if SSL wrapper is used
self.secure = secure
# How often a heartbeat will be sent to a client
self.heartbeat = heartbeat
# Queues used for sending messages and receiving results using `send`
# and `results`
self.inbox = inbox
self.outbox = outbox
self.handler = SocketServerHandler if handler is None else handler
# An object that determines how the server reacts to events, will use
# net\SocketServerHandler if none are specified. Use it as a model for
# how other handlers should look / work.
self.handler = handler(self)
# Sets up the socket itself
self.sock = socket(AF_INET, SOCK_STREAM)
if self.secure:
# Note: kwargs is used here to specify any SSL parameters desired
self.sock = wrap_socket(self.sock, **kwargs)
# Will later be set to the file descriptor of the socket on the server
# See `_prepare`
self.sock_fd = -1
self.lookup = list()
# Will later be set to the alias used for the socket on the server
# See `_prepare`
self.sock_alias = None
# List of all sockets involved (both client and server)
self.sockets = list()
# Maps metadata about the clients
self.clients = dict()
# State variable for if the server is running or not. See `run`.
self.running = True
def _close_sock(self, sock):
# Sends all messages queued in inbox
def _process_inbox(self):
while not self.inbox.empty():
# In the format" mode, message, args
data = self.inbox.get()
mode = data[0]
# Send to one socket
if mode == self.handler.send_verb:
client, message, args = data[1:]
self.send_message(message, *args)
# Broadcast to sockets
elif mode == self.handler.broadcast_verb:
message, args = data[1:]
self.broadcast_message(self.sock, message, *args)
self.inbox.task_done()
# Logic for the client socket running in its own thread
def _client_thread(self, sock, alias):
last = time()
client = self.clients[alias]
while self.running:
now = time()
# Run heartbeat after defined time elapses
# This will probably drift somewhat, but this is fine here
if now - last >= self.heartbeat:
# If the client missed last heartbeat, close client
if not client["alive"]:
self.handler.close_client(alias)
break
# The handler must set this to True, this is how a missed
# heartbeat is checked later on
client["alive"] = False
last = now
self.handler.send_heartbeat(alias)
try:
message = self.handler.get_message(sock)
# The socket can either be broken or no longer open at all
except (BrokenPipeError, OSError) as e:
# In this case, the socket most likely died before the
# heartbeat caught it
self.handler.close_client(alias)
break
if message is None:
continue
# Each message returns a status code, exactly which code is
# determined by the handler
status = self.handler.message(sock, message)
# Send status and message received to the outbox queue
self.outbox.put((status, message))
# Prepares socket server before starting it
def _prepare(self):
self.sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
try:
self.sock.bind((self.host, self.port))
# The socket can either be broken or no longer open at all
except (BrokenPipeError, OSError) as e:
# This usually means that the port is already in use
return e
self.sock.listen(self.listeners)
# Gets the file descriptor of the socket, which is a fallback for a
# unique identifier for the sockets when an alias does not work
self.sock_fd = self.sock.fileno()
sock_address = self.sock.getsockname()
sock_host, sock_port = sock_address
# This may change later, but for now aliases start at @0 and continue
# on from there numerically
self.sock_alias = "@{}".format(len(self.sockets))
self.sockets.append(self.sock)
# Set metadata about the socket server, the fd and alias are both set
# here to make obtaining the other metadata possible with less lookups
self.clients[self.sock_fd] = dict()
self.clients[self.sock_fd]["fd"] = self.sock_fd
self.clients[self.sock_fd]["host"] = sock_host
self.clients[self.sock_fd]["port"] = sock_port
self.clients[self.sock_fd]["sock"] = self.sock
self.clients[self.sock_fd]["alias"] = self.sock_alias
# Here the alias is just a pointer to the same data, or at least acts
# like a pointer given how Python handles dictionaries referencing the
# same data
self.clients[self.sock_alias] = self.clients[self.sock_fd]
return None
# Closes a connected socket and removes it from the server metadata
def close_sock(self, alias):
client = self.clients.get(alias, None)
if client is None:
return None
sock = client["sock"]
fd = client["fd"]
self.sockets.remove(sock)
fd = self._get_client_fd(sock)
if fd is not None:
# While the alias is a pointer, you need to delete both
# individually to truly remove the socket from `clients`
del self.clients[fd]
del self.clients[alias]
sock.close()
def _recv_bytes(self, sock, get_bytes, decode=True):
# Receives specified number of bytes from a socket
# sock - one of the sockets in sockets
# get_bytes - number of bytes to receive from socket
# decode - flag if the returned data is binary-to-string decoded
def receive_bytes(self, sock, get_bytes, decode=True):
data = "".encode()
eol = self.end_of_line.encode()
if get_bytes > self.max_message_size:
# Auto-fail if requested bytes is greater than allowed by server
if self.max_message_size > 0 and get_bytes > self.max_message_size:
return None
attempts = 0
while len(data) < get_bytes:
if attempts > self.max_message_size / self.buffer_size:
break
# Automatically break loop to prevent infinite loop
if self.max_message_size > 0:
if attempts > self.max_message_size / self.buffer_size:
break
else:
attempts = attempts + 1
else:
attempts = attempts + 1
# With max_message_size not set, allow at least twice the
# needed iterations to occur before breaking loop
if attempts > 2 * (get_bytes / self.buffer_size):
break
else:
attempts = attempts + 1
bufsize = get_bytes - len(data)
# Force bufsize to cap out at buffer_size
if bufsize > self.buffer_size:
bufsize = self.buffer_size
try:
packet = sock.recv(bufsize)
except OSError:
# The socket can either be broken or no longer open at all
except (BrokenPipeError, OSError) as e:
return None
length = len(data) + len(packet)
checker = packet if length < get_bytes else packet[:-2]
# Automatically stop reading message if EOL character sent
if eol in checker:
packet = packet.split(eol)[0] + eol
return data + packet
data = data + packet
return data.decode() if decode else data
def _package_message(self, message, *args):
formatted = None
if len(args) > 0:
formatted = message.format(*args) + self.end_of_line
else:
formatted = message + self.end_of_line
packaged = pack(">I", len(formatted)) + formatted.encode()
return packaged
def _get_message_size(self, sock):
raw_message_size = self._recv_bytes(sock, 4, False)
if not raw_message_size:
return None
message_size = unpack(">I", raw_message_size)[0]
return message_size
def _get_message(self, sock):
message_size = self._get_message_size(sock)
if message_size is None:
return None
elif message_size > self.max_message_size:
return None
# Packages a message and sends it to socket
def send_message(self, sock, message, *args):
formatted = self.handler.format_message(message, *args)
try:
return self._recv_bytes(sock, message_size).strip(self.end_of_line)
except OSError:
self._close_sock(sock)
return None
sock.send(formatted)
# The socket can either be broken or no longer open at all
except (BrokenPipeError, OSError) as e:
alias = self.get_client_alias_by_sock(sock)
if alias is not None:
self.close_sock(alias)
def _send_message(self, sock, message, *args):
packaged = self._package_message(message, *args)
try:
sock.send(packaged)
except BrokenPipeError:
self._close_sock(sock)
except OSError:
self._close_sock(sock)
def _broadcast_message(self, client_sock, client_message, *args):
# Like send_message, but sends to all sockets but the server and the sender
def broadcast_message(self, client_sock, client_message, *args):
for sock in self.sockets:
not_server = sock != self.sock
not_client = sock != client_sock
if not_server and not_client:
self._send_message(sock, client_message, *args)
self.send_message(sock, client_message, *args)
def _get_client_fd(self, client_sock):
# Obtains file descriptor of the socket
def get_client_fd(self, client_sock):
try:
# First, try the easy route of just pulling it directly
return client_sock.fileno()
except OSError:
for fd, sock in self.lookup:
# The socket can either be broken or no longer open at all
except (BrokenPipeError, OSError) as e:
# Otherwise, the socket is probably dead and we can try finding it
# using brute-force. This sometimes works
for fd, sock in self.sockets:
if sock != client_sock:
continue
return fd
# If the brute-force option does not work, I cannot think of a good
# way to get the fd aside from passing it along everywhere that
# sock is also used, which would be extremely tedios. However, if
# you have the alias you can skip this entirely and just pull the
# fd from `clients` using the alias
return None
def _process_inbox(self):
while not self.inbox.empty():
data = self.inbox.get()
mode = data[0]
if mode == "SEND":
client, message, args = data[1:]
self._send_message(message, *args)
elif mode == "BCAST":
message, args = data[1:]
self._broadcast_message(self.sock, message, *args)
self.inbox.task_done()
def _client_thread(self, sock):
while self.running:
message = self._get_message(sock)
if message is None:
continue
status = self.handler(self, sock, message)
self.outbox.put((status, message))
def _prepare(self):
self.sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
try:
self.sock.bind((self.host, self.port))
except OSError as e:
return e
self.sock.listen(self.listeners)
self.sock_fd = self.sock.fileno()
sock_address = self.sock.getsockname()
sock_host, sock_port = sock_address
self.lookup.append((self.sock_fd, sock_address))
self.sockets.append(self.sock)
self.clients[self.sock_fd] = dict()
self.clients[self.sock_fd]["host"] = sock_host
self.clients[self.sock_fd]["port"] = sock_port
self.clients[self.sock_fd]["sock"] = self.sock
return None
# I realize the function name here is long, but for the few times I use
# this it makes it clear exactly what magic is going on
def get_client_alias_by_sock(self, client_sock):
client_fd = self.get_client_fd(client_sock)
if client_fd is None:
return None
return self.clients.get(client_fd, dict()).get("alias", None)
# Externally called function to send a message to a client
def send(self, client, message, *args):
self.inbox.put(("SEND", client, message, args))
# This queue will be read by `_process_inbox` during the next loop
self.inbox.put((self.handler.send_verb, client, message, args))
# Externally called function to broadcast a message to all clients
def broadcast(self, message, *args):
self.inbox.put(("BCAST", message, args))
# This queue will be read by `_process_inbox` during the next loop
self.inbox.put((self.handler.broadcast_verb, message, args))
def results(self):
# Externally called function to iterates over the outbox queue and returns
# them as a list in FIFO order
def results(self, remove_status=False):
messages = list()
while not self.outbox.empty():
messages.append(self.outbox.get())
result = self.outbox.get()
# For when you do not care about the status codes
if remove_status:
status, message = result
messages.append(message)
else:
messages.append(result)
return messages
# The Process function for running the socket server logic loop
def run(self):
err = self._prepare()
if err is not None:
print(err)
return err
print("Server ready!")
# print("Server ready!")
while self.running:
# try:
# selection = select(self.sockets, list(), list(), 5)
# read_socks, write_socks, err_socks = selection
# except OSError as e:
# print("Error", e)
# continue
# for sock in read_socks:
# if sock == self.sock:
try:
# Accept new socket client
client_sock, client_address = self.sock.accept()
client_sock.settimeout(60)
except OSError:
# The socket can either be broken or no longer open at all
except (BrokenPipeError, OSError) as e:
continue
# Collect the metadata of the client socket
client_name = "{}:{}".format(*client_address)
client_host, client_port = client_address
client_fd = client_sock.fileno()
self.lookup.append((client_fd, client_sock))
client_alias = "@{}".format(len(self.sockets))
# Define metadata for client
self.sockets.append(client_sock)
self.clients[client_fd] = dict()
self.clients[client_fd]["fd"] = client_fd
self.clients[client_fd]["host"] = client_host
self.clients[client_fd]["port"] = client_port
self.clients[client_fd]["sock"] = client_sock
joined = "ENTER {}".format(client_name)
self.outbox.put((1, joined))
self._broadcast_message(client_sock, joined)
Thread(target=self._client_thread, args=(client_sock,)).start()
# else:
# message = self._get_message(sock)
# if message is None:
# continue
# status = self.handler(self, sock, message)
# self.outbox.put((status, message))
self.clients[client_fd]["alias"] = client_alias
self.clients[client_fd]["alive"] = True
# The alias is just a key that points to the same metadata
self.clients[client_alias] = self.clients[client_fd]
# Have handler process new client event
status = self.handler.open_client(client_alias)
# Send status and message received to the outbox queue
self.outbox.put((status, message))
# Spawn new thread for client
args = (client_sock, client_alias)
Thread(target=self._client_thread, args=args).start()
# Process messages waiting in inbox queue
# This is done at the end in case for some weird reason a message
# is sent to the new client in the middle of processing this data
# it eliminates the chance of a race condition.
self._process_inbox()
# Stop the socket server
def stop(self):
self.handler.close_server()
self.running = False
self.sock.close()

View File

@ -1,43 +1,166 @@
def SocketServerHandler(server, sock, message):
print("RAW:", message)
if message == "STOP":
server._broadcast_message(server.sock, "STOP")
server.stop()
"""
Socket Server Handlers
======================
The socket server was made to be versitile where the handler can be swapped out
in favor of another handler. This handler is the default provided if one is not
passed to get the basic client-server relationship working for either starting
with something simple, testing, and/or providing a template to build other
handlers from.
"""
class SocketServerHandler:
def __init__(self, server):
self.server = server
# These are used when processing inbox messages
self.send_verb = "SEND"
self.broadcast_verb = "CAST"
self.close_verb = "STOP"
# Tells all clients that a node joined the socket server
def open_client(self, alias):
alias = message[5:]
client = self.server.clients.get(alias, None)
if client is None:
return 1
self.server.broadcast_message(client, message)
return 0
# Informs the other clients a client left and closes that client's socket
def close_client(self, alias):
client = self.server.clients.get(alias, None)
if client is None:
return 1
message = "LEFT {}".format(alias)
self.server.broadcast_message(self.server.sock, message)
self.server.close_sock(alias)
return 0
# Lets the clients know the server is intentionally closing
def close_server(self):
self.server.broadcast_message(self.server.sock, self.close_verb)
return -1
if message == "QUIT":
client_fd = server._get_client_fd(sock)
if client_fd is None:
# Sends a heartbeat to the client to detect if it is still responding
def send_heartbeat(self, alias):
client = self.server.clients.get(alias, None)
if client is None:
return 1
sock = client.get("sock", None)
if sock is None:
return 1
self.server.send_message(sock, "PING")
return 0
# Format a message before sending to client(s)
# Prepends message size code along with replacing variables in message
def format_message(self, message, *args):
formatted = None
if len(args) > 0:
formatted = message.format(*args) + self.server.end_of_line
else:
formatted = message + self.server.end_of_line
# Puts message size at the front of the message
prefixed = pack(">I", len(formatted)) + formatted.encode()
return prefixed
# Get message from socket with `format_message` in mind
def get_message(self, sock):
raw_message_size = self.server.receive_bytes(sock, 4, False)
if raw_message_size is None:
return None
message_size = unpack(">I", raw_message_size)[0]
if self.max_message_size > 0 and message_size > self.max_message_size:
return None
eol = self.server.end_of_line
return self.server.receive_bytes(sock, message_size).strip(eol)
# Takes the server object, the client socket, and a message to process
# Each message returns a status code:
# -1 : Going offline
# 0 : Success
# 1 : Failure
# 2 : Invalid
def message(self, sock, message):
# print("DEBUG:", message)
send = self.send_verb + " "
cast = self.broadcast_verb + " "
send_size = len(send)
cast_size = len(cast)
# React to heartbeat from client
if message == "PONG":
client_fd = self.server.get_client_fd(sock)
client = self.server.clients.get(client_fd, dict())
client_alive = client.get("alive", None)
if client_aliave is None:
return 1
elif client_alive:
return 1
# Setting this to True is what tells the server the heartbeat worked
client["alive"] = True
return 0
client_address = [a for fd, a in server.lookup if fd == client_fd][0]
client_name = "{}:{}".format(*client_address)
server._broadcast_message(server.sock, "LEAVE {}".format(client_name))
server._close_sock(sock)
return 1
elif message == "LIST":
fds = list() #list(map(str, server.clients.keys()))
client_fd = server._get_client_fd(sock)
for fd in server.clients.keys():
if fd == server.sock_fd:
fds.append("*{}".format(fd))
elif fd == client_fd:
fds.append("+{}".format(fd))
else:
fds.append(str(fd))
server._send_message(sock, ",".join(fds))
return 1
elif message[:5] == "SEND ":
params = message[5:].split(" ", 1)
if len(params) < 2:
# Tell the clients to stop before server itself stops
elif message == self.close_verb:
status = self.close_server()
self.server.stop()
return status
# Informs the other clients one left and closes that client's socket
elif message == "QUIT":
client_alias = self.server.get_client_alias_by_sock(sock)
if client_alias is None:
return 1
return self.close_client(client_alias)
# Lists all client alises, puts itself first and the server second
elif message == "LIST":
aliases = list()
client_alias = self.server.get_client_alias_by_sock(sock)
if client_alias is None:
return 1
self.server_alias = self.server.sock_alias
for alias in self.server.clients.keys():
# We need to skip ints since the file descriptors are also keys
if type(alias) is int:
continue
# The server and sending client are skipped to retain ordering
elif alias == self.server.sock_alias:
continue
elif alias == client_alias:
continue
else:
aliases.append(alias)
listed = ",".join([client_alias, self.server_alias] + aliases)
self.server.send_message(sock, listed)
return 0
fd, response = params
client_sock = server.clients.get(int(fd), dict()).get("sock", None)
if client_sock is None:
# Sends a message to the client with the specified client (via an alias)
elif message[:send_size] == send:
params = message[(send_size + 1):].split(" ", 1)
if len(params) < 2:
return 1
alias, response = params
client = self.server.clients.get(alias, dict())
client_sock = client.get("sock", None)
if client_sock is None:
return 1
self.server.send_message(client_sock, response)
return 0
server._send_message(client_sock, response)
return 1
elif message[:6] == "BCAST ":
response = message[6:]
server._broadcast_message(sock, response)
return 1
else:
return 2
# Broadcasts a message to all other clients
elif message[:cast_size] == cast:
response = message[(cast_size + 1):]
self.server.broadcast_message(sock, response)
return 0
# All other commands are invalid
else:
return 2

1
abots/ui/__init__.py Normal file
View File

@ -0,0 +1 @@
from abots.ui.tui import TUI

18
abots/ui/tui.py Normal file
View File

@ -0,0 +1,18 @@
from curses import initscr, noecho, cbreak
"""
ui\TUI: Text User Interface
===========================
The curses library is one of those things I always wanted to use, but never got
around to it.
That ends here, as this script will try to take curses and abstract it into a
nice framework I can re-use without needing to pull out a manual for curses to
figure out exactly what everything does (which is what I will be doing during
the duration of writing this script).
"""
class TUI:
pass

View File

@ -1,3 +1,5 @@
pytest
python-gnupg
cryptography
flask
websockets