2012-03-02 04:31:57 +00:00
from twisted . words . protocols import irc
from twisted . internet import reactor , protocol
2012-03-03 01:42:01 +00:00
from twisted . internet . protocol import ReconnectingClientFactory
2012-03-02 04:31:57 +00:00
from twisted . python import log
2012-03-02 17:24:25 +00:00
from twisted . internet . endpoints import clientFromString
2013-01-20 05:31:23 +00:00
from twisted . internet . task import LoopingCall
2012-03-03 03:16:43 +00:00
from twisted . application import service
2012-03-02 17:24:25 +00:00
from signal import signal , SIGINT
2012-03-03 06:41:51 +00:00
from ConfigParser import SafeConfigParser
2012-03-03 03:54:28 +00:00
import re , sys
2012-03-02 04:31:57 +00:00
2012-03-03 03:54:28 +00:00
#
# RelayBot is a derivative of http://code.google.com/p/relaybot/
#
2012-03-02 04:31:57 +00:00
log . startLogging ( sys . stdout )
2012-03-03 03:56:25 +00:00
__version__ = " 0.1 "
2012-03-03 03:16:43 +00:00
application = service . Application ( " RelayBot " )
2012-03-02 04:31:57 +00:00
2012-03-03 01:53:32 +00:00
def main ( ) :
2012-03-03 06:41:51 +00:00
config = SafeConfigParser ( )
config . read ( " relaybot.config " )
defaults = config . defaults ( )
2012-07-16 22:06:00 +00:00
2012-03-03 06:41:51 +00:00
for section in config . sections ( ) :
2012-07-16 22:06:00 +00:00
2012-03-04 19:51:06 +00:00
def get ( option ) :
2012-07-04 00:14:14 +00:00
if option in defaults or config . has_option ( section , option ) :
return config . get ( section , option ) or defaults [ option ]
else :
return None
2012-07-16 22:06:00 +00:00
2012-03-11 05:55:48 +00:00
options = { }
2015-02-02 00:09:12 +00:00
for option in [ " timeout " , " host " , " port " , " nick " , " channel " , " heartbeat " , " password " , " username " , " realname " ] :
2012-03-11 05:55:48 +00:00
options [ option ] = get ( option )
2012-07-16 22:06:00 +00:00
2012-03-11 05:55:48 +00:00
mode = get ( " mode " )
2012-07-16 22:06:00 +00:00
2012-03-03 01:53:32 +00:00
#Not using endpoints pending http://twistedmatrix.com/trac/ticket/4735
#(ReconnectingClientFactory equivalent for endpoints.)
factory = None
2012-03-11 07:10:29 +00:00
if mode == " Default " :
2012-03-04 19:42:46 +00:00
factory = RelayFactory
2012-03-11 07:10:29 +00:00
elif mode == " FLIP " :
factory = FLIPFactory
elif mode == " NickServ " :
factory = NickServFactory
options [ " nickServPassword " ] = get ( " nickServPassword " )
2013-01-20 06:16:24 +00:00
elif mode == " ReadOnly " :
factory = ReadOnlyFactory
options [ " nickServPassword " ] = get ( " nickServPassword " )
2012-07-16 22:06:00 +00:00
2012-03-11 05:55:48 +00:00
factory = factory ( options )
reactor . connectTCP ( options [ ' host ' ] , int ( options [ ' port ' ] ) , factory , int ( options [ ' timeout ' ] ) )
2012-07-16 22:06:00 +00:00
2012-03-03 01:53:32 +00:00
reactor . callWhenRunning ( signal , SIGINT , handler )
2012-03-02 18:09:00 +00:00
class Communicator :
def __init__ ( self ) :
self . protocolInstances = { }
def register ( self , protocol ) :
self . protocolInstances [ protocol . identifier ] = protocol
2012-03-11 22:14:32 +00:00
def isRegistered ( self , protocol ) :
return protocol . identifier in self . protocolInstances
2012-03-02 18:09:00 +00:00
def unregister ( self , protocol ) :
2012-03-11 13:52:59 +00:00
if protocol . identifier not in self . protocolInstances :
log . msg ( " No protocol instance with identifier %s . " % protocol . identifier )
return
2012-03-02 18:09:00 +00:00
del self . protocolInstances [ protocol . identifier ]
def relay ( self , protocol , message ) :
for identifier in self . protocolInstances . keys ( ) :
if identifier == protocol . identifier :
continue
instance = self . protocolInstances [ identifier ]
2013-01-20 06:03:04 +00:00
instance . sayToChannel ( message )
2012-03-02 18:09:00 +00:00
#Global scope: all protocol instances will need this.
communicator = Communicator ( )
2012-03-02 04:31:57 +00:00
class IRCRelayer ( irc . IRCClient ) :
2012-07-16 22:06:00 +00:00
2012-03-11 05:55:48 +00:00
def __init__ ( self , config ) :
self . network = config [ ' host ' ]
2012-07-04 00:14:14 +00:00
self . password = config [ ' password ' ]
2012-03-11 05:55:48 +00:00
self . channel = config [ ' channel ' ]
self . nickname = config [ ' nick ' ]
self . identifier = config [ ' identifier ' ]
2012-03-14 15:57:29 +00:00
self . heartbeatInterval = float ( config [ ' heartbeat ' ] )
2013-01-20 07:14:18 +00:00
self . username = config [ ' username ' ]
self . realname = config [ ' realname ' ]
2012-03-11 05:55:48 +00:00
log . msg ( " IRC Relay created. Name: %s | Host: %s | Channel: %s " % ( self . nickname , self . network , self . channel ) )
2013-01-20 17:32:53 +00:00
# IRC RFC: https://tools.ietf.org/html/rfc2812#page-4
if len ( self . nickname ) > 9 :
log . msg ( " Nickname %s is %d characters long, which exceeds the RFC maximum of 9 characters. This may cause connection problems. " % ( self . nickname , len ( self . nickname ) ) )
2012-03-02 17:24:25 +00:00
2012-03-02 20:59:26 +00:00
def formatUsername ( self , username ) :
2012-03-04 01:50:55 +00:00
return username . split ( " ! " ) [ 0 ]
2012-03-02 20:59:26 +00:00
2012-03-02 17:24:25 +00:00
def relay ( self , message ) :
2012-03-02 18:09:00 +00:00
communicator . relay ( self , message )
2012-03-02 14:31:43 +00:00
2012-03-02 04:31:57 +00:00
def signedOn ( self ) :
2012-03-02 20:59:26 +00:00
log . msg ( " [ %s ] Connected to network. " % self . network )
2012-03-14 15:57:29 +00:00
self . startHeartbeat ( )
2012-03-02 17:24:25 +00:00
self . join ( self . channel , " " )
2012-07-16 22:06:00 +00:00
2012-03-02 04:31:57 +00:00
def connectionLost ( self , reason ) :
2012-03-02 20:59:26 +00:00
log . msg ( " [ %s ] Connection lost, unregistering. " % self . network )
2012-03-02 18:09:00 +00:00
communicator . unregister ( self )
2012-07-16 22:06:00 +00:00
2013-01-20 06:03:04 +00:00
def sayToChannel ( self , message ) :
2012-03-02 17:24:25 +00:00
self . say ( self . channel , message )
2012-07-16 22:06:00 +00:00
2012-03-02 04:31:57 +00:00
def joined ( self , channel ) :
2012-03-02 20:59:26 +00:00
log . msg ( " Joined channel %s , registering. " % channel )
2012-03-02 18:09:00 +00:00
communicator . register ( self )
2012-07-16 22:06:00 +00:00
2012-03-02 04:31:57 +00:00
def privmsg ( self , user , channel , message ) :
2015-02-02 00:09:12 +00:00
# If someone addresses the bot directly, don't respond.
2012-03-02 20:59:26 +00:00
if channel == self . nickname :
2015-02-02 00:09:12 +00:00
log . msg ( " Recieved privmsg from %s : %s " % ( user , message ) )
2012-03-02 20:59:26 +00:00
else :
2012-03-02 21:30:38 +00:00
self . relay ( " [ %s ] %s " % ( self . formatUsername ( user ) , message ) )
2012-07-16 22:06:00 +00:00
2012-03-02 04:31:57 +00:00
def kickedFrom ( self , channel , kicker , message ) :
2012-03-02 18:09:00 +00:00
log . msg ( " Kicked by %s . Message \" %s \" " % ( kicker , message ) )
communicator . unregister ( self )
2012-07-16 22:06:00 +00:00
2012-03-02 04:31:57 +00:00
def userJoined ( self , user , channel ) :
2012-03-02 21:30:38 +00:00
self . relay ( " %s joined. " % self . formatUsername ( user ) )
2012-07-16 22:06:00 +00:00
2012-03-02 04:31:57 +00:00
def userLeft ( self , user , channel ) :
2012-03-02 21:30:38 +00:00
self . relay ( " %s left. " % self . formatUsername ( user ) )
2012-07-16 22:06:00 +00:00
2012-03-02 04:31:57 +00:00
def userQuit ( self , user , quitMessage ) :
2012-03-11 18:27:52 +00:00
self . relay ( " %s quit. ( %s ) " % ( self . formatUsername ( user ) , quitMessage ) )
2012-07-16 22:06:00 +00:00
2012-03-02 04:31:57 +00:00
def action ( self , user , channel , data ) :
2012-03-02 21:30:38 +00:00
self . relay ( " * %s %s " % ( self . formatUsername ( user ) , data ) )
2012-07-16 22:06:00 +00:00
2012-03-02 04:31:57 +00:00
def userRenamed ( self , oldname , newname ) :
2012-03-02 21:30:38 +00:00
self . relay ( " %s is now known as %s . " % ( self . formatUsername ( oldname ) , self . formatUsername ( newname ) ) )
2012-07-16 22:06:00 +00:00
2012-03-03 01:42:01 +00:00
class RelayFactory ( ReconnectingClientFactory ) :
2012-03-02 04:31:57 +00:00
protocol = IRCRelayer
2012-03-03 01:42:01 +00:00
#Log information which includes reconnection status.
noisy = True
2012-07-16 22:06:00 +00:00
2012-03-11 05:55:48 +00:00
def __init__ ( self , config ) :
2012-03-11 21:59:40 +00:00
config [ " identifier " ] = " {0} {1} {2} " . format ( config [ " host " ] , config [ " port " ] , config [ " channel " ] )
2012-03-11 05:55:48 +00:00
self . config = config
2012-07-16 22:06:00 +00:00
2012-03-02 04:31:57 +00:00
def buildProtocol ( self , addr ) :
2012-03-03 01:42:01 +00:00
#Connected - reset reconnect attempt delay.
self . resetDelay ( )
2012-03-11 05:55:48 +00:00
x = self . protocol ( self . config )
2012-03-02 04:31:57 +00:00
x . factory = self
return x
2012-05-15 00:58:26 +00:00
class SilentJoinPart ( IRCRelayer ) :
def userJoined ( self , user , channel ) :
pass
def userLeft ( self , user , channel ) :
pass
def userQuit ( self , user , quitMessage ) :
pass
2013-01-20 04:44:54 +00:00
def userRenamed ( self , oldname , newname ) :
pass
2012-03-02 22:58:11 +00:00
#Remove the _<numbers> that FLIP puts on the end of usernames.
2012-05-15 00:58:26 +00:00
class FLIPRelayer ( SilentJoinPart ) :
2012-03-02 22:58:11 +00:00
def formatUsername ( self , username ) :
2012-03-04 01:50:55 +00:00
return re . sub ( " _ \ d+$ " , " " , IRCRelayer . formatUsername ( self , username ) )
2012-03-02 22:58:11 +00:00
class FLIPFactory ( RelayFactory ) :
protocol = FLIPRelayer
2012-05-15 00:58:26 +00:00
class NickServRelayer ( SilentJoinPart ) :
2012-03-11 07:10:29 +00:00
NickServ = " nickserv "
2013-01-20 05:25:31 +00:00
NickPollInterval = 30
2012-03-11 22:02:17 +00:00
2012-03-11 07:10:29 +00:00
def signedOn ( self ) :
2012-03-11 22:02:17 +00:00
log . msg ( " [ %s ] Connected to network. " % self . network )
2012-03-14 15:57:29 +00:00
self . startHeartbeat ( )
2012-07-16 22:25:10 +00:00
self . join ( self . channel , " " )
2013-01-20 05:25:31 +00:00
self . checkDesiredNick ( )
def checkDesiredNick ( self ) :
"""
Checks that the nick is as desired , and if not attempts to retrieve it with
NickServ GHOST and trying again to change it after a polling interval .
"""
2012-07-16 22:25:10 +00:00
if self . nickname != self . desiredNick :
2012-03-14 20:57:47 +00:00
log . msg ( " [ %s ] Using GHOST to reclaim nick %s . " % ( self . network , self . desiredNick ) )
2012-03-11 22:02:17 +00:00
self . msg ( NickServRelayer . NickServ , " GHOST %s %s " % ( self . desiredNick , self . password ) )
2013-01-20 05:25:31 +00:00
# If NickServ does not respond try to regain nick anyway.
self . nickPoll . start ( self . NickPollInterval )
def regainNickPoll ( self ) :
if self . nickname != self . desiredNick :
log . msg ( " [ %s ] Reclaiming desired nick in polling. " % ( self . network ) )
self . setNick ( self . desiredNick )
else :
2013-01-20 06:00:50 +00:00
log . msg ( " [ %s ] Have desired nick. " % ( self . network ) )
2013-01-20 05:25:31 +00:00
self . nickPoll . stop ( )
def nickChanged ( self , nick ) :
log . msg ( " [ %s ] Nick changed from %s to %s . " % ( self . network , self . nickname , nick ) )
self . nickname = nick
self . checkDesiredNick ( )
2012-07-16 22:25:10 +00:00
2012-03-11 07:10:29 +00:00
def noticed ( self , user , channel , message ) :
2012-07-16 22:25:10 +00:00
log . msg ( " [ %s ] Recieved notice \" %s \" from %s . " % ( self . network , message , user ) )
#Identify with nickserv if requested
if IRCRelayer . formatUsername ( self , user ) . lower ( ) == NickServRelayer . NickServ :
msg = message . lower ( )
if msg . startswith ( " this nickname is registered and protected " ) :
log . msg ( " [ %s ] Password requested; identifying with %s . " % ( self . network , NickServRelayer . NickServ ) )
self . msg ( NickServRelayer . NickServ , " IDENTIFY %s " % self . password )
elif msg == " ghost with your nickname has been killed. " or msg == " ghost with your nick has been killed. " :
2012-05-25 14:08:12 +00:00
log . msg ( " [ %s ] GHOST successful, reclaiming nick %s . " % ( self . network , self . desiredNick ) )
2012-03-11 22:02:17 +00:00
self . setNick ( self . desiredNick )
2012-07-18 11:41:37 +00:00
elif msg . endswith ( " isn ' t currently in use. " ) :
2012-07-29 04:32:20 +00:00
log . msg ( " [ %s ] GHOST not needed, reclaiming nick %s . " % ( self . network , self . desiredNick ) )
2012-07-18 11:41:37 +00:00
self . setNick ( self . desiredNick )
2012-07-16 22:06:00 +00:00
2012-03-11 07:10:29 +00:00
def __init__ ( self , config ) :
IRCRelayer . __init__ ( self , config )
self . password = config [ ' nickServPassword ' ]
2012-05-15 00:47:23 +00:00
self . desiredNick = config [ ' nick ' ]
2013-01-20 05:25:31 +00:00
self . nickPoll = LoopingCall ( self . regainNickPoll )
2012-03-11 07:10:29 +00:00
2013-01-20 06:16:24 +00:00
class ReadOnlyRelayer ( NickServRelayer ) :
def sayToChannel ( self , message ) :
pass
class ReadOnlyFactory ( RelayFactory ) :
protocol = ReadOnlyRelayer
2012-03-11 07:10:29 +00:00
class NickServFactory ( RelayFactory ) :
protocol = NickServRelayer
2012-03-02 17:24:25 +00:00
def handler ( signum , frame ) :
2012-07-16 22:06:00 +00:00
reactor . stop ( )
2012-03-02 04:31:57 +00:00
2012-03-03 03:16:43 +00:00
#Main if run as script, builtin for twistd.
if __name__ in [ " __main__ " , " __builtin__ " ] :
2012-03-03 01:53:32 +00:00
main ( )