174 lines
5.6 KiB
Python
174 lines
5.6 KiB
Python
# -*- 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 logging
|
|
import socket
|
|
import threading
|
|
|
|
class Server(threading.Thread):
|
|
def __init__(self, socket = None):
|
|
self.stop = False
|
|
self.stopping = threading.Event()
|
|
self.s = socket
|
|
self.connected = self.s is not None
|
|
self.closing_event = None
|
|
|
|
self.moremessages = dict()
|
|
|
|
self.logger = logging.getLogger(__name__ + "/" + self.id)
|
|
|
|
threading.Thread.__init__(self)
|
|
|
|
def isDCC(self, to=None):
|
|
return to is not None and to in self.dcc_clients
|
|
|
|
@property
|
|
def ip(self):
|
|
"""Convert common IP representation to little-endian integer representation"""
|
|
sum = 0
|
|
if self.node.hasAttribute("ip"):
|
|
ip = self.node["ip"]
|
|
else:
|
|
#TODO: find the external IP
|
|
ip = "0.0.0.0"
|
|
for b in ip.split("."):
|
|
sum = 256 * sum + int(b)
|
|
return sum
|
|
|
|
def toIP(self, input):
|
|
"""Convert little-endian int to IPv4 adress"""
|
|
ip = ""
|
|
for i in range(0,4):
|
|
mod = input % 256
|
|
ip = "%d.%s" % (mod, ip)
|
|
input = (input - mod) / 256
|
|
return ip[:len(ip) - 1]
|
|
|
|
@property
|
|
def id(self):
|
|
"""Gives the server identifiant"""
|
|
raise NotImplemented()
|
|
|
|
def accepted_channel(self, msg, sender=None):
|
|
return True
|
|
|
|
def msg_treated(self, origin):
|
|
"""Action done on server when a message was treated"""
|
|
raise NotImplemented()
|
|
|
|
def send_response(self, res, origin):
|
|
"""Analyse a Response and send it"""
|
|
# TODO: how to send a CTCP message to a different person
|
|
if res.ctcp:
|
|
self.send_ctcp(res.sender, res.get_message())
|
|
|
|
elif res.channel is not None and res.channel != self.nick:
|
|
self.send_msg(res.channel, res.get_message())
|
|
|
|
if not res.alone:
|
|
if hasattr(self, "send_bot"):
|
|
self.send_bot("NOMORE %s" % res.channel)
|
|
self.moremessages[res.channel] = res
|
|
elif res.sender is not None:
|
|
self.send_msg_usr(res.sender, res.get_message())
|
|
|
|
if not res.alone:
|
|
self.moremessages[res.sender] = res
|
|
|
|
def send_ctcp(self, to, msg, cmd="NOTICE", endl="\r\n"):
|
|
"""Send a message as CTCP response"""
|
|
if msg is not None and to is not None:
|
|
for line in msg.split("\n"):
|
|
if line != "":
|
|
self.send_msg_final(to.split("!")[0], "\x01" + line + "\x01", cmd, endl)
|
|
|
|
def send_dcc(self, msg, to):
|
|
"""Send a message through DCC connection"""
|
|
raise NotImplemented()
|
|
|
|
def send_msg_final(self, channel, msg, cmd="PRIVMSG", endl="\r\n"):
|
|
"""Send a message without checks or format"""
|
|
raise NotImplemented()
|
|
|
|
def send_msg_usr(self, user, msg):
|
|
"""Send a message to a user instead of a channel"""
|
|
raise NotImplemented()
|
|
|
|
def send_msg(self, channel, msg, cmd="PRIVMSG", endl="\r\n"):
|
|
"""Send a message to a channel"""
|
|
if msg is not None:
|
|
for line in msg.split("\n"):
|
|
if line != "":
|
|
self.send_msg_final(channel, line, cmd, endl)
|
|
|
|
def send_msg_verified(self, sender, channel, msg, cmd="PRIVMSG", endl="\r\n"):
|
|
"""A more secure way to send messages"""
|
|
raise NotImplemented()
|
|
|
|
def send_global(self, msg, cmd="PRIVMSG", endl="\r\n"):
|
|
"""Send a message to all channels on this server"""
|
|
raise NotImplemented()
|
|
|
|
def disconnect(self):
|
|
"""Close the socket with the server"""
|
|
if self.connected:
|
|
self.stop = True
|
|
try:
|
|
self.s.shutdown(socket.SHUT_RDWR)
|
|
except socket.error:
|
|
pass
|
|
|
|
self.stopping.wait()
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def kill(self):
|
|
"""Just stop the main loop, don't close the socket directly"""
|
|
if self.connected:
|
|
self.stop = True
|
|
self.connected = False
|
|
#Send a message in order to close the socket
|
|
try:
|
|
self.s.send(("Bye!\r\n").encode ())
|
|
except:
|
|
pass
|
|
self.stopping.wait()
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def launch(self, receive_action, verb=True):
|
|
"""Connect to the server if it is no yet connected"""
|
|
self._receive_action = receive_action
|
|
if not self.connected:
|
|
self.stop = False
|
|
self.logger.info("Entering main loop for server")
|
|
try:
|
|
self.start()
|
|
except RuntimeError:
|
|
pass
|
|
elif verb:
|
|
print (" Already connected.")
|
|
|
|
def treat_msg(self, line, private=False):
|
|
self._receive_action(self, line, private)
|
|
|
|
def run(self):
|
|
raise NotImplemented()
|