1  """
  2  @Author:        code@darkc0de.org
  3  @Date:          June 6th, 2007
  4  @Filename:      bot.py
  5  @Description:
  6
  7      This small piece of code (bot.py) is a
  8      fairly functional IRC bot designed to deliver
  9      headlines from various RSS feeds (CNN, Slashdot,
 10      Rotten Tomatoes, Shacknews) to a pre-defined IRC
 11      channel upon command.
 12      
 13      Features include spam protection; more specifically,
 14      the bot will not re-download RSS feeds until five
 15      minutes have passed, in an attempt to saturate less
 16      bandwidth (and avoid channel abusers). As another
 17      abuse prevention method, the bot will only respond
 18      in channel every five seconds.
 19      
 20      Torrent searching (from torrent conglomerate btjunkie.org
 21      was added in in version 1.1; although the user can
 22      search for torrents (!torrents search term) in either
 23      the channel or a private message, the response will
 24      only be sent via private message.
 25      
 26      TO DO:
 27      I'm going to add in a function to link to the specific
 28      stories themselves (much like a real RSS feed) in the
 29      next version. This will probably function something like
 30      !news 4 (for a link to story number 4 in the news feed.
 31      
 32      POSSIBLE BUGS:
 33      bot.py was designed to work on the IRC server
 34      chingShih.2600.net. The "hack" mentality of the code
 35      involved here may prevent it from connecting successfully
 36      to other servers. This should be able to be solved fairly
 37      easily by looking at connect messages.
 38      
 39      SOURCE CODE is free and open source under the GPL.
 40
 41  """
 42
 43  # gotta import a buncha stuff. you know how i do.
 44  import sys
 45  import socket
 46  import string
 47  import time
 48  import os
 49  import urllib
 50
 51  # stuff we need to connect our socket, etc.
 52  HOST = "chingShih.2600net.org"
 53  PORT = 6667
 54  NICK = "NewsBot"
 55  IDENT = "newsbot"
 56  REALNAME = "News Bot"
 57  global CHANNEL  # bah... those damn global values annoy me.
 58  CHANNEL = "#ca2600"
 59  readbuffer = ""
 60
 61  # here's our block of update times... it's messy, i know.
 62  # we have to declare them as globals here (and elsewhere!)
 63  # to allow other functions to use them. sucks, i know.
 64  global newsUPDATE
 65  global moviesUPDATE
 66  global techUPDATE
 67  global gamesUPDATE
 68  global UPDATE
 69  global torrentUPDATE
 70  global VERSION
 71
 72  newsUPDATE = 0
 73  moviesUPDATE = 0
 74  techUPDATE = 0
 75  gamesUPDATE = 0
 76  UPDATE = 0
 77  torrentUPDATE = 0
 78  VERSION = "1.2"
 79
 80  # i wrote this up as a simple method to not have to
 81  # parse everything to RFC standards. it's awesome.
 82  def message(msg, dest):
 83    s.send("PRIVMSG " + dest + " :" + msg+"\r\n")
 84
 85
 86  # getheadlines and grabnews code borrowed and
 87  # modified from news.py (SMS relay), of which
 88  # i am the author.
 89
 90  def getheadlines(loc, feed, tor):
 91    heads = ""
 92    num = 0
 93    lnum = 0
 94    if tor == 0:
 95      for line in open(feed, "r").readlines():
 96        if line.find( "<title>" ) != -1:
 97          num = num + 1
 98          if num >= loc and len(heads) + len(line[7:len(line)-8]) <= 200:
 99            heads += line[7:len(line)-10] + ", "
100
101  # you know what? the code might be messy, but it's functional!
102
103    elif tor == 1:
104      for line in open(feed, "r").readlines():
105        if line.find("<title>") != -1:
106          num = num + 1
107          if num >= loc and len(heads) + len(line[7:len(line)-8]) <= 200:
108            heads += line[7:len(line)-10] + " --- "
109        elif line.find("<link>") != -1:
110          lnum = lnum + 1
111          if lnum >= loc and len(heads) + len(line[6:len(line)-7]) <= 200:
112            heads += line[6:len(line)-7] + " ; "
113
114    return heads
115
116
117  # we throw our query to this to see if we can grab headlines.
118  # if we don't find anything, it obviously won't give us anything.
119  # it might be more efficient to check *before* throwing around
120  # CPU cycles, but whatever.                                                                               
121  def grabnews(sub):
122
123  # again, we had to define these as globals so we don't
124  # get an undefined error... python can be silly somtimes,
125  # i guess...
126
127    global newsUPDATE
128    global moviesUPDATE
129    global techUPDATE
130    global gamesUPDATE
131
132    if sub == "help" or sub == "version":
133      message("This bot will send headlines to the channel.", CHANNEL)
134      message("Current commands: !news, !tech, !movies, !games", CHANNEL)
135      message("New: Torrent search! !torrents search terms",CHANNEL)
136      message("NewsBot v"+VERSION+" -- written by code", CHANNEL)
137
138    body = "" # obviously nothing in the body yet!
139              # also: 'body' is a throwback from news.py,
140              # in which this was the body of an email/SMS
141
142    # grab latest headlines and put it in a file. 
143    if sub == "news":
144      print newsUPDATE
145      if(time.time()-newsUPDATE > 300):
146        newsUPDATE = time.time()
147        print "--- grabbing the CNN feed"
148        text = urllib.urlopen("http://rss.cnn.com/rss/cnn_topstories.rss").read()
149        fout = open("newsFeed", "w")
150        fout.write(text)
151        fout.close()
152        print "--- CNN feed written"
153      body = getheadlines(3, "newsFeed", 0)
154
155    # needs better parsing -- currently not functional.
156    elif sub == "sports":
157      if(time.time()-sportsUPDATE > 300):
158        sportUPDATE = time.time()
159        print "--- grabbing the sports feed from Google"
160        text = urllib.urlopen("http://news.google.com/?ned=us&topic=s&output=rss").read()
161        fout = open("feed", "w")
162        fout.write(text)
163        fout.close()
164        print "sports feed written"
165      body = getheadlines(2, "techFeed", 0)
166
167
168    elif sub == "games":
169      if(time.time()-gamesUPDATE > 300):
170        gamesUPDATE = time.time()
171        print "--- grabbing the shacknews feed"
172        text = urllib.urlopen("http://www.shacknews.com/headlines.rdf").read()
173        fout = open("gamesFeed", "w", 0)
174        fout.write(text)
175        fout.close()
176        print "Shacknews feed written"
177      body = getheadlines(2, "gamesFeed", 0)
178
179
180    elif sub == "movies":
181      if(time.time()-moviesUPDATE > 300):
182        moviesUPDATE = time.time()
183        print "--- getting the rotten tomatoes feed"
184        text = urllib.urlopen("http://i.rottentomatoes.com/syndication/rss/complete_movies.xml").read()
185        fout = open("moviesFeed", "w")
186        fout.write(text)
187        fout.close()
188        print "rotten tomatoes feed written"
189      body = getheadlines(3, "moviesFeed", 0)
190
191
192    elif sub == "tech":
193      if(time.time()-techUPDATE > 300):
194        techUPDATE = time.time()
195        print "--- grabbing the slashdot feed"
196        text = urllib.urlopen("http://rss.slashdot.org/Slashdot/slashdot").read()
197        fout = open("techFeed", "w")
198        fout.write(text)
199        fout.close()
200        print "slashdot feed written"
201      body = getheadlines(3, "techFeed", 0)
202
203    elif sub == "test" or sub == "ping":
204      body = "I am alive!! ... NewsBot v"+VERSION+" by code"
205
206    message(body, CHANNEL)
207
208  ### end borrowed/modified code
209
210  # this method will grab torrent titles and
211  # links from BTJunkie, which searches other
212  # trackers...
213
214  # grabbing torrents has to be different than the
215  # rest because it alone includes more parameters
216  # (after all, we *are* searching...
217  def grabtorrent(search, user):
218    global torrentUPDATE
219    # we're nicer to torrent searchers...
220    if(time.time()-torrentUPDATE > 30):
221      torrentUPDATE = time.time()
222      print "--- searching btjunkie for " + search
223      text = urllib.urlopen("http://btjunkie.org/rss.xml?query="+search).read()
224      fout = open("torrentsFeed", "w")
225      fout.write(text)
226      fout.close()
227      print "torrents feed writen"
228    body = getheadlines(2, "torrentsFeed", 1)
229
230    message(body, user)
231
232
233  print """
234    _   _                   ____        _
235   | \ | | _____      _____| __ )  ___ | |_
236   |  \| |/ _ \ \ /\ / / __|  _ \ / _ \| __|
237   | |\  |  __/\ V  V /\__ \ |_) | (_) | |_
238   |_| \_|\___| \_/\_/ |___/____/ \___/ \__|
239   
240    -------------------> darkc0de industries
241    """
242
243  print "--- creating socket"
244  s = socket.socket( )
245  print "--- connecting to %s, port %s" % (HOST, PORT)
246  s.connect((HOST, PORT))
247  s.send("NICK %s\r\n" % NICK)
248  s.send("USER %s %s bla :%s\r\n" % (IDENT, HOST, REALNAME))
249  s.send("JOIN %s\r\n" % CHANNEL)
250  print "--- joined %s as %s" % (CHANNEL, NICK)
251
252
253  # code borrowed from O'Reilly's tutorial on
254  # python irc bots. ... then modified to all
255  # hell. it was still helpful, though!
256  while 1:
257
258      readbuffer=readbuffer+s.recv(1024)
259      temp=string.split(readbuffer, "\n")
260      readbuffer=temp.pop()
261
262      for line in temp:
263        line=string.rstrip(line)
264        line=string.split(line)
265
266      if(line[1] == "PRIVMSG" and str(line[0]) == ":code!code@judecca.aculei.net"):
267        if(str(line[3]) == ":!shutdown" or str(line[3]) == ":!die"):
268          message("Aww... my master is euthanizing me...", CHANNEL)
269          sys.exit(0)
270
271      if(line[1] == "PRIVMSG" and time.time()-UPDATE > 5):
272        if(str(line[2]) == CHANNEL and str(line[3])[:2] == ":!"):
273          print "--- trying " + str(line[3])[1:]
274          grabnews(str(line[3])[2:])
275          UPDATE = time.time()
276
277        if(str(line[3])[1:] == "!torrents"):
278          print "--- trying to get torrent files"
279
280          if(len(line) > 4):
281            query = ""
282            x = 4
283            while x < len(line):
284              query += line[x]
285              x = x + 1
286            user = str(line[0])[1:str(line[0]).find('!')]
287            print "--- trying to find "+query+" and send to "+ user
288            grabtorrent(query, user)
289
290
291      # gotta love IRC's PING? PONG! feature...                                  
292      if(line[0]=="PING"):
293        s.send("PONG %s\r\n" % line[1])
294
295  ### the end! ###