"""Sputnik Client Implementation
This module provides the Sputnik Client implementation. This is a subclass of
a Connection, and defines an interface to IRC client applications implementing
_RFC 2812: https://tools.ietf.org/html/rfc2812 .
"""
from connection import Connection
[docs]class Client(Connection):
"""An instance of a connection from an IRC client.
A Client is the product of an asyncio protocol factory, and represents
an instance of a connection from an IRC client to the listen server. It
does not implement an actual IRC client, as defined in
_RFC 2812: https://tools.ietf.org/html/rfc2812 .
Attributes:
bouncer (sputnik.Bouncer): A reference to the Bouncer singleton.
broker (sputnik.Network): The connected Network instance.
network (str): The name of the IRC network to connect to.
ready (bool): Indicates if the Client has connected to a Network.
"""
def __init__(self, bouncer):
"""Creates an instance of a Client.
The Network and Broker attributes are defined here in order to support
Look-Before-You-Leap (LBYL). This is an explicit design decision made
in favor of simplicity and readability. The bouncer reference is needed
in order to access the list of connected Networks.
Args:
bouncer (sputnik.Bouncer): The singleton Bouncer instance.
"""
self.bouncer = bouncer
self.network = None
self.broker = None
[docs] def connection_made(self, transport):
"""Registers the connected Client with the Bouncer.
Adds the Client to the set of connected Clients in the Bouncer and
saves the transport for later use.
"""
print("Client Connected to Bouncer")
self.bouncer.clients.add(self)
self.transport = transport
self.ready = False
[docs] def connection_lost(self, exc):
"""Unregister the connected Client from the Bouncer.
Removes the Client from the set of connected Clients in the Bouncer
before the connection is terminated. After this point, there should be
no remaining references to this instance of the Client.
"""
print("Client Disconnected from Bouncer")
self.bouncer.clients.remove(self)
[docs] def data_received(self, data):
"""Handles incoming messages from connected IRC clients.
Messages coming from IRC clients are potentially batched, and need to
be parsed into individual lines before any other operation may occur.
Afterwards, we split lines according to the IRC message format and then
perform actions as appropriate.
"""
for line in self.decode(data).rstrip().split("\r\n"):
l = line.split(" ", 1)
if l[0] == "QUIT": pass # Suppress the QUIT Command
elif l[0] == "USER":
self.network = l[1].split(" ")[0]
if self.network not in self.bouncer.networks:
self.send("This Network Does Not Exist")
else: self.broker = self.bouncer.networks[self.network]
elif l[0] == "JOIN":
for channel in l[1].split(","):
channel = channel.split(" ")
password = channel[1] if len(channel) > 1 else None
if self.bouncer.datastore:
self.bouncer.datastore.add_channel(
self.network, channel[0], password)
self.forward(line)
self.forward("WHO", channel[0])
elif l[0] == "PART":
if self.bouncer.datastore:
self.bouncer.datastore.remove_channel(
self.network, l[1].split(" ")[0])
self.forward(line)
else: self.forward(line)
if self.broker and not self.ready:
for line in self.broker.server_log: self.send(line)
self.ready = True
[docs] def forward(self, *args):
"""Writes a message to the Network.
Because the Client represents an instance of a connection from an IRC
client, we instead need to write to the transport associated with the
connected network.
Args:
args (list of str): A list of strings to concatenate.
"""
message = self.normalize(" ".join(args))
if self.broker:
self.broker.transport.write(message.encode())
print("[C to B]\t%s" % message, end="")