1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 """ 5 azukibot - python twisted base IRCbot 6 """ 7 8 from twisted.words.protocols import irc 9 from twisted.internet import reactor, protocol, defer 10 from twisted.python import log 11 import twisted.web.client 12 13 import sys, os, re, time 14 import htmllib, formatter 15 import codecs 16 17 # __version__ = '$Revision: 36 $'[11:-2] 18 19 def toUnicode(s): 20 encodings = ["ascii", "iso-2022-jp", "utf-8", "euc-jp", "sjis"] 21 for enc in encodings: 22 try: 23 text = unicode(s, enc) 24 break 25 except UnicodeDecodeError: 26 enc = "" 27 text = "" 28 return text 29 30 class AZK_Parser(htmllib.HTMLParser): 31 32 def __init__(self, formatter) : 33 htmllib.HTMLParser.__init__(self, formatter) 34 35 def get_title(self): 36 return self.title 37 38 class AZK_Logger: 39 """ 40 An independent logger class (because separation of application 41 and protocol logic is a good thing). 42 """ 43 def __init__(self, file): 44 self.file = file 45 46 def log(self, message): 47 """Write a message to the file.""" 48 timestamp = time.strftime("[%Y/%m/%d %H:%M:%S]", time.localtime(time.time())) 49 message = toUnicode(message) 50 message = message.encode('utf-8', 'ignore') 51 self.file.write('%s %s\n' % (timestamp, message)) 52 self.file.flush() 53 54 def close(self): 55 self.file.close() 56 57 58 class AZK_Bot(irc.IRCClient): 59 60 """A logging IRC bot.""" 61 nickname = "azukibot" 62 realname = "azuki.py twisted base IRC bot <shomas@gmail.com>" 63 userinfo = "I'm a bot. contact to <shomas@gmail.com>" 64 65 def connectionMade(self): 66 irc.IRCClient.connectionMade(self) 67 self.logger = AZK_Logger(open(self.factory.filename, "a")) 68 self.logger.log("[connected at %s]" % 69 time.asctime(time.localtime(time.time()))) 70 71 def connectionLost(self, reason): 72 irc.IRCClient.connectionLost(self, reason) 73 self.logger.log("[disconnected at %s]" % 74 time.asctime(time.localtime(time.time()))) 75 self.logger.close() 76 77 def sendMessage(self, channel, message): 78 message = toUnicode(message) 79 message = message.encode("iso-2022-jp", 'ignore') 80 print message 81 self.say(channel, message) 82 83 def sendNotice(self, channel, message): 84 message = toUnicode(message) 85 message = message.encode("iso-2022-jp", 'ignore') 86 self.notice(channel, msg) 87 88 89 # callbacks for events 90 91 def signedOn(self): 92 """Called when bot has succesfully signed on to server.""" 93 self.join(self.factory.channel) 94 95 def joined(self, channel): 96 """This will get called when the bot joins the channel.""" 97 self.logger.log("[I have joined %s]" % channel) 98 self.sendMessage(channel, "Hi") 99 100 def privmsg(self, user, channel, msg): 101 """This will get called when I have a message from a user to me or a channel.""" 102 user = user.split('!', 1)[0] 103 self.logger.log("<%s> %s" % (user, msg + " " + channel)) 104 self.doMyAction(channel, msg) 105 106 # Check to see if they're sending me a private message 107 if channel == self.nickname: 108 msg = "It isn't nice to whisper! Play nice with the group." 109 self.msg(user, msg) 110 return 111 112 # Otherwise check to see if it is a message directed at me 113 if msg.startswith(self.nickname + ":"): 114 msg = "%s: I am a log bot" % user 115 self.logger.log("<%s> %s" % (self.nickname, msg)) 116 self.msg(channel, msg) 117 118 def action(self, user, channel, msg): 119 """Send an action to a channel or user.""" 120 user = user.split('!', 1)[0] 121 self.logger.log("* %s %s" % (user, msg+"action")) 122 123 def noticed(self, user, channel, msg): 124 """Called when I have a notice from a user to me or a channel.""" 125 self.logger.log("* %s %s" % (user, msg+"notice")) 126 127 def topicUpdated(self, user, channel, newTopic): 128 self.logger.log("%s change %s topic to %s" % (user, channel, newTopic)) 129 130 def modeChanged(self, user, channel, set, modes, args): 131 """Called when a channel's modes are changed.""" 132 user = user.split('!', 1)[0] 133 list = ",".join(args) 134 if (set == True): 135 setopt = '+' 136 else: 137 setopt = '-' 138 139 self.logger.log("%s change mode %s%s to %s at %s" % (user, setopt, modes, list, channel)) 140 141 def userJoined(self, user, channel): 142 self.logger.log("%s joined %s" % (user, channel)) 143 144 def userLeft(self, user, channel): 145 self.logger.log("%s left %s" % (user, channel)) 146 147 # irc callbacks 148 149 def irc_NICK(self, prefix, params): 150 """Called when an IRC user changes their nickname.""" 151 old_nick = prefix.split('!')[0] 152 new_nick = params[0] 153 self.logger.log("%s is now known as %s" % (old_nick, new_nick)) 154 155 156 # my Responce Action 157 def doMyAction(self, channel, message): 158 if message[:4] == "http": 159 url = message.split()[0] 160 self.request(url, channel) 161 elif message[:3] == "#r ": 162 reply = message[3:] 163 self.sendMessage(channel, reply.strip()) 164 return 165 166 def request(self, url, channel): 167 self.getter = twisted.web.client.getPage(url, timeout=10) 168 self.getter.addCallback(self.getTitle, channel) 169 self.getter.addErrback(self.handleWebError(url)) 170 171 def getTitle(self, data, channel): 172 format = formatter.NullFormatter() 173 htmlparser = AZK_Parser(format) 174 htmlparser.feed(data) 175 htmlparser.close() 176 title = htmlparser.get_title() 177 self.logger.log("[web][title]" + title) 178 if (type(title) == type("")): 179 if len(title) > 0: 180 self.sendMessage(channel, title) 181 182 def handleWebError(self, error): 183 self.logger.log("[web][error]" + str(error)) 184 185 class AZK_Factory(protocol.ClientFactory): 186 """A factory for LogBots. 187 A new protocol instance will be created each time we connect to the server. 188 """ 189 190 # the class of the protocol to build when new connection is made 191 protocol = AZK_Bot 192 193 def __init__(self, channel, filename): 194 self.channel = channel 195 self.filename = filename 196 197 def clientConnectionLost(self, connector, reason): 198 """If we get disconnected, reconnect to server.""" 199 connector.connect() 200 201 def clientConnectionFailed(self, connector, reason): 202 print "connection failed:", reason 203 reactor.stop() 204 205 206 if __name__ == '__main__': 207 # initialize logging 208 log.startLogging(sys.stdout) 209 210 # config 211 f = AZK_Factory('test', 'log') 212 # connect factory to this host 213 reactor.connectTCP("irc.hoge.co.jp", 6667, f) 214 215 # run bot 216 reactor.run()