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()