[wip] move files in order to have a clean directory structure

This commit is contained in:
nemunaire 2015-01-03 18:49:01 +01:00
commit 41f7dc2456
31 changed files with 0 additions and 0 deletions

241
nemubot/server/DCC.py Normal file
View file

@ -0,0 +1,241 @@
# -*- coding: utf-8 -*-
# Nemubot is a modulable IRC bot, built around XML configuration files.
# Copyright (C) 2012 Mercier Pierre-Olivier
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import imp
import os
import re
import socket
import sys
import time
import threading
import traceback
import message
import server
#Store all used ports
PORTS = list()
class DCC(server.AbstractServer):
def __init__(self, srv, dest, socket=None):
server.Server.__init__(self)
self.error = False # An error has occur, closing the connection?
self.messages = list() # Message queued before connexion
# Informations about the sender
self.sender = dest
if self.sender is not None:
self.nick = (self.sender.split('!'))[0]
if self.nick != self.sender:
self.realname = (self.sender.split('!'))[1]
else:
self.realname = self.nick
# Keep the server
self.srv = srv
self.treatement = self.treat_msg
# Found a port for the connection
self.port = self.foundPort()
if self.port is None:
self.logger.critical("No more available slot for DCC connection")
self.setError("Il n'y a plus de place disponible sur le serveur"
" pour initialiser une session DCC.")
def foundPort(self):
"""Found a free port for the connection"""
for p in range(65432, 65535):
if p not in PORTS:
PORTS.append(p)
return p
return None
@property
def id(self):
"""Gives the server identifiant"""
return self.srv.id + "/" + self.sender
def setError(self, msg):
self.error = True
self.srv.send_msg_usr(self.sender, msg)
def accept_user(self, host, port):
"""Accept a DCC connection"""
self.s = socket.socket()
try:
self.s.connect((host, port))
self.logger.info("Accepted user from %s:%d for %s", host, port, self.sender)
self.connected = True
self.stop = False
except:
self.connected = False
self.error = True
return False
self.start()
return True
def request_user(self, type="CHAT", filename="CHAT", size=""):
"""Create a DCC connection"""
#Open the port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.bind(('', self.port))
except:
try:
self.port = self.foundPort()
s.bind(('', self.port))
except:
self.setError("Une erreur s'est produite durant la tentative"
" d'ouverture d'une session DCC.")
return False
self.logger.info("Listening on %d for %s", self.port, self.sender)
#Send CTCP request for DCC
self.srv.send_ctcp(self.sender,
"DCC %s %s %d %d %s" % (type, filename, self.srv.ip,
self.port, size),
"PRIVMSG")
s.listen(1)
#Waiting for the client
(self.s, addr) = s.accept()
self.logger.info("Connected by %d", addr)
self.connected = True
return True
def send_dcc_raw(self, line):
self.s.sendall(line + b'\n')
def send_dcc(self, msg, to = None):
"""If we talk to this user, send a message through this connection
else, send the message to the server class"""
if to is None or to == self.sender or to == self.nick:
if self.error:
self.srv.send_msg_final(self.nick, msg)
elif not self.connected or self.s is None:
try:
self.start()
except RuntimeError:
pass
self.messages.append(msg)
else:
for line in msg.split("\n"):
self.send_dcc_raw(line.encode())
else:
self.srv.send_dcc(msg, to)
def send_file(self, filename):
"""Send a file over DCC"""
if os.path.isfile(filename):
self.messages = filename
try:
self.start()
except RuntimeError:
pass
else:
self.logger.error("File not found `%s'", filename)
def run(self):
self.stopping.clear()
# Send file connection
if not isinstance(self.messages, list):
self.request_user("SEND",
os.path.basename(self.messages),
os.path.getsize(self.messages))
if self.connected:
with open(self.messages, 'rb') as f:
d = f.read(268435456) #Packets size: 256Mo
while d:
self.s.sendall(d)
self.s.recv(4) #The client send a confirmation after each packet
d = f.read(268435456) #Packets size: 256Mo
# Messages connection
else:
if not self.connected:
if not self.request_user():
#TODO: do something here
return False
#Start by sending all queued messages
for mess in self.messages:
self.send_dcc(mess)
time.sleep(1)
readbuffer = b''
self.nicksize = len(self.srv.nick)
self.Bnick = self.srv.nick.encode()
while not self.stop:
raw = self.s.recv(1024) #recieve server messages
if not raw:
break
readbuffer = readbuffer + raw
temp = readbuffer.split(b'\n')
readbuffer = temp.pop()
for line in temp:
self.treatement(line)
if self.connected:
self.s.close()
self.connected = False
#Remove from DCC connections server list
if self.realname in self.srv.dcc_clients:
del self.srv.dcc_clients[self.realname]
self.logger.info("Closing connection with %s", self.nick)
self.stopping.set()
if self.closing_event is not None:
self.closing_event()
#Rearm Thread
threading.Thread.__init__(self)
def treat_msg(self, line):
"""Treat a receive message, *can be overwritten*"""
if line == b'NEMUBOT###':
bot = self.srv.add_networkbot(self.srv, self.sender, self)
self.treatement = bot.treat_msg
self.send_dcc("NEMUBOT###")
elif (line[:self.nicksize] == self.Bnick and
line[self.nicksize+1:].strip()[:10] == b'my name is'):
name = line[self.nicksize+1:].strip()[11:].decode('utf-8',
'replace')
if re.match("^[a-zA-Z0-9_-]+$", name):
if name not in self.srv.dcc_clients:
del self.srv.dcc_clients[self.sender]
self.nick = name
self.sender = self.nick + "!" + self.realname
self.srv.dcc_clients[self.realname] = self
self.send_dcc("Hi " + self.nick)
else:
self.send_dcc("This nickname is already in use"
", please choose another one.")
else:
self.send_dcc("The name you entered contain"
" invalid char.")
else:
self.srv.treat_msg(
(":%s PRIVMSG %s :" % (
self.sender,self.srv.nick)).encode() + line,
True)

433
nemubot/server/IRC.py Normal file
View file

@ -0,0 +1,433 @@
# -*- coding: utf-8 -*-
# Nemubot is a smart and modulable IM bot.
# Copyright (C) 2012-2014 nemunaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import calendar
from datetime import datetime, timezone
import ipaddress
import re
import time
import shlex
from channel import Channel
import message
from message.printer.IRC import IRC as IRCPrinter
from server.socket import SocketServer
class IRC(SocketServer):
"""Concrete implementation of a connexion to an IRC server"""
def __init__(self, owner, nick="nemubot", host="localhost", port=6667,
ssl=False, username=None, password=None, realname="Nemubot",
encoding="utf-8", caps=None, channels=list(),
on_connect=None):
"""Prepare a connection with an IRC server
Keyword arguments:
owner -- bot's owner
nick -- bot's nick
host -- host to join
port -- port on the host to reach
ssl -- is this server using a TLS socket
username -- the username as sent to server
password -- if a password is required to connect to the server
realname -- the bot's realname
encoding -- the encoding used on the whole server
caps -- client capabilities to register on the server
channels -- list of channels to join on connection (if a channel is password protected, give a tuple: (channel_name, password))
on_connect -- generator to call when connection is done
"""
self.username = username if username is not None else nick
self.password = password
self.nick = nick
self.owner = owner
self.realname = realname
self.id = self.username + "@" + host + ":" + port
self.printer = IRCPrinter
SocketServer.__init__(self, host=host, port=port, ssl=ssl)
self.encoding = encoding
# Keep a list of joined channels
self.channels = dict()
# Server/client capabilities
self.capabilities = caps
# Register CTCP capabilities
self.ctcp_capabilities = dict()
def _ctcp_clientinfo(msg, cmds):
"""Response to CLIENTINFO CTCP message"""
return " ".join(self.ctcp_capabilities.keys())
def _ctcp_dcc(msg, cmds):
"""Response to DCC CTCP message"""
try:
ip = ipaddress.ip_address(int(cmds[3]))
port = int(cmds[4])
conn = DCC(srv, msg.sender)
except:
return "ERRMSG invalid parameters provided as DCC CTCP request"
self.logger.info("Receive DCC connection request from %s to %s:%d", conn.sender, ip, port)
if conn.accept_user(ip, port):
srv.dcc_clients[conn.sender] = conn
conn.send_dcc("Hello %s!" % conn.nick)
else:
self.logger.error("DCC: unable to connect to %s:%d", ip, port)
return "ERRMSG unable to connect to %s:%d" % (ip, port)
import bot
self.ctcp_capabilities["ACTION"] = lambda msg, cmds: print ("ACTION receive: %s" % cmds)
self.ctcp_capabilities["CLIENTINFO"] = _ctcp_clientinfo
#self.ctcp_capabilities["DCC"] = _ctcp_dcc
self.ctcp_capabilities["FINGER"] = lambda msg, cmds: "VERSION nemubot v%s" % bot.__version__
self.ctcp_capabilities["NEMUBOT"] = lambda msg, cmds: "NEMUBOT %s" % bot.__version__
self.ctcp_capabilities["PING"] = lambda msg, cmds: "PING %s" % " ".join(cmds[1:])
self.ctcp_capabilities["SOURCE"] = lambda msg, cmds: "SOURCE https://github.com/nemunaire/nemubot"
self.ctcp_capabilities["TIME"] = lambda msg, cmds: "TIME %s" % (datetime.now())
self.ctcp_capabilities["USERINFO"] = lambda msg, cmds: "USERINFO %s" % self.realname
self.ctcp_capabilities["VERSION"] = lambda msg, cmds: "VERSION nemubot v%s" % bot.__version__
# TODO: Temporary fix, waiting for hook based CTCP management
self.ctcp_capabilities["TYPING"] = lambda msg, cmds: None
self.logger.debug("CTCP capabilities setup: %s", ", ".join(self.ctcp_capabilities))
# Register hooks on some IRC CMD
self.hookscmd = dict()
# Respond to PING
def _on_ping(msg):
self.write(b"PONG :" + msg.params[0])
self.hookscmd["PING"] = _on_ping
# Respond to 001
def _on_connect(msg):
# First, send user defined command
if on_connect is not None:
for oc in on_connect():
self.write(oc)
# Then, JOIN some channels
for chn in channels:
if isinstance(chn, tuple):
self.write("JOIN %s %s" % chn)
else:
self.write("JOIN %s" % chn)
self.hookscmd["001"] = _on_connect
# Respond to ERROR
def _on_error(msg):
self.close()
self.hookscmd["ERROR"] = _on_error
# Respond to CAP
def _on_cap(msg):
if len(msg.params) != 3 or msg.params[1] != b"LS":
return
server_caps = msg.params[2].decode().split(" ")
for cap in self.capabilities:
if cap not in server_caps:
self.capabilities.remove(cap)
if len(self.capabilities) > 0:
self.write("CAP REQ :" + " ".join(self.capabilities))
self.write("CAP END")
self.hookscmd["CAP"] = _on_cap
# Respond to JOIN
def _on_join(msg):
if len(msg.params) == 0:
return
for chname in msg.decode(msg.params[0]).split(","):
# Register the channel
chan = Channel(chname)
self.channels[chname] = chan
self.hookscmd["JOIN"] = _on_join
# Respond to PART
def _on_part(msg):
if len(msg.params) != 1 and len(msg.params) != 2:
return
for chname in msg.params[0].split(b","):
if chname in self.channels:
if msg.nick == self.nick:
del self.channels[chname]
elif msg.nick in self.channels[chname].people:
del self.channels[chname].people[msg.nick]
self.hookscmd["PART"] = _on_part
# Respond to 331/RPL_NOTOPIC,332/RPL_TOPIC,TOPIC
def _on_topic(msg):
if len(msg.params) != 1 and len(msg.params) != 2:
return
if msg.params[0] in self.channels:
if len(msg.params) == 1 or len(msg.params[1]) == 0:
self.channels[msg.params[0]].topic = None
else:
self.channels[msg.params[0]].topic = msg.decode(msg.params[1])
self.hookscmd["331"] = _on_topic
self.hookscmd["332"] = _on_topic
self.hookscmd["TOPIC"] = _on_topic
# Respond to 353/RPL_NAMREPLY
def _on_353(msg):
if len(msg.params) == 3:
msg.params.pop(0) # 353: like RFC 1459
if len(msg.params) != 2:
return
if msg.params[0] in self.channels:
for nk in msg.decode(msg.params[1]).split(" "):
res = re.match("^(?P<level>[^a-zA-Z[\]\\`_^{|}])(?P<nickname>[a-zA-Z[\]\\`_^{|}][a-zA-Z0-9[\]\\`_^{|}-]*)$")
self.channels[msg.params[0]].people[res.group("nickname")] = res.group("level")
self.hookscmd["353"] = _on_353
# Respond to INVITE
def _on_invite(msg):
if len(msg.params) != 2:
return
self.write("JOIN " + msg.decode(msg.params[1]))
self.hookscmd["INVITE"] = _on_invite
# Respond to ERR_NICKCOLLISION
def _on_nickcollision(msg):
self.nick += "_"
self.write("NICK " + self.nick)
self.hookscmd["433"] = _on_nickcollision
self.hookscmd["436"] = _on_nickcollision
# Handle CTCP requests
def _on_ctcp(msg):
if len(msg.params) != 2 or not msg.is_ctcp:
return
cmds = msg.decode(msg.params[1][1:len(msg.params[1])-1]).split(' ')
if cmds[0] in self.ctcp_capabilities:
res = self.ctcp_capabilities[cmds[0]](msg, cmds)
else:
res = "ERRMSG Unknown or unimplemented CTCP request"
if res is not None:
self.write("NOTICE %s :\x01%s\x01" % (msg.nick, res))
self.hookscmd["PRIVMSG"] = _on_ctcp
# Open/close
def _open(self):
if SocketServer._open(self):
if self.password is not None:
self.write("PASS :" + self.password)
if self.capabilities is not None:
self.write("CAP LS")
self.write("NICK :" + self.nick)
self.write("USER %s %s bla :%s" % (self.username, self.host, self.realname))
return True
return False
def _close(self):
if self.connected: self.write("QUIT")
return SocketServer._close(self)
# Writes: as inherited
# Read
def read(self):
for line in SocketServer.read(self):
msg = IRCMessage(line, self.encoding)
if msg.cmd in self.hookscmd:
self.hookscmd[msg.cmd](msg)
mes = msg.to_message(self)
if mes is not None:
yield mes
# Parsing stuff
mgx = re.compile(b'''^(?:@(?P<tags>[^ ]+)\ )?
(?::(?P<prefix>
(?P<nick>[^!@ ]+)
(?: !(?P<user>[^@ ]+))?
(?:@(?P<host>[^ ]+))?
)\ )?
(?P<command>(?:[a-zA-Z]+|[0-9]{3}))
(?P<params>(?:\ [^:][^ ]*)*)(?:\ :(?P<trailing>.*))?
$''', re.X)
class IRCMessage:
"""Class responsible for parsing IRC messages"""
def __init__(self, raw, encoding="utf-8"):
self.encoding = encoding
self.tags = { 'time': datetime.now(timezone.utc) }
self.params = list()
p = mgx.match(raw.rstrip())
if p is None:
raise Exception("Not a valid IRC message: %s" % raw)
# Parse tags if exists: @aaa=bbb;ccc;example.com/ddd=eee
if p.group("tags"):
for tgs in self.decode(p.group("tags")).split(';'):
tag = tgs.split('=')
if len(tag) > 1:
self.add_tag(tag[0], tag[1])
else:
self.add_tag(tag[0])
# Parse prefix if exists: :nick!user@host.com
self.prefix = self.decode(p.group("prefix"))
self.nick = self.decode(p.group("nick"))
self.user = self.decode(p.group("user"))
self.host = self.decode(p.group("host"))
# Parse command
self.cmd = self.decode(p.group("command"))
# Parse params
if p.group("params") is not None and p.group("params") != b'':
for param in p.group("params").strip().split(b' '):
self.params.append(param)
if p.group("trailing") is not None:
self.params.append(p.group("trailing"))
def add_tag(self, key, value=None):
"""Add an IRCv3.2 Message Tags
Arguments:
key -- tag identifier (unique for the message)
value -- optional value for the tag
"""
# Treat special tags
if key == "time":
value = datetime.fromtimestamp(calendar.timegm(time.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ")), timezone.utc)
# Store tag
self.tags[key] = value
@property
def is_ctcp(self):
"""Analyze a message, to determine if this is a CTCP one"""
return self.cmd == "PRIVMSG" and len(self.params) == 2 and len(self.params[1]) > 1 and (self.params[1][0] == 0x01 or self.params[1][1] == 0x01)
def decode(self, s):
"""Decode the content string usign a specific encoding
Argument:
s -- string to decode
"""
if isinstance(s, bytes):
try:
s = s.decode()
except UnicodeDecodeError:
s = s.decode(self.encoding, 'replace')
return s
def to_irc_string(self, client=True):
"""Pretty print the message to close to original input string
Keyword argument:
client -- export as a client-side string if true
"""
res = ";".join(["@%s=%s" % (k, v if not isinstance(v, datetime) else v.strftime("%Y-%m-%dT%H:%M:%S.%fZ")) for k, v in self.tags.items()])
if not client:
res += " :%s!%s@%s" % (self.nick, self.user, self.host)
res += " " + self.cmd
if len(self.params) > 0:
if len(self.params) > 1:
res += " " + self.decode(b" ".join(self.params[:-1]))
res += " :" + self.decode(self.params[-1])
return res
def to_message(self, srv):
"""Convert to one of concrete implementation of AbstractMessage
Argument:
srv -- the server from the message was received
"""
if self.cmd == "PRIVMSG" or self.cmd == "NOTICE":
receivers = self.decode(self.params[0]).split(',')
common_args = {
"server": srv.id,
"date": self.tags["time"],
"to": receivers,
"to_response": [r if r != srv.nick else self.nick for r in receivers],
"frm": self.nick
}
# If CTCP, remove 0x01
if self.is_ctcp:
text = self.decode(self.params[1][1:len(self.params[1])-1])
else:
text = self.decode(self.params[1])
if text.find(srv.nick) == 0 and len(text) > len(srv.nick) + 2 and text[len(srv.nick)] == ":":
designated = srv.nick
text = text[len(srv.nick) + 1:].strip()
else:
designated = None
# Is this a command?
if len(text) > 1 and text[0] == '!':
text = text[1:].strip()
# Split content by words
try:
args = shlex.split(text)
except ValueError:
args = text.split(' ')
return message.Command(cmd=args[0], args=args[1:], **common_args)
# Is this an ask for this bot?
elif designated is not None:
return message.DirectAsk(designated=designated, message=text, **common_args)
# Normal message
else:
return message.TextMessage(message=text, **common_args)
return None

161
nemubot/server/__init__.py Normal file
View file

@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
# Nemubot is a smart and modulable IM bot.
# Copyright (C) 2012-2014 nemunaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
import imp
import logging
import socket
import queue
# Lists for select
_rlist = []
_wlist = []
_xlist = []
# Extends from IOBase in order to be compatible with select function
class AbstractServer(io.IOBase):
"""An abstract server: handle communication with an IM server"""
def __init__(self, send_callback=None):
"""Initialize an abstract server
Keyword argument:
send_callback -- Callback when developper want to send a message
"""
if not hasattr(self, "id"):
raise Exception("No id defined for this server. Please set one!")
self.logger = logging.getLogger("nemubot.server." + self.id)
self._sending_queue = queue.Queue()
if send_callback is not None:
self._send_callback = send_callback
else:
self._send_callback = self._write_select
# Open/close
def __enter__(self):
self.open()
return self
def __exit__(self, type, value, traceback):
self.close()
def open(self):
"""Generic open function that register the server un _rlist in case
of successful _open"""
self.logger.info("Opening connection to %s", self.id)
if not hasattr(self, "_open") or self._open():
_rlist.append(self)
_xlist.append(self)
return True
return False
def close(self):
"""Generic close function that register the server un _{r,w,x}list in
case of successful _close"""
self.logger.info("Closing connection to %s", self.id)
if not hasattr(self, "_close") or self._close():
if self in _rlist:
_rlist.remove(self)
if self in _wlist:
_wlist.remove(self)
if self in _xlist:
_xlist.remove(self)
return True
return False
# Writes
def write(self, message):
"""Asynchronymously send a message to the server using send_callback
Argument:
message -- message to send
"""
self._send_callback(message)
def write_select(self):
"""Internal function used by the select function"""
try:
_wlist.remove(self)
while not self._sending_queue.empty():
self._write(self._sending_queue.get_nowait())
self._sending_queue.task_done()
except queue.Empty:
pass
def _write_select(self, message):
"""Send a message to the server safely through select
Argument:
message -- message to send
"""
self._sending_queue.put(self.format(message))
self.logger.debug("Message '%s' appended to Queue", message)
if self not in _wlist:
_wlist.append(self)
def send_response(self, response):
"""Send a formated Message class
Argument:
response -- message to send
"""
if response is None:
return
elif isinstance(response, list):
for r in response:
self.send_response(r)
else:
vprnt = self.printer()
response.accept(vprnt)
self.write(vprnt.pp)
# Exceptions
def exception(self):
"""Exception occurs in fd"""
self.logger.warning("Unhandle file descriptor exception on server %s",
self.id)
def reload():
import server.socket
imp.reload(server.socket)
import server.IRC
imp.reload(server.IRC)

112
nemubot/server/socket.py Normal file
View file

@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
# Nemubot is a smart and modulable IM bot.
# Copyright (C) 2012-2014 nemunaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import ssl
import socket
from server import AbstractServer
class SocketServer(AbstractServer):
"""Concrete implementation of a socket connexion (can be wrapped with TLS)"""
def __init__(self, host, port, ssl=False):
AbstractServer.__init__(self)
self.host = host
self.port = int(port)
self.ssl = ssl
self.socket = None
self.readbuffer = b''
def fileno(self):
return self.socket.fileno() if self.socket else None
@property
def connected(self):
"""Indicator of the connection aliveness"""
return self.socket is not None
# Open/close
def _open(self):
# Create the socket
self.socket = socket.socket()
# Wrap the socket for SSL
if self.ssl:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
self.socket = ctx.wrap_socket(self.socket)
try:
self.socket.connect((self.host, self.port)) # Connect to server
self.logger.info("Connected to %s:%d", self.host, self.port)
except socket.error as e:
self.socket = None
self.logger.critical("Unable to connect to %s:%d: %s",
self.host, self.port,
os.strerror(e.errno))
return False
return True
def _close(self):
self._sending_queue.join()
if self.connected:
try:
self.socket.shutdown(socket.SHUT_RDWR)
self.socket.close()
except socket.error:
pass
self.socket = None
return True
# Write
def _write(self, cnt):
if not self.connected:
return
self.socket.send(cnt)
def format(self, txt):
if isinstance(txt, bytes):
return txt + b'\r\n'
else:
return txt.encode() + b'\r\n'
# Read
def read(self):
if not self.connected:
return
raw = self.socket.recv(1024)
temp = (self.readbuffer + raw).split(b'\r\n')
self.readbuffer = temp.pop()
for line in temp:
yield line