JZTech101

Untitled

Nov 30th, 2013
241
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 83.57 KB | None | 0 0
  1. # coding=utf-8
  2.  
  3. import feedparser
  4. import math
  5. import os
  6. import random
  7. import re
  8. import simplejson
  9. import time
  10. import lxml
  11. import upsidedown
  12. from os.path import join, abspath, dirname
  13. from BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, StopParsing
  14. from cgi import parse_qs
  15. from datetime import date, datetime
  16. from elementtidy import TidyHTMLTreeBuilder
  17. from int2word import int2word
  18. from random import randint, choice, seed
  19. from urllib import quote, urlencode
  20. from urllib2 import urlopen, urlparse, Request, build_opener, HTTPError
  21. from urlparse import urlparse
  22. from threading import Timer
  23. import csv
  24.  
  25. from supybot.commands import *
  26. import supybot.callbacks as callbacks
  27.  
  28. from beck import BeckGenerator
  29. import sheendata
  30.  
  31. class PollNotFoundException(Exception):
  32. pass
  33.  
  34.  
  35. class Assorted(callbacks.Privmsg):
  36.  
  37. def devil(self,irc,msg,args):
  38. """fetch a random entry from the devil's dictionary at
  39. http://www.eod.com/devil/archive/
  40. """
  41. base = 'http://www.eod.com'
  42. ns = 'http://www.w3.org/1999/xhtml'
  43. tree = TidyHTMLTreeBuilder.parse(urlopen(base+'/devil/archive/'))
  44.  
  45. def is_entry(a):
  46. if a.attrib.has_key('href') \
  47. and '/devil/archive/' in a.attrib['href']:
  48. return True
  49. return False
  50.  
  51. anchors = filter(is_entry, tree.findall('.//{%s}a' % ns))
  52. anchor = anchors[randint(0, len(anchors)-1)]
  53.  
  54. tree = TidyHTMLTreeBuilder.parse(urlopen(base+anchor.attrib['href']))
  55. word = tree.find('.//{%s}strong' % ns).text
  56.  
  57. paras = tree.findall('.//{%(ns)s}blockquote/{%(ns)s}p' % {'ns': ns})
  58. irc.reply("%s - %s" % (word, paras[0].text.encode('utf8','ignore')))
  59.  
  60.  
  61. def underbelly(self,irc,msg,args):
  62. """access2005 podcast downloads today
  63. """
  64. text = urlopen("http://access2005.library.ualberta.ca/podcast-stats.txt").read().strip()
  65. irc.reply(text)
  66.  
  67. def runoff(self,irc,msg,args):
  68. """polls code4libcon 2007 runoff votes
  69. """
  70. results = self.get_votes(122)
  71. irc.reply('; '.join(results).encode('utf8'))
  72.  
  73. def tshirts(self,irc,msg,args):
  74. """code4libcon 2007 tshirt votes
  75. """
  76. results = self.get_votes(150)
  77. irc.reply('; '.join(results).encode('utf8'))
  78.  
  79. def tshirters(self,irc,msg,args):
  80. """code4libcon 2007 tshirt voters
  81. """
  82. results = self.get_voters(150)
  83. irc.reply(("[%i voters] " % len(results)) +
  84. '; '.join(results).encode('utf8'))
  85.  
  86. def hosts2008(self,irc,msg,args):
  87. """code4libcon 2008 host site votes
  88. """
  89. results = self.get_votes(164)
  90. irc.reply('; '.join(results).encode('utf8', 'ignore'))
  91.  
  92. def hosters(self,irc,msg,args):
  93. """code4libcon 2008 host site voters
  94. """
  95. results = self.get_voters(164)
  96. irc.reply(("[%i voters] " % len(results)) +
  97. '; '.join(results).encode('utf8'))
  98.  
  99. def votes2007(self,irc,msg,args):
  100. """polls code4libcon 2007 votes
  101. """
  102. results = self.get_votes(120)
  103. irc.reply('; '.join(results).encode('utf8'))
  104.  
  105. # def keynotes2013(self,irc,msg,args):
  106. # """votes for the 2011 code4libcon keynote
  107. # """
  108. # keynotes = [
  109. # "Estelle Getty", "Sylvester Stallone", "Yngwie Malmsteen",
  110. # "Andy Dick", "U. Rex Dumdum", "Michael J. Giarlo", "Ron Paul",
  111. # "Bacon Salt", "Zoia", "Ima Hogg", "Mark Lemongello", "'anon'",
  112. # "Trout Fishing in America", "Freddie Skunkcap", "Dick Assman",
  113. # "Rooster McConaughey", "Hacker John", "Ace Waterwheels"
  114. # ]
  115. # n = randint(3,8)
  116. # people = [keynotes.pop(randint(0, len(keynotes)-1)) for i in range(n)]
  117. # votes = [randint(1,100) for a in range(0,n)]
  118. # votes.sort(lambda a,b: cmp(b,a))
  119. # results = zip(people,votes)
  120. # msg = ["%s [%i]" % (result[0], result[1]) for result in results]
  121. # irc.reply('; '.join(msg))
  122.  
  123. def hosts2013(self,irc,msg,args):
  124. """
  125. Shows votes for the next code4libcon. Courtesy of
  126. http://www.floydpinkerton.net/fun/citynames.html
  127. """
  128. places = [
  129. "Aces of Diamonds, Florida", "Fearnot, Pennsylvania", "Normal, Illinois",
  130. "Assawoman, Virginia", "Fifty-Six, Arkansas", "Odd, West Virginia",
  131. "Bald Head, Maine", "Forks of Salmon, California", "Ogle, Kentucky",
  132. "Beetown, Wisconsin", "Fort Dick, California", "Okay, Oklahoma",
  133. "Belcher, New York", "Frankenstein, Missouri", "Panic, Pennsylvania",
  134. "Ben Hur, Texas", "Footville, Wisconsin", "Peculiar, Missouri",
  135. "Big Foot, Illinois", "Gaylordsville, Connecticut", "Plain City, Utah",
  136. "Big Sandy, Wyoming", "Gay Mills, Wisconsin", "Porkey, Pennsylvanania",
  137. "Blueballs, Pennsylvania", "Gun Barrel City, Texas", "Quiggleville, Pennsylvania",
  138. "Boring, Maryland", "Hell, Michigan", "Rambo Riviera, Arkansas",
  139. "Bivalve, California", "Boring, Oregon", "Hicksville, Ohio", "River Styx, Ohio",
  140. "Buddha, Indiana", "Hooker, Arkansas", "Roachtown, Illinois",
  141. "Chestnut, Illinois", "Hooker, Oklahoma", "Romance, Arkansas",
  142. "Chicken, Alaska", "Hornytown, North Carolina", "Rough and Ready, California",
  143. "Chocalate Bayou, Texas", "Hot Coffee, Mississippi", "Sand Draw, Wyoming",
  144. "Chugwater, Wyoming", "Hot Water, Mississippi", "Sandwich, Illinois",
  145. "Climax, North Carolina", "Intercourse, Pennsylvania", "Smackover, Arkansas",
  146. "Climax, Pennsylvania", "It, Mississippi", "Spread Eagle, Wisconsin",
  147. "Climax, Saskatchewan", "Jupiter, Florida", "Toad Suck, Arkansas",
  148. "Cold Foot, Alaska", "Kill Devil Hills, North Carolina", "Umpire, Arkansas",
  149. "Cold Water, Mississippi", "King Arthur's Court, Michigan", "Uncertain, Texas",
  150. "Cut-Off, Louisiana", "Koopa, Colorado", "Yazoo, Mississippi",
  151. "Dickeyville, Wisconsin", "Lawyersville, New York", "Yeehaw Junction, Florida",
  152. "Ding Dong, Texas", "Lollipop, Texas",
  153. "Disco, Tennessee", "Love Ladies, New Jersey",
  154. "Dismal, Tennessee", "Magazine, Arkansas",
  155. "Dry Fork, Wyoming", "Mars, Pennsylvania",
  156. "Eclectic, Alabama", "Monkey's Elbow, Kentucky",
  157. "Eek, Alaska", "Muck City, Alabama",
  158. "Embarrass, Minnesota", "No Name, Colorado",
  159. "Gross, Nebraska", "Leaksville-Spray, North Carolina",
  160. "Poison, Montana", "Dingle, Idaho", "Liberal, Kansas",
  161. ]
  162. def rand_places(num, seen=None):
  163. """ """
  164. if seen == None:
  165. seen = []
  166. if num == 0:
  167. return []
  168. place = places[randint(0, len(places)-1)]
  169. if place in seen:
  170. place = rand_places(1, seen)
  171. seen.append(place)
  172. return [place] + rand_places(num-1, seen)
  173. n = randint(3,8)
  174. cities = rand_places(n)
  175. votes = [randint(1,100) for a in range(0,n)]
  176. votes.sort(lambda a,b: cmp(b,a))
  177. results = zip(cities,votes)
  178. msg = []
  179. for result in results:
  180. msg.append("%s [%i]" % (result[0], result[1]))
  181. irc.reply('; '.join(msg))
  182.  
  183. def get_votes(self, node_id, brief=True):
  184. """ """
  185. json = urlopen("http://code4lib.org/votes.php?node_id=%i" % node_id).read()
  186. results = []
  187. for tally in simplejson.loads(json):
  188. talk_name = tally['talk_name']
  189. if brief and not re.match('http', talk_name):
  190. talk_name = re.sub('( -|[:/.]).*', '', talk_name)
  191. results.append("%s %s" % (talk_name, tally['votes']))
  192. if brief and len(results) > 15:
  193. break
  194. return results
  195.  
  196. def twss(self, irc, msg, args):
  197. """ """
  198. html = urlopen("http://thatswutshesaid.com/").read()
  199. soup = BeautifulSoup(html)
  200. twss = soup.find("h1")
  201. irc.reply(twss.string.encode('utf8'))
  202.  
  203. def hench(self, irc, msg, args):
  204. """Generates an evil henchperson name from http://www.seventhsanctum.com/"""
  205. html = urlopen("http://www.seventhsanctum.com/generate.php?Genname=evilnamer").read()
  206. soup = BeautifulSoup(html)
  207. name = soup.find('div', { 'id': 'colContent' }).find('h2').nextSibling.strip()
  208. irc.reply(name.encode('utf8'))
  209.  
  210. def luther(self, irc, msg, args):
  211. """Insults from Martin Luther, from http://ergofabulous.org/luther/"""
  212. html = urlopen("http://ergofabulous.org/luther/").read()
  213. soup = BeautifulSoup(html)
  214. luther = soup.find("p", {"class": "larger"})
  215. irc.reply(luther.string.encode('utf8'))
  216.  
  217. def foodholiday(self, irc, msg, args):
  218. """ """
  219. datestring = time.strftime('%B %d', time.localtime())
  220. datere = re.compile('^'+datestring+'\\b')
  221. html = urlopen("http://www.tfdutch.com/foodh.htm").read()
  222. soup = BeautifulSoup(html)
  223. matches = soup.findAll(text = re.compile(datere))
  224. holidays = [match.parent.parent.parent.find('a').string for match in matches]
  225. if len(holidays) > 0:
  226. response = "%s: %s" % (datestring, ', '.join(holidays))
  227. else:
  228. response = "No food holidays found for %s" % datestring
  229. irc.reply(response)
  230.  
  231. def penny(self, irc, msg, args):
  232. """ """
  233. html = urlopen("http://www.penny-arcade.com/archive/").read()
  234. soup = BeautifulSoup(html)
  235. tagcloud = soup.find("ul","tagcloud")
  236. tags = []
  237. for tag in tagcloud.findAll('li'):
  238. tags.append(tag.a.string)
  239. mytag = tags[randint(0, len(tags))]
  240. search = urlopen("http://www.penny-arcade.com/archive/?q=" + mytag).read()
  241. comiclist = BeautifulSoup(search)
  242. comics = comiclist.findAll("td","content_title")
  243. links = []
  244. for td in comics:
  245. print td.a['href']
  246. links.append(td.a['href'])
  247. comic = links[randint(0, len(links))]
  248. irc.reply("http://www.penny-arcade.com" + comic)
  249.  
  250. def wine(self, irc, msg, args):
  251. """
  252. Wine from 2010 wines of the year list
  253. """
  254. f = join(dirname(abspath(__file__)), 'wines.html')
  255. data = open(f).read()
  256. soup = BeautifulSoup(data, convertEntities=BeautifulSoup.HTML_ENTITIES)
  257. wines = []
  258. for li in soup.findAll('li'):
  259. if li.a:
  260. wines.append((li.a['href'], li.a.string))
  261. wine = wines[randint(0, len(wines))]
  262. if len(args) > 0:
  263. nick = ' '.join(args)
  264. else:
  265. nick = msg.nick
  266. irc.reply("fills a glass with %s for %s (%s)" % (wine[1], nick, wine[0]), action=True)
  267.  
  268.  
  269. def runoffers(self,irc,msg,args):
  270. """code4libcon 2007 runoff voters
  271. """
  272. results = self.get_voters(122)
  273. irc.reply(("[%i voters] " % len(results)) +
  274. '; '.join(results).encode('utf8'))
  275.  
  276.  
  277. def voters2007(self,irc,msg,args):
  278. """code4libcon 2007 voters
  279. """
  280. results = self.get_voters(120)
  281. irc.reply(("[%i voters] " % len(results)) +
  282. '; '.join(results).encode('utf8'))
  283.  
  284. def get_voters(self, node_id):
  285. """ """
  286. json = urlopen("http://code4lib.org/voters.php?node_id=%d" % node_id).read()
  287. results = []
  288. for tally in simplejson.loads(json):
  289. results.append("%s - %s" % (tally['name'], tally['votes']))
  290. return results
  291.  
  292. def moe(self,irc,msg,args):
  293. """moe
  294. """
  295. ns = 'http://www.w3.org/1999/xhtml'
  296. url = 'http://www.snpp.com/guides/moe_calls.html'
  297. root = TidyHTMLTreeBuilder.parse(urlopen(url))
  298.  
  299. bolds = root.findall('.//{%s}b' % ns)
  300. quote = bolds[ randint(0,len(bolds)-1) ]
  301. irc.reply(quote.text)
  302.  
  303. def deli(self,irc,msg,args):
  304. """ """
  305. max = 1
  306. url = 'http://del.icio.us/rss/popular'
  307. if (len(args) > 0):
  308. url += '/' + quote(args[0])
  309. if (len(args) > 1):
  310. max = int(args[1])
  311.  
  312. feed = feedparser.parse(url)
  313. count = 0
  314. reply = ''
  315. for entry in feed.entries:
  316. count += 1
  317. if count > max: break
  318. reply += '%s - %s ; ' % (entry.title, entry.link)
  319. irc.reply(reply.encode('utf8'))
  320.  
  321. def jihad(self,irc,msg,args):
  322. """welcomes a fellow jihadist
  323.  
  324. http://www.elsewhere.org/cgi-bin/jihad
  325. """
  326. url = "http://www.elsewhere.org/cgi-bin/jihad"
  327. tree = TidyHTMLTreeBuilder.parse(urlopen(url))
  328. strong = tree.find('.//{http://www.w3.org/1999/xhtml}strong')
  329. irc.reply("welcome %s!" % strong.text)
  330.  
  331. def journal(self,irc,msg,args):
  332. """randomly sample a journal name
  333. """
  334. url = "http://www.code4lib.org/node/86"
  335. tree = TidyHTMLTreeBuilder.parse(urlopen(url))
  336. list = tree.find('.//{http://www.w3.org/1999/xhtml}ul')
  337. names = []
  338. for name in list:
  339. names.append(name.text)
  340. irc.reply(names[randint(0, len(names)-1)])
  341.  
  342. def jvotes(self,irc,msg,args):
  343. """journal name votes
  344. """
  345. text = urlopen("http://rsinger.library.gatech.edu/journalnamevote/overbelly_all.php").read().strip()
  346. irc.reply(text)
  347.  
  348. def jvoters(self,irc,msg,args):
  349. """journal name voters
  350. """
  351. text = urlopen("http://rsinger.library.gatech.edu/journalnamevote/voters.php").read().strip()
  352. irc.reply(text)
  353.  
  354. def lastjournal(self,irc,msg,args):
  355. """last journal name on website
  356. """
  357. url = "http://www.code4lib.org/node/86"
  358. tree = TidyHTMLTreeBuilder.parse(urlopen(url))
  359. list = tree.find('.//{http://www.w3.org/1999/xhtml}ul')
  360. names = []
  361. for name in list:
  362. names.append(name.text)
  363. irc.reply(names.pop())
  364.  
  365. def gc(self,irc,msg,args):
  366. """google count
  367. """
  368. irc.reply(self.count(' '.join(args)))
  369.  
  370. def skillz(self,irc,msg,args):
  371. """generate a fantasy Internet user"""
  372. attrs = ['Posturing', 'FanFiction', 'SocialMedia', 'Chat', 'LOLCats', 'Piracy', 'Email']
  373. dev = ', '.join(["%s:%d" % (attr, self.dnd_attr()) for attr in attrs])
  374. irc.reply(dev)
  375.  
  376. def developer(self,irc,msg,args):
  377. """generate a fantasy developer"""
  378. attrs = ['Communication', 'BigPicture', 'DetailOriented', 'KungFu', 'GetsStuffDone', 'FlakeFactor', 'JavaAvoidance']
  379. dev = ', '.join(["%s:%d" % (attr, self.dnd_attr()) for attr in attrs])
  380. irc.reply(dev)
  381.  
  382. def librarian(self,irc,msg,args):
  383. """generate a fantasy librarian"""
  384. attrs = ['Management', 'Cataloging', 'Acquisitions', 'Reference',
  385. 'Circulation', 'Systems', 'Research', 'Custodial']
  386. dev = ', '.join(["%s:%d" % (attr, self.dnd_attr()) for attr in attrs])
  387. irc.reply(dev)
  388.  
  389.  
  390. def gamma(self,irc,msg,args):
  391. """generate a gamma world character"""
  392. attrs = ['Charisma', 'Constitution', 'Dexterity','Intelligence','Mental Strength','Physical Strength']
  393. pc = ', '.join(["%s:%d" % (attr, self.dnd_attr()) for attr in attrs])
  394. irc.reply(pc)
  395.  
  396. def dnd(self,irc,msg,args):
  397. """get a d&d character
  398. """
  399. irc.reply("strength:%d dexterity:%d constitution:%d intelligence:%d wisdom:%d charisma:%d" % tuple([self.dnd_attr() for i in range(6)]))
  400.  
  401. def roll(self, s):
  402. """ """
  403. times, die = map(int, s.split('d'))
  404. return [randint(1, die) for i in range(times)]
  405.  
  406. def drop_lowest(self, rolls):
  407. """ """
  408. rolls.remove(min(rolls))
  409. return rolls
  410.  
  411. def dnd_attr(self):
  412. """ """
  413. return sum(self.drop_lowest(self.roll('4d6')))
  414.  
  415. def duel(self,irc,msg,args):
  416. """ """
  417. if len(args) != 2:
  418. irc.reply("usage: duel thing1 thing2")
  419. return
  420. thing1, thing2 = args
  421. c1 = self.count(thing1)
  422. c2 = self.count(thing2)
  423. if c1 > c2:
  424. irc.reply("%s wins %i to %i" % (thing1, c1, c2))
  425. elif c1 < c2:
  426. irc.reply("%s wins %i to %i" % (thing2, c2, c1))
  427. else:
  428. irc.reply("%s and %s tied at %i" % (thing1, thing2, c1))
  429.  
  430. def indieduel(self,irc,msg,args):
  431. """ """
  432. if len(args) != 2:
  433. irc.reply("usage: indieduel thing1 thing2")
  434. return
  435. thing1, thing2 = args
  436. c1 = self.count(thing1)
  437. c2 = self.count(thing2)
  438. if c1 > c2:
  439. irc.reply("%s is too mainstream: %i to %i" % (thing1, c1, c2))
  440. elif c1 < c2:
  441. irc.reply("%s is too mainstream: %i to %i" % (thing2, c2, c1))
  442. else:
  443. irc.reply("%s and %s tied at %i" % (thing1, thing2, c1))
  444.  
  445. def count(self,q):
  446. """ """
  447. q = q.replace('"','')
  448. q = q.replace("'",'')
  449. r = google.doGoogleSearch(q)
  450. self.log.info("searching google for %s" % q)
  451. return r.meta.estimatedTotalResultsCount
  452.  
  453. def library(self,irc,msg,args):
  454. """ """
  455. search_url = 'http://www.librarytechnology.org/lwc-processquery.pl'
  456. query = {}
  457.  
  458. # validate
  459. if len(args) == 0:
  460. irc.reply("usage: library [institution_name] [city state [country]]")
  461. return
  462.  
  463. if len(args) == 1:
  464. query['Quick'] = args[0]
  465. elif len(args) == 2:
  466. query['City'] = args[0]
  467. query['State'] = args[1]
  468. if len(args) == 3:
  469. query['Country'] = args[2]
  470.  
  471. url = search_url + '?' + urlencode(query)
  472. hits = TidyHTMLTreeBuilder.parse(urlopen(url))
  473.  
  474. results = []
  475. for p in hits.findall('.//{http://www.w3.org/1999/xhtml}p'):
  476. if p.attrib.get('class') == 'listing':
  477. results.append(self.get_library_detail(p))
  478. if len(results) == 0:
  479. irc.reply("game over man!")
  480. else:
  481. irc.reply(' ; '.join(results).encode('utf8'))
  482.  
  483. def get_library_detail(self,p):
  484. """ """
  485. text = self.get_text(p)
  486. for a in p.findall('.//{http://www.w3.org/1999/xhtml}a'):
  487. params = parse_qs(urlparse(a.attrib.get('href'))[4])
  488. if params.has_key('URL'):
  489. return text + params['URL'][0]
  490. return text
  491.  
  492. def lurkers(self,irc,msg,args):
  493. """see who looked at the irc-logs on the web today. use 'all' if
  494. you want to see non-resolvable ip addresses
  495. """
  496. include_ips = False
  497. if len(args)>0 and args[0]=='all': include_ips = True
  498. ips = os.popen("grep 'irc-logs/index.php\?' /var/www/code4lib.org/logs/access_log | cut -f 1 -d ' ' | sort | uniq").read().splitlines()
  499. resolved = []
  500. for ip in ips:
  501. if ip == '': continue
  502. hostname = os.popen('dnsname %s' % ip).read().strip()
  503. if hostname == '' and include_ips:
  504. resolved.append(ip)
  505. elif hostname:
  506. resolved.append(hostname)
  507. resolved.sort()
  508. irc.reply('; '.join(resolved))
  509.  
  510. def rnd(self,irc,msg,args):
  511. """random page from the wikipedia
  512. """
  513. # how awful is this, at least we're working on a DOM ...
  514. # wikipedia seems to not like the User-Agent of urllib2
  515. url = 'http://en.wikipedia.org/wiki/Special:Random'
  516. ua = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11'
  517. opener = build_opener()
  518. opener.addheaders = [('User-Agent', ua)]
  519. html = opener.open(url)
  520.  
  521. # i tried this beautifulsoup stuff when elementtree was being buggy
  522. # (http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=461629) gsf
  523. html_str = html.read()
  524. try:
  525. soup = BeautifulSoup(html_str)
  526. except UnicodeDecodeError, e:
  527. raise UnicodeDecodeError, "%s. HTML was %s." % (e, html_str)
  528. title = soup.title.string
  529.  
  530. def strip_tags(elem):
  531. text_list = []
  532. for child in elem.recursiveChildGenerator():
  533. if isinstance(child, unicode):
  534. text_list.append(child)
  535. return ''.join(text_list)
  536.  
  537. text = strip_tags(soup.p)
  538.  
  539. link_elem = soup.find('a', title=re.compile('^Permanent link.*'))
  540. link = link_elem['href'].replace('&amp;', '&')
  541.  
  542. irc.reply(("%s : %s <http://en.wikipedia.org%s>" % (title, text, link)).encode('utf-8'))
  543.  
  544. def hillary(self,irc,msg,args):
  545. """ """
  546. url = 'http://www.hillaryismomjeans.com/'
  547. ua = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11'
  548. opener = build_opener()
  549. opener.addheaders = [('User-Agent', ua)]
  550. html = opener.open(url)
  551. html_str = html.read()
  552. soup = BeautifulSoup(html_str)
  553. irc.reply(soup.find('a').string)
  554.  
  555. def obama(self,irc,msg,args):
  556. """ """
  557. url = 'http://barackobamaisyournewbicycle.com/'
  558. ua = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11'
  559. opener = build_opener()
  560. opener.addheaders = [('User-Agent', ua)]
  561. html = opener.open(url)
  562. html_str = html.read()
  563. soup = BeautifulSoup(html_str)
  564. irc.reply(soup.find('a').string.strip().upper())
  565.  
  566. def isiticedcoffeeweather(self,irc,msg,args):
  567. """ """
  568. if len(args) != 1:
  569. irc.reply("usage: isiticedcoffeeweather zipcode")
  570. return
  571. url = 'http://isiticedcoffeeweather.com/%s' % args[0]
  572. ua = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11'
  573. opener = build_opener()
  574. opener.addheaders = [('User-Agent', ua)]
  575. html = opener.open(url)
  576. html_str = html.read()
  577. soup = BeautifulSoup(html_str)
  578. irc.reply(soup.find('h2').string.strip().upper())
  579.  
  580. def wodehouse(self,irc,msg,args):
  581. """grabs a p.g. wodehouse quote from http://www.drones.com/pgw.cgi"""
  582. url = 'http://www.drones.com/pgw.cgi'
  583. ua = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11'
  584. opener = build_opener()
  585. opener.addheaders = [('User-Agent', ua)]
  586. html = opener.open(url)
  587. html_str = html.read()
  588. soup = BeautifulSoup(html_str)
  589. irc.reply(soup.find('blockquote').next.replace("\n"," ").strip())
  590.  
  591. def get_text(self,e):
  592. """ """
  593. string = ''
  594. if e.text: string += e.text
  595. for child in e.getchildren():
  596. string += self.get_text(child)
  597. if e.tail: string += e.tail
  598. string = string.replace("\n",'')
  599. string = re.sub('\+? Details.','', string)
  600. return string
  601.  
  602. def isitfriday(self, irc, msg, args):
  603. """ """
  604. # irc.reply("If it feels like Friday, it's Friday!")
  605. # return
  606. isfriday = "nope."
  607. dow = date.today().weekday()
  608. if dow == 4:
  609. isfriday = "yes!"
  610. irc.reply(isfriday, prefixNick=True)
  611. return
  612.  
  613. def isit420(self, irc, msg, args):
  614. """ """
  615. irc.reply("uhhhhh, what?")
  616. return
  617.  
  618. def arewethereyet(self, irc, msg, args):
  619. """ """
  620. irc.reply("nope", prefixNick=True)
  621. return
  622.  
  623. def bin2int(self, irc, msg, args, binstring):
  624. """
  625. usage: bin2int <bin>
  626. returns the integer form of a given binary string
  627. """
  628.  
  629. irc.reply(int(binstring, 2), prefixNick=True)
  630. return
  631.  
  632. bin2int = wrap(bin2int, ['text'])
  633.  
  634. def nonsense(self, irc, msg, args, nb_words, lang, minl, maxl):
  635. """
  636. a generator for pronounceable random words --
  637. Source: http://ygingras.net/yould --
  638. Usage: nonsense [num_words] [lang] [min len] [max len] --
  639. Language codes: en,fr,kjb,fi,nl,de,la
  640. """
  641. if nb_words > 10:
  642. nb_words = 10
  643.  
  644. postdata = {}
  645. postdata['lang'] = lang or 'en'
  646. postdata['minl'] = minl or 5
  647. postdata['maxl'] = maxl or 12
  648. postdata['nb_words'] = nb_words or 1
  649. postdata = urlencode(postdata)
  650.  
  651. try:
  652. soup = self._url2soup('http://ygingras.net/yould?lang=en', {}, postdata)
  653. except HTTPError, e:
  654. irc.reply('http error %s for %s' % (e.code, url), prefixNick=True); return
  655. except StopParsing, e:
  656. irc.reply('parsing error %s for %s' % (e.code, url), prefixNick=True); return
  657.  
  658. words = soup.find('textarea',{'name':'new_domains'}).string.split()
  659. words = [w.encode('utf-8') for w in words if isinstance(w, unicode)]
  660. irc.reply(' '.join(words))
  661.  
  662. nonsense = wrap(nonsense, [optional('int'),optional('anything'),optional('int'),optional('int')])
  663.  
  664. def hero(self, irc, msg, args, opts, nick):
  665. """[--sex <male|female|neuter>] [--powers <1..15>] [name] - Generate some random superhero powers
  666. from http://www.rps.net/cgi-bin/stone/randpower.pl"""
  667. sexes = { 'male': 0, 'female': 1, 'neuter': 2 }
  668. if not nick:
  669. nick = msg.nick
  670.  
  671. sex = 0
  672. powers = 0
  673.  
  674. for (opt, arg) in opts:
  675. if opt == 'sex':
  676. try:
  677. sex = sexes[arg]
  678. except:
  679. sex = 0
  680. if opt == 'powers':
  681. powers = arg
  682.  
  683. if powers > 15:
  684. irc.error('Maximum of 15 powers allowed.')
  685. else:
  686. postdata = {}
  687. postdata['name'] = nick
  688. postdata['sex'] = sex
  689. postdata['numpowers'] = powers
  690. postdata['weakness'] = 1
  691. postdata = urlencode(postdata)
  692.  
  693. try:
  694. soup = self._url2soup('http://www.rps.net/cgi-bin/stone/randpower.pl', {}, postdata)
  695. except HTTPError, e:
  696. irc.reply('http error %s for %s' % (e.code, url), prefixNick=True); return
  697. except StopParsing, e:
  698. irc.reply('parsing error %s for %s' % (e.code, url), prefixNick=True); return
  699.  
  700. text = soup.findAll(text=re.compile('\S'))[-1].strip()
  701. irc.reply(text, prefixNick=False)
  702.  
  703. hero = wrap(hero, [getopts({'sex':("literal", ("male","female","neuter")),'powers':'int'}), optional('text')])
  704.  
  705. def itr(self,irc,msg,args,continent):
  706. """
  707. Usage: itr [continent]
  708. Returns current traffic index from http://internettrafficreport.com/
  709. Data available for Asia, Australia, Europe, North America and South America
  710. (default is North America)
  711. """
  712.  
  713. if not continent:
  714. continent = 'North America'
  715.  
  716. try:
  717. soup = self._url2soup('http://internettrafficreport.com/')
  718. except HTTPError, e:
  719. irc.reply('http error %s for %s' % (e.code, url), prefixNick=True); return
  720. except StopParsing, e:
  721. irc.reply('parsing error %s for %s' % (e.code, url), prefixNick=True); return
  722.  
  723. region = None
  724. for row in soup.find('table', attrs={'border': 1}).findAll('tr'):
  725. if row.find(text=re.compile(continent,re.I)):
  726. region = row
  727.  
  728. if not region:
  729. irc.reply("No index for %s" % continent)
  730. return
  731.  
  732. ci = region.contents[1].font.b.string
  733. art = region.contents[2].font.string
  734. pl = region.contents[3].font.string
  735. resp = "ITR for %s: Current Index: %s, Avg Response Time: %s, Avg Packet Loss: %s" % (continent,ci,art,pl)
  736. irc.reply(resp)
  737.  
  738. itr = wrap(itr, [optional('text')])
  739.  
  740. def haiku(self, irc, msg, args):
  741. """
  742. Random haiku from http://www.vikingmud.org/guilds/samurai/?haiku/random
  743. """
  744. soup = self._url2soup('http://www.vikingmud.org/guilds/samurai/?haiku/random')
  745. main = soup.find('td', {'class': 'main'})
  746. haiku = main.find('i')
  747. lmb = lambda s: s.string and s.string.strip() or None
  748. resp = [lmb(x) for x in haiku.contents]
  749. irc.reply(' / '.join([x for x in resp if x]), prefixNick=False)
  750.  
  751. def dance(self, irc, msg, args):
  752. """ """
  753. irc.reply(" o/", prefixNick=False)
  754. irc.reply("/|", prefixNick=False)
  755. irc.reply(" >>", prefixNick=False)
  756.  
  757. def toast(self, irc, msg, args):
  758. """ """
  759. irc.reply(" o . o o ", prefixNick=False)
  760. irc.reply(" . o _o_._'_ ", prefixNick=False)
  761. irc.reply(" o_.__'\~~~~~/ ", prefixNick=False)
  762. irc.reply("\~~~~~/ '-.-' ", prefixNick=False)
  763. irc.reply(" '-.-' | ", prefixNick=False)
  764. irc.reply(" | _|_ ", prefixNick=False)
  765. irc.reply(' _|_ `"""` ', prefixNick=False)
  766. irc.reply(' `"""` ', prefixNick=False)
  767.  
  768. def pong(self, irc, msg, args):
  769. """ """
  770. irc.reply("|˙ |", prefixNick=False)
  771. irc.reply("| ‧ |", prefixNick=False)
  772. irc.reply("| .|", prefixNick=False)
  773. irc.reply("| . |", prefixNick=False)
  774. irc.reply("|. |", prefixNick=False)
  775. irc.reply("| ‧ |", prefixNick=False)
  776. irc.reply("| |‧", prefixNick=False)
  777. irc.reply("I win!", prefixNick=False)
  778.  
  779. def grumpycat(self, irc, msg, args):
  780. """ """
  781. irc.reply(" ^ ^ ", prefixNick=False)
  782. irc.reply(" . . ", prefixNick=False)
  783. irc.reply(" > n < ", prefixNick=False)
  784. irc.reply(" ", prefixNick=False)
  785. irc.reply(" NO ", prefixNick=False)
  786.  
  787. def stab(self, irc, msg, args):
  788. """ """
  789. irc.reply("o()xxxx[{::::::*" + ' '.join(args) + "*::::::>", prefixNick=False)
  790.  
  791. def halfbaked(self, irc, msg, args):
  792. """
  793. returns a radom half-baked idea from http://halfbakery.com
  794. """
  795. try:
  796. soup = self._url2soup('http://www.halfbakery.com/random-idea.html')
  797. except HTTPError, e:
  798. irc.reply('http error %s for %s' % (e.code, url), prefixNick=True); return
  799. except StopParsing, e:
  800. irc.reply('parsing error %s for %s' % (e.code, url), prefixNick=True); return
  801.  
  802. idea = soup.find('a', {'name': 'idea'})
  803. title = idea.h1.string
  804. try:
  805. subtitle = idea.parent.find('font', 'fcl').string
  806. title = '%s -- %s' % (title, subtitle)
  807. except:
  808. pass
  809.  
  810. irc.reply(title, prefixNick=True)
  811.  
  812. def dow(self, irc, msg, args):
  813. """
  814. Get the Dow Jones Industrial Average from teh GOOG
  815. """
  816. irc.reply(self._cid_quote(983582,args))
  817.  
  818. def nasdaq(self, irc, msg, args):
  819. """
  820. Get the NASDAQ from teh GOOG
  821. """
  822. irc.reply(self._cid_quote(13756934,args))
  823.  
  824. def sandp(self, irc, msg, args):
  825. """
  826. Get the S&P from teh GOOG
  827. """
  828. irc.reply(self._cid_quote(626307,args))
  829.  
  830. def ftse(self, irc, msg, args):
  831. """
  832. Get the FTSE from teh GOOG
  833. """
  834. irc.reply(self._cid_quote(12590587,args))
  835.  
  836. def _cid_quote(self, cid, args):
  837. """ """
  838. directions = { '+' : 'up', '-' : 'down' }
  839. formats = {
  840. 'def' : "%(idx)s; %(sign)s%(diff)s (%(sign)s%(pct)s)",
  841. 'eng' : "The %(name)s is %(direction)s %(diff)s points (%(pct)s) to %(idx)s."
  842. }
  843. try:
  844. format = formats[args[0].lower()[0:3]]
  845. except:
  846. format = formats['def']
  847.  
  848. # TODO: make this so you can use an ticker
  849. try:
  850. soup = self._url2soup("http://finance.google.com/finance?cid=%s" % cid)
  851. except:
  852. return 'Is it possible Google is sending back invalid HTML?'
  853. data = {}
  854. try:
  855. data = {
  856. 'idx' : soup.find(id="ref_%s_l" % cid).string,
  857. 'diff' : soup.find(id="ref_%s_c" % cid).string or '+0',
  858. 'pct' : soup.find(id="ref_%s_cp" % cid).string or '0%',
  859. 'name' : soup.find('h3').string
  860. }
  861. data['sign'] = data['diff'][0]
  862. data['direction'] = directions[data['sign']]
  863. data['diff'] = data['diff'].strip('+-')
  864. data['pct'] = data['pct'].strip('()+-')
  865. except:
  866. raise
  867. return 'ruhroh, me no speak google'
  868. return format % data
  869.  
  870. def stock(self, irc, msg, args):
  871. """ """
  872. t = quote(' '.join(args))
  873. soup = self._url2soup("http://finance.google.com/finance?q=%s" % t)
  874. match = re.search('var _companyId = (\d+)', str(soup))
  875. if not match:
  876. irc.reply("gah, couldn't find stock ticker %s" % t)
  877. return
  878. try:
  879. cid = match.group(1)
  880. idx = soup.find(id="ref_%s_l" % cid).string.replace('&nbsp;', '')
  881. p = soup.find(id="ref_%s_cp" % cid).string.replace('&nbsp;', '')
  882. mktcap = soup.find('span', text="Mkt cap").next.next.string
  883. pe = soup.find('span', text='P/E').next.next.string
  884. # hi = soup.find(id="ref_%s_hi" % cid).string.replace('&nbsp;', '')
  885. # lo = soup.find(id="ref_%s_lo" % cid).string.replace('&nbsp;', '')
  886. # hi52 = soup.find(id="ref_%s_hi52" % cid).string.replace('&nbsp;','')
  887. # lo52 = soup.find(id="ref_%s_lo52" % cid).string.replace('&nbsp;','')
  888. name = ' - '.join(soup.find('title').string.split('-')[0:2]).strip()
  889.  
  890. # irc.reply("%s - %s %s high:%s low:%s high52:%s low52:%s p/e:%s mktcap:%s" % (name, idx, p, hi, lo, hi52, lo52, pe, mktcap))
  891. irc.reply("%s - %s %s p/e:%s mktcap:%s" % (name, idx, p, pe, mktcap))
  892. except:
  893. irc.reply("ruhroh, me no speak google")
  894.  
  895. def prez(self, irc, msg, args):
  896. """
  897. Returns expected Presedential race results from http://www.electoral-vote.com/
  898. """
  899. irc.reply(self._electoral(0))
  900.  
  901. def senate(self, irc, msg, args):
  902. """
  903. Returns expected Senate race results from http://www.electoral-vote.com/
  904. """
  905. irc.reply(self._electoral(1))
  906.  
  907. def house(self, irc, msg, args):
  908. """
  909. Returns expected House race results from http://www.electoral-vote.com/
  910. """
  911. irc.reply(self._electoral(2))
  912.  
  913. def _electoral(self, idx):
  914. """ """
  915. soup = self._url2soup("http://www.electoral-vote.com/")
  916. dems = soup.findAll('span', {'class':'dem'})
  917. gops = soup.findAll('span', {'class':'gop'})
  918. ties = gops[idx].next.next
  919. return ', '.join([x.string.strip() for x in [dems[idx], gops[idx], ties]])
  920.  
  921. def debt(self, irc, msg, args):
  922. """
  923. Returns current US gross national debt as
  924. calculated at http://zfacts.com/p/461.html
  925. """
  926. # values taken from http://zfacts.com/giz/G05ndc.js on 2008-10-03
  927. gndstart = 10124225067127.69
  928. add_debt_per_year = 676 # in $ billions
  929. add_debt_per_sec = add_debt_per_year*1000000000/(365*24*60*60)
  930. from_date = datetime(2008, 10, 01, 11, 0, 0)
  931. now = datetime.now()
  932. delta = now - from_date
  933. add_debt = delta.seconds * add_debt_per_sec
  934. gnd = gndstart + add_debt
  935. gnd_str = '%.2f' % gnd
  936. gnd_whole, gnd_decimal = gnd_str.split('.')
  937. response = '%sdollars and %scents' % (int2word(gnd_whole),
  938. int2word(gnd_decimal))
  939. irc.reply(response)
  940.  
  941. def lhc(self, irc, msg, args):
  942. """
  943. reports how much time is left until the HLC starts smashing reality^Watoms
  944. """
  945. targetdate = datetime(2008, 10, 21, 13, 0, 0)
  946. now = datetime.now()
  947. delta = targetdate - now
  948. irc.reply('%d days left' % delta.days)
  949.  
  950. def nytags(self, irc, msg, args, **opts):
  951. """ """
  952. url = 'http://api.nytimes.com/svc/timestags/suggest'
  953. key = 'aa51418354ed1dc2378f9679a04a0f54:14:13476879'
  954. q = urlencode({'query': ' '.join(args), 'api-key': key})
  955. json = urlopen("%s?%s" % (url, q)).read()
  956. tags = simplejson.loads(json)
  957. result = ', '.join(tags['results'])
  958. irc.reply(result)
  959.  
  960. def flu(self, irc, msg, args, loc):
  961. """
  962. Estimate of flu search activity for a given region or location. --
  963. Usage: flu [location] --
  964. Enter "list" as location for a list of options ---
  965. Data from http://www.google.org/flutrends/
  966. """
  967. try:
  968. reader = csv.reader(urlopen("http://www.google.org/flutrends/data.txt"))
  969. except:
  970. print "Error fetching flu data: ", sys.exc_info()[0]
  971. data = [row for row in reader if len(row) > 2]
  972. locations = [x.lower() for x in data[0][1:]]
  973. if loc == 'list':
  974. resp = ', '.join(locations)
  975. irc.reply(resp)
  976. return
  977. latest_data = ["%.2f" % float(x) for x in data[-1][1:]]
  978. previous_data = ["%.2f" % float(x) for x in data[-2][1:]]
  979. latest = dict(zip(locations, latest_data))
  980. previous = dict(zip(locations, previous_data))
  981. if loc.lower() not in latest:
  982. irc.reply("No data for location: %s" % loc)
  983. else:
  984. trend = cmp(latest[loc.lower()], previous[loc.lower()])
  985. if trend < 0:
  986. trend = 'down'
  987. elif trend == 0:
  988. trend = 'stable'
  989. else:
  990. trend = 'up'
  991. resp = "As of %s the flu search activity index for %s was %s; trend is %s" \
  992. % (data[-1][0], loc, latest[loc.lower()], trend)
  993. irc.reply(resp)
  994.  
  995. flu = wrap(flu, ['text'])
  996.  
  997. def orsome(self, irc, msg, args, splitter, s):
  998. """Usage: orsome [/separator/] string"""
  999. if not s:
  1000. return
  1001. if splitter:
  1002. items = [x.strip() for x in re.split(splitter, s) if len(x)]
  1003. else:
  1004. items = [x.strip() for x in re.split('\s+', s) if len(x)]
  1005. irc.reply(' or '.join(items), prefixNick=False)
  1006.  
  1007. orsome = wrap(orsome, [optional('regexpMatcher'), 'text'])
  1008.  
  1009. def decide(self, irc, msg, args, opts, choices):
  1010. """fwgew """
  1011. prefix = "go with "
  1012. for opt,arg in opts:
  1013. if opt == 'raw':
  1014. prefix = ""
  1015.  
  1016. seed(choices)
  1017.  
  1018. pattern = re.compile('\s+or\s+', re.I)
  1019. clist = re.split(pattern, choices)
  1020. if randint(0, 10) == 0:
  1021. irc.reply("That's a tough one...")
  1022. return
  1023. irc.reply(prefix + clist[randint(0, len(clist)-1)])
  1024.  
  1025. seed()
  1026.  
  1027. decide = wrap(decide, [getopts({'raw':''}),'text'])
  1028.  
  1029. def should(self, irc, msg, args, name, choices):
  1030. """ """
  1031. choices = re.sub('[^A-Za-z0-9]+$','',choices)
  1032. adverbs = ['','','','totally', 'absolutely', 'probably']
  1033. pattern = re.compile('\s+or(?:\s+should \S+?)?\s+', re.I)
  1034. clist = re.split(pattern, choices)
  1035. if re.match('I',name,re.I):
  1036. name = 'You'
  1037. elif re.match('You',name,re.I):
  1038. name = 'I'
  1039.  
  1040. if randint(0, 10) == 0:
  1041. irc.reply("That's a tough one...")
  1042. return
  1043. adverb = random.choice(adverbs)
  1044. if len(adverb) > 0:
  1045. adverb = adverb + ' '
  1046. action = random.choice(clist)
  1047. response = "%s should %s%s." % (name, adverb, action)
  1048. irc.reply(response)
  1049.  
  1050. should = wrap(should, ['somethingWithoutSpaces','text'])
  1051.  
  1052. def pick(self, irc, msg, args, choices):
  1053. """
  1054. @decide without the niceties
  1055. """
  1056. pattern = re.compile('\s+or\s+', re.I)
  1057. clist = re.split(pattern, choices)
  1058. if randint(0, 10) == 0:
  1059. irc.reply("That's a tough one...")
  1060. return
  1061. irc.reply(clist[randint(0, len(clist)-1)])
  1062.  
  1063. pick = wrap(pick, ['text'])
  1064.  
  1065. def _url2soup(self, url, qsdata={}, postdata=None, headers={}):
  1066. """
  1067. Fetch a url and BeautifulSoup-ify the returned doc
  1068. """
  1069. ua = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11'
  1070. headers.update({'User-Agent': ua})
  1071. params = urlencode(qsdata)
  1072. if params:
  1073. if '?' in url:
  1074. url = "%s&%s" % (url,params)
  1075. else:
  1076. url = "%s?%s" % (url,params)
  1077. req = Request(url,postdata,headers)
  1078. doc = urlopen(req)
  1079. data = doc.read()
  1080. soup = BeautifulSoup(data, convertEntities=BeautifulSoup.HTML_ENTITIES)
  1081. return soup
  1082.  
  1083. def coffee(self, irc, msg, args):
  1084. """
  1085. makes and pours a sensational cup of coffee from the highest rated
  1086. coffees at ttp://www.coffeereview.com/allreviews.cfm?search=1
  1087. """
  1088. f = join(dirname(abspath(__file__)), 'coffee.html')
  1089. data = open(f).read()
  1090. root = lxml.html.soupparser.fromstring(data, convertEntities=False)
  1091. nodes = root.xpath('//div[@class="review_general2"]//h3')
  1092. coffees = [x.text_content() for x in nodes]
  1093. coffee = coffees[randint(0, len(coffees))]
  1094. if len(args) > 0:
  1095. nick = ' '.join(args)
  1096. else:
  1097. nick = msg.nick
  1098. irc.reply("brews and pours a cup of %s, and sends it sliding down the bar to %s" % (coffee, nick), action=True)
  1099.  
  1100.  
  1101.  
  1102. def tea(self, irc, msg, args):
  1103. """
  1104. makes and pours a sensational cup of tea from the highest rated
  1105. teas at http://ratetea.com/highest_rated_teas.php
  1106. """
  1107. soup = self._url2soup('http://ratetea.com/highest_rated_teas.php')
  1108. rows = soup.findAll('tr')[1:]
  1109. teas = [t.a for t in rows]
  1110. tea = teas[randint(0, len(teas)-1)]
  1111. tea_name = tea.string
  1112. tea_url = "http://ratetea.com" + tea['href']
  1113. if len(args) > 0:
  1114. nick = ' '.join(args)
  1115. else:
  1116. nick = msg.nick
  1117. irc.reply("brews and pours a pot of %s, and sends it sliding down the bar to %s (%s)" % (tea_name, nick, tea_url), action=True)
  1118.  
  1119. def bartender(self, irc, msg, args):
  1120. """
  1121. pours a beer from an archived copy of
  1122. http://web.mit.edu/~tcarlile/www/beer/beerlist.html
  1123. for you
  1124. """
  1125. f = join(dirname(abspath(__file__)), 'beerlist.html')
  1126. data = open(f).read()
  1127. soup = BeautifulSoup(data, convertEntities=BeautifulSoup.HTML_ENTITIES)
  1128. beers = []
  1129. for li in soup.findAll('li'):
  1130. if li.a:
  1131. beers.append((li.a['href'], li.a.string))
  1132. beer = beers[randint(0, len(beers))]
  1133. if len(args) > 0:
  1134. nick = ' '.join(args)
  1135. else:
  1136. nick = msg.nick
  1137. irc.reply("fills a pint glass with %s, and sends it sliding down the bar to %s (%s)" % (beer[1], nick, beer[0]), action=True)
  1138.  
  1139. def whisky(self, irc, msg, args):
  1140. """
  1141. pours a shot of whisky for you from http://whisky.com/
  1142. """
  1143. raw_html = urlopen('http://whisky.com/select.html').read()
  1144. raw_html = raw_html.replace('<option brands/>','<option>').replace('<option value=>','<option>')
  1145. soup = BeautifulSoup(raw_html)
  1146. menu = []
  1147. for option in soup.findAll('option'):
  1148. try:
  1149. menu.append((option['value'], option.string.replace('_',' ')))
  1150. except KeyError:
  1151. pass
  1152. order = menu[randint(0, len(menu))]
  1153. if len(args) > 0:
  1154. nick = ' '.join(args)
  1155. else:
  1156. nick = msg.nick
  1157. irc.reply("pours a shot of %s and sends it sliding down the bar to %s (http://whisky.com/%s)" % (order[1], nick, order[0]), action=True)
  1158.  
  1159. whiskey = whisky
  1160.  
  1161. def swill(self, irc, msg, args):
  1162. """
  1163. pours...something else.
  1164. """
  1165. catalog = [
  1166. ["forty of", ["Colt 45", "Camo 40", "Black Fist", "Country Club", "Olde English 800", "Mickey's", "Black Bull",
  1167. "Labatt Blue Dry 7.1", "WildCat", "Molson Dry 6.5/7.5/8.5/10.1", "Private Stock", "Big Bear", "St. Ides",
  1168. "Steel Reserve 211", "B40 Bull Max", "King Cobra", "Jeremiah Weed", "Hurricane"]],
  1169. ["bottle of", ["Zima", "Extra Dry Champale", "Pink Champale", "Golden Champale", "Night Train", "MD 20/20", "Thunderbird", "Wild Irish Rose", "Cisco Red"]],
  1170. ["can of Sparks", ["", "Light","Plus","Red","Stinger"]],
  1171. ["bottle of Smirnoff Ice", ["Watermelon","Wild Grape","Passionfruit","Mango","Triple Black","Pomegranate Fusion",
  1172. "Arctic Berry", "Green Apple Bite","Strawberry Acai","Pineapple","Raspberry Burst"]]
  1173. ]
  1174. menu = []
  1175. package,options = catalog[randint(0, len(catalog)-1)]
  1176. for option in options:
  1177. menu.append(('%s %s' % (package, option)).strip())
  1178. if len(args) > 0:
  1179. nick = ' '.join(args)
  1180. else:
  1181. nick = msg.nick
  1182. order = menu[randint(0, len(menu)-1)]
  1183. irc.reply("grabs a %s and sends it sliding down the bar to %s" % (order, nick), action=True)
  1184.  
  1185. def anon(self, irc, msg, args):
  1186. """
  1187. Spits out random nonsense from 'anon,' that loveable idiot of a troll
  1188. """
  1189. trolls = ["Hey Roy, get a life.",
  1190. "Did OCLC put co-option of code4lib in your performance review for 2009?",
  1191. "you don't belong to code4lib, since you acted in an unwelcome fashion there and were shunned accordingly.",
  1192. "How about enabling comments over on your site, Winer of the library world?",
  1193. "Roy's a big boy--think he can speak to OCLC's desire to act like Microsoft and assert dominance over an informal organization",
  1194. "Follow the money on the code4libcon Google group",
  1195. "Awesome, hypocrisy.",
  1196. "Let's say that OCLC and code4lib is about as awesome as DSpace plus HP.",
  1197. "OCLC as largest code4lib sponsor=death",
  1198. "Roy as OCLC shill=shame for LJ and code4lib",
  1199. "Roy=saint, or Joe of Arc since leaving CDL?",
  1200. "court fool of the library world",
  1201. "aren't you on strike or something?",
  1202. "Hey Roy, what does OCLC think about code4lib?",
  1203. "the community voted the ridiculous Ohio location down, so it does know what sponsorship is, indeed",
  1204. "Isn't Ohio the state that doesn't know the difference between a business and a non-profit cooperative?",
  1205. "OCLC is using Roy and its money to influence a loose collective in the process of expanding.",
  1206. "are you affiliated with OCLC?",
  1207. "But you don't now--you're a vendor.",
  1208. "May as well post an OCLC ad, since you're shilling for their services in your presentation.",
  1209. "Oh, does this benefit WorldCat Local?",
  1210. "RFP's exist because libraries are public. The Oh Can Libraries Concede trust would prefer otherwise, I'm sure",
  1211. "if channels matter, then why not community dot oclc dot org slash roy?",
  1212. "Nice OCLC shill, Roy."
  1213. ]
  1214. troll = trolls[randint(0, len(trolls)-1)]
  1215. irc.reply(troll, prefixNick=True)
  1216.  
  1217. def _diebold_tallies(self, tally='', year=''):
  1218. """ Gets a tally from the diebold-o-tron """
  1219. base_url = 'http://vote.code4lib.org/election/'
  1220. tallies = {
  1221. 'keynotes': {'2009': '4',
  1222. '2010': '11',
  1223. '2011': '16',
  1224. '2012': '20',
  1225. '2013': '23'},
  1226. 'necode4lib': {'2008': '5'},
  1227. 'tshirts': {'2009': '8',
  1228. '2010': '14',
  1229. '2011': '18'},
  1230. 'hosts': {'2009': '3',
  1231. '2010': '9',
  1232. '2011': '15'},
  1233. 'talks': {'2008': '2',
  1234. '2009': '7',
  1235. '2010': '13',
  1236. '2011': '17',
  1237. '2012': '21',
  1238. '2013': '24'},
  1239. 'logo': {'2008': '6'}
  1240. }
  1241. try:
  1242. poll_number = tallies[tally][year]
  1243. except KeyError:
  1244. raise PollNotFoundException("tally or year not found")
  1245. poll_url = base_url + 'results/' + poll_number
  1246. vote_url = base_url + poll_number
  1247. from socket import setdefaulttimeout
  1248. setdefaulttimeout(60)
  1249. try:
  1250. json = urlopen(Request(poll_url, None, {'Accept': 'application/json'})).read()
  1251. except HTTPError, e:
  1252. raise PollNotFoundException("%s" % e)
  1253. json = re.sub(r'\\[0-9A-fa-f]{3}', '', json)
  1254. try:
  1255. votes = simplejson.loads(json)
  1256. except UnicodeDecodeError:
  1257. votes = simplejson.loads(json.decode('ascii', 'ignore'))
  1258. votes.sort(cmp=lambda x,y: int(y['score'])-int(x['score']))
  1259. tallies = [(vote['title'], vote['score']) for vote in votes]
  1260. return tallies, vote_url
  1261.  
  1262. def talks2013(self, irc, msg, args):
  1263. """
  1264. Gets tally of talk votes for 2012 conference
  1265. """
  1266. try:
  1267. tallies, vote_url = self._diebold_tallies("talks", "2013")
  1268. except PollNotFoundException, pnfe:
  1269. irc.reply("Poll not found for talk votes in 2013: %s" % pnfe)
  1270. else:
  1271. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1272. irc.reply("Voting link: %s" % vote_url)
  1273.  
  1274. def talks2012(self, irc, msg, args):
  1275. """
  1276. Gets tally of talk votes for 2012 conference
  1277. """
  1278. try:
  1279. tallies, vote_url = self._diebold_tallies("talks", "2012")
  1280. except PollNotFoundException, pnfe:
  1281. irc.reply("Poll not found for talk votes in 2012: %s" % pnfe)
  1282. else:
  1283. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1284. irc.reply("Voting link: %s" % vote_url)
  1285.  
  1286. def talks2011(self, irc, msg, args):
  1287. """
  1288. Gets tally of talk votes for 2011 conference
  1289. """
  1290. try:
  1291. tallies, vote_url = self._diebold_tallies("talks", "2011")
  1292. except PollNotFoundException, pnfe:
  1293. irc.reply("Poll not found for talk votes in 2011: %s" % pnfe)
  1294. else:
  1295. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1296. irc.reply("Voting link: %s" % vote_url)
  1297.  
  1298. def talks2010(self, irc, msg, args):
  1299. """
  1300. Gets tally of talk votes for 2010 conference
  1301. """
  1302. try:
  1303. tallies, vote_url = self._diebold_tallies("talks", "2010")
  1304. except PollNotFoundException, pnfe:
  1305. irc.reply("Poll not found for talk votes in 2010: %s" % pnfe)
  1306. else:
  1307. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1308.  
  1309. def talks2009(self, irc, msg, args):
  1310. """
  1311. Gets tally of talk votes for 2009 conference
  1312. """
  1313. try:
  1314. tallies, vote_url = self._diebold_tallies("talks", "2009")
  1315. except PollNotFoundException, pnfe:
  1316. irc.reply("Poll not found for talk votes in 2009: %s" % pnfe)
  1317. else:
  1318. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1319.  
  1320. def tshirts2009(self, irc, msg, args):
  1321. """
  1322. Gets tally of t-shirt votes for 2009 conference
  1323. """
  1324. try:
  1325. tallies, vote_url = self._diebold_tallies("tshirts", "2009")
  1326. except PollNotFoundException, pnfe:
  1327. irc.reply("Poll not found for t-shirt votes in 2009: %s" % pnfe)
  1328. else:
  1329. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1330.  
  1331. def tshirts2010(self, irc, msg, args):
  1332. """
  1333. Gets tally of t-shirt votes for 2010 conference
  1334. """
  1335. try:
  1336. tallies, vote_url = self._diebold_tallies("tshirts", "2010")
  1337. except PollNotFoundException, pnfe:
  1338. irc.reply("Poll not found for t-shirt votes in 2010: %s" % pnfe)
  1339. else:
  1340. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1341.  
  1342. def tshirts2011(self, irc, msg, args):
  1343. """
  1344. Gets tally of t-shirt votes for 2011 conference
  1345. """
  1346. try:
  1347. tallies, vote_url = self._diebold_tallies("tshirts", "2011")
  1348. except PollNotFoundException, pnfe:
  1349. irc.reply("Poll not found for t-shirt votes in 2011: %s" % pnfe)
  1350. else:
  1351. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1352.  
  1353. def bubble2009(self, irc, msg, args):
  1354. """
  1355. List talks that are within 4 ranks of the cutoff range for the 2009 conference
  1356. """
  1357. cutoff = 22
  1358. try:
  1359. tallies, vote_url = self._diebold_tallies("talks", "2009")
  1360. except PollNotFoundException, pnfe:
  1361. irc.reply("Poll not found for talk votes in 2009: %s" % pnfe)
  1362. else:
  1363. irc.reply(('; '.join("%s [%s]" % t for t in tallies[cutoff-3:cutoff+2])).encode('utf-8'))
  1364.  
  1365. def talks2008(self, irc, msg, args):
  1366. """
  1367. Gets tally of talk votes for 2008 conference
  1368. """
  1369. try:
  1370. tallies, vote_url = self._diebold_tallies("talks", "2008")
  1371. except PollNotFoundException, pnfe:
  1372. irc.reply("Poll not found for talk votes in 2008: %s" % pnfe)
  1373. else:
  1374. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1375.  
  1376.  
  1377. def logo2008(self, irc, msg, args):
  1378. """
  1379. Gets tally of logo votes for 2008
  1380. """
  1381. try:
  1382. tallies, vote_url = self._diebold_tallies("logo", "2008")
  1383. except PollNotFoundException, pnfe:
  1384. irc.reply("Poll not found for logos in 2008: %s" % pnfe)
  1385. else:
  1386. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1387.  
  1388. def hosts2009(self, irc, msg, args):
  1389. """
  1390. Gets tally of host votes for 2009 conference
  1391. """
  1392. try:
  1393. tallies, vote_url = self._diebold_tallies("hosts", "2009")
  1394. except PollNotFoundException, pnfe:
  1395. irc.reply("Poll not found for hosts in 2009: %s" % pnfe)
  1396. else:
  1397. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1398.  
  1399. def hosts2010(self, irc, msg, args):
  1400. """
  1401. Gets tally of host votes for 2010 conference
  1402. """
  1403. try:
  1404. tallies, vote_url = self._diebold_tallies("hosts", "2010")
  1405. except PollNotFoundException, pnfe:
  1406. irc.reply("Poll not found for hosts in 2010: %s" % pnfe)
  1407. else:
  1408. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1409.  
  1410. def hosts2011(self, irc, msg, args):
  1411. """
  1412. Gets tally of host votes for 2011 conference
  1413. """
  1414. try:
  1415. tallies, vote_url = self._diebold_tallies("hosts", "2011")
  1416. except PollNotFoundException, pnfe:
  1417. irc.reply("Poll not found for hosts in 2011: %s" % pnfe)
  1418. else:
  1419. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1420.  
  1421. def hosts2012(self, irc, msg, args):
  1422. """
  1423. Gets tally of host votes for 2012 conference
  1424. """
  1425. irc.reply("Seattle [*]")
  1426.  
  1427. def keynotes2009(self, irc, msg, args):
  1428. """
  1429. Gets tally of keynoter votes for 2009 conference
  1430. """
  1431. try:
  1432. tallies, vote_url = self._diebold_tallies("keynotes", "2009")
  1433. except PollNotFoundException, pnfe:
  1434. irc.reply("Poll not found for keynotes in 2009: %s" % pnfe)
  1435. else:
  1436. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1437.  
  1438. def keynotes2010(self, irc, msg, args):
  1439. """
  1440. Gets tally of keynoter votes for 2010 conference
  1441. """
  1442. try:
  1443. tallies, vote_url = self._diebold_tallies("keynotes", "2010")
  1444. except PollNotFoundException, pnfe:
  1445. irc.reply("Poll not found for keynotes in 2010: %s" % pnfe)
  1446. else:
  1447. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1448.  
  1449. def keynotes2011(self, irc, msg, args):
  1450. """
  1451. Gets tally of keynoter votes for 2011 conference
  1452. """
  1453. try:
  1454. tallies, vote_url = self._diebold_tallies("keynotes", "2011")
  1455. except PollNotFoundException, pnfe:
  1456. irc.reply("Poll not found for keynotes in 2011: %s" % pnfe)
  1457. else:
  1458. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1459. irc.reply("Have you voted? %s" % vote_url)
  1460.  
  1461. def keynotes2012(self, irc, msg, args):
  1462. """
  1463. Gets tally of keynoter votes for 2012 conference
  1464. """
  1465. try:
  1466. tallies, vote_url = self._diebold_tallies("keynotes", "2012")
  1467. except PollNotFoundException, pnfe:
  1468. irc.reply("Poll not found for keynotes in 2012: %s" % pnfe)
  1469. else:
  1470. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1471. irc.reply("Have you voted? %s" % vote_url)
  1472.  
  1473. def keynotes2013(self, irc, msg, args):
  1474. """
  1475. Gets tally of keynoter votes for 2012 conference
  1476. """
  1477. try:
  1478. tallies, vote_url = self._diebold_tallies("keynotes", "2013")
  1479. except PollNotFoundException, pnfe:
  1480. irc.reply("Poll not found for keynotes in 2013: %s" % pnfe)
  1481. else:
  1482. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1483. irc.reply("Have you voted? %s" % vote_url)
  1484.  
  1485. #def hosts2013(self, irc, msg, args):
  1486. # irc.reply("I'm sorry. That request involves dates after December 21, 2012, and is therefore not worth any of my precious remaining time.")
  1487.  
  1488. #talks2013 = keynotes2013 = tshirts2013 = hosts2013
  1489.  
  1490. def once(self, irc, msg, args, s):
  1491. """ """
  1492. irc.reply("%s once.... Once." % s, prefixNick=False)
  1493.  
  1494. once = wrap(once, ['text'])
  1495.  
  1496. def inaworld(self, irc, msg, args, s):
  1497. irc.reply("In a world... %s..." % s, prefixNick=False)
  1498.  
  1499. inaworld = wrap(inaworld, ['text'])
  1500.  
  1501. def cluephone(self, irc, msg, args, who):
  1502. """
  1503. Rings the cluephone. Is it for you?
  1504. """
  1505. irc.reply("BBRRRRrrringgg! It's for %s" % who, prefixNick=False)
  1506.  
  1507. cluephone = wrap(cluephone, ['text'])
  1508.  
  1509. def necode4lib(self, irc, msg, args):
  1510. """
  1511. Gets tally of location votes for 2008 New England code4lib conference
  1512. """
  1513. try:
  1514. tallies, vote_url = self._diebold_tallies("necode4lib", "2008")
  1515. except PollNotFoundException, pnfe:
  1516. irc.reply("Poll not found for necode4lib in 2008: %s" % pnfe)
  1517. else:
  1518. irc.reply(('; '.join("%s [%s]" % t for t in tallies)).encode('utf-8'))
  1519.  
  1520. def reminder(self, irc, msg, args, nick, seconds, note):
  1521. """
  1522. Set a timer to remind you to do something - Usage: reminder seconds [note]
  1523. """
  1524. note = note or "This is your reminder to do that thing"
  1525.  
  1526. def remind():
  1527. if nick:
  1528. irc.reply("DING DING DING! " + note, to=nick)
  1529. else:
  1530. irc.reply("DING DING DING! " + note)
  1531.  
  1532. t = Timer(float(seconds), remind)
  1533. t.start()
  1534. irc.reply("OK. I'll remind you in %s seconds" % seconds)
  1535.  
  1536. reminder = wrap(reminder, [optional('nickInChannel'), 'int', optional('text')])
  1537.  
  1538. def cowbell(self, irc, msg, args):
  1539. """<text>
  1540. adds more cowbell!
  1541. """
  1542. cowbell = " *cowbell* "
  1543. if len(args) > 1:
  1544. s = cowbell.join(args)
  1545. elif len(args) == 1:
  1546. s = re.compile(r' ').sub(cowbell, args[0])
  1547. else:
  1548. s = cowbell
  1549. irc.reply(s, prefixNick=True)
  1550.  
  1551. def sheen(self, irc, msg, args):
  1552. """
  1553. Melting your face off.
  1554. """
  1555. quote = unicode(sheendata.quotes[randint(0, len(sheendata.quotes))])
  1556. irc.reply(quote.encode('utf-8'), prefixNick=False)
  1557.  
  1558. def slowclap(self, irc, msg, args):
  1559. """
  1560. Show your heartfelt appreciation (or mockery) of an
  1561. underdog who surprises everyone with his or her
  1562. classiness, gumption and/or dancing ability
  1563. """
  1564. irc.reply("*clap*", prefixNick=False)
  1565. time.sleep(1)
  1566. irc.reply("*clap*", prefixNick=False)
  1567. time.sleep(1)
  1568. irc.reply("*clap*", prefixNick=False)
  1569. time.sleep(1)
  1570. irc.reply("*clap*", prefixNick=False)
  1571.  
  1572. def excuse(self, irc, msg, args):
  1573. """
  1574. returns the excuse of the day http://meyerweb.com/feeds/excuse/
  1575. """
  1576.  
  1577. ns = 'http://www.w3.org/1999/xhtml'
  1578. url = 'http://meyerweb.com/feeds/excuse/'
  1579. xpathLite = ('{%s}body/{%s}div/{%s}div/{%s}p' % (ns, ns, ns, ns))
  1580. try:
  1581. tree = TidyHTMLTreeBuilder.parse(urlopen(url));
  1582. except HTTPError, e:
  1583. irc.reply('http error %s for %s' % (e.code, url), prefixNick=True)
  1584. return
  1585. except StopParsing, e:
  1586. irc.reply('parsing error %s for %s' % (e.code, url), prefixNick=True)
  1587. return
  1588.  
  1589. excuseNode = tree.find(xpathLite)
  1590.  
  1591. excuseStr = excuseNode.text
  1592. irc.reply('My excuse for today is "%s"' % excuseStr, prefixNick=True)
  1593.  
  1594. def swineflu(self, irc, msg, args):
  1595. """ """
  1596. soup = self._url2soup('http://www.cdc.gov/swineflu/?s_cid=swineFlu_outbreak_internal_001')
  1597. cases = soup.find('td', text=re.compile('\d+ cases')).string
  1598. deaths = soup.find('td', text=re.compile('\d+ death')).string
  1599. irc.reply(('Total: %s, %s' % (cases, deaths)) + '; <http://icanhaz.com/swine-flu>')
  1600.  
  1601. def bar(self, irc, msg, args):
  1602. """ """
  1603. qhash = md5("%s%s%s%d" % ('zoia','random_by_author','william shakespeare', 1))
  1604. url = 'http://quotesdaddy.com/api/zoia/'
  1605.  
  1606. def blues(self, irc, msg, args, trouble):
  1607. """
  1608. sing your troubles away
  1609. """
  1610. song = "I woke up this morning / %s / I woke up this morning / Lord, %s" \
  1611. % (trouble, trouble)
  1612. irc.reply(song, prefixNick=False)
  1613.  
  1614. blues = wrap(blues, ['text'])
  1615.  
  1616. def tdih(self, irc, msg, args):
  1617. """
  1618. Get a piece of computing history from http://www.computerhistory.org/tdih.
  1619. """
  1620. url = 'http://www.computerhistory.org/tdih'
  1621. soup = self._url2soup(url)
  1622. try:
  1623. # kinda fragile, but whatevs
  1624. h3 = soup.find('h3')
  1625. date = h3.string
  1626. title = h3.nextSibling.nextSibling.string
  1627. text = h3.nextSibling.nextSibling.nextSibling.nextSibling.string
  1628. msg = "[%s] %s - %s" % (date, title, text)
  1629. except RuntimeError, e:
  1630. msg = "d'oh something is b0rk3n: %s" % e
  1631. irc.reply(msg.encode('utf-8'))
  1632.  
  1633. history = tdih
  1634.  
  1635. def snow(self, irc, msg, args):
  1636. """ """
  1637. flake = """
  1638. o
  1639. o : o
  1640. '.\'/.'
  1641. :->@<-:
  1642. .'/.\'.
  1643. o : o
  1644. o
  1645. """
  1646. for line in flake.split("\n"):
  1647. if line:
  1648. irc.reply(line, prefixNick=False)
  1649.  
  1650. def pony(self, irc, msg, args, who):
  1651. """
  1652. zOMG PONieZ!!
  1653. """
  1654. pony = """ ,//)
  1655. ,;;' \
  1656. ,;;' ( '\
  1657. / '\_)"""
  1658. if who:
  1659. who = who + "'s"
  1660. else:
  1661. who = 'yours'
  1662.  
  1663. for line in pony.split("\n"):
  1664. if line:
  1665. irc.reply(line, prefixNick=False)
  1666. resp = '^^ not %s' % who
  1667. irc.reply(resp, prefixNick=False)
  1668.  
  1669. pony = wrap(pony, [optional('text')])
  1670.  
  1671. def _random_nick(self, irc, msg, args, channel):
  1672. """ """
  1673. # Modified from Channel.nicks
  1674. #
  1675. # Make sure we don't elicit information about private channels to
  1676. # people or channels that shouldn't know
  1677. if 's' in irc.state.channels[channel].modes and \
  1678. msg.args[0] != channel and \
  1679. (ircutils.isChannel(msg.args[0]) or \
  1680. msg.nick not in irc.state.channels[channel].users):
  1681. irc.error("You don't have access to that information.")
  1682. return choice(list(irc.state.channels[channel].users))
  1683.  
  1684. def someone(self, irc, msg, args, channel):
  1685. """[<channel>]
  1686.  
  1687. Returns a random nick from <channel>. <channel> is only necessary if the
  1688. message isn't sent in the channel itself.
  1689. """
  1690. irc.reply(self._random_nick(irc, msg, args, channel).strip(), prefixNick=False)
  1691.  
  1692. someone = wrap(someone, ['inChannel'])
  1693.  
  1694. def _personalize(self,matchobj):
  1695. """ """
  1696. replacements = { 'your': 'my', 'my': 'your', 'you': 'me', 'me': 'you' }
  1697. return replacements[matchobj.group(0)]
  1698.  
  1699. def who(self, irc, msg, args, channel, question):
  1700. """[<channel>] <question>
  1701.  
  1702. Answers <question> with a random nick from <channel>. <channel> is only
  1703. necessary if the message isn't sent in the channel itself.
  1704. """
  1705. subject = self._random_nick(irc, msg, args, channel)
  1706. predicate = re.sub("[^A-Za-z0-9]+$",'',question)
  1707. predicate = re.sub("\\b(your?|me|my)\\b",self._personalize,predicate)
  1708. predicate = re.sub("\\$whose",subject + "'s",predicate)
  1709. predicate = re.sub("\\$who",subject,predicate)
  1710. irc.reply("%s %s." % (subject, predicate), prefixNick=False)
  1711.  
  1712. who = wrap(who, ['inChannel', 'text'])
  1713.  
  1714. def whose(self, irc, msg, args, channel, question):
  1715. """ """
  1716. subject = self._random_nick(irc, msg, args, channel)
  1717. predicate = re.sub("[^A-Za-z0-9]+$",'',question)
  1718. predicate = re.sub("\\b(your?|me|my)\\b",self._personalize,predicate)
  1719. predicate = re.sub("\\$whose",subject + "'s",predicate)
  1720. predicate = re.sub("\\$who",subject,predicate)
  1721. irc.reply("%s's %s" % (subject, predicate), prefixNick=False)
  1722.  
  1723. whose = wrap(whose, ['inChannel', 'text'])
  1724.  
  1725. def compliment(self, irc, msg, args, who):
  1726. """[<who>]
  1727.  
  1728. Delivers surreal compliments from the The Surrealist Compliment Generator
  1729. (http://madsci.org/cgi-bin/cgiwrap/~lynn/jardin/SCG)
  1730. """
  1731. url = 'http://madsci.org/cgi-bin/cgiwrap/~lynn/jardin/SCG'
  1732. ua = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11'
  1733. opener = build_opener()
  1734. opener.addheaders = [('User-Agent', ua)]
  1735. html = opener.open(url)
  1736. html_str = html.read()
  1737. soup = BeautifulSoup(html_str)
  1738. text = soup.find('h2').string.replace("\n"," ").strip()
  1739. if who is None:
  1740. irc.reply(text, prefixNick=True)
  1741. else:
  1742. irc.reply(who + ": " + text, prefixNick=False)
  1743.  
  1744. compliment = wrap(compliment, [optional('text')])
  1745.  
  1746. def isitwhiskytime(self, irc, msg, args):
  1747. """ """
  1748. affirmatives = ['Yes!','Always!','You need to ask?','When is it not?','Yes! (Duh!)']
  1749. irc.reply(affirmatives[randint(0, len(affirmatives)-1)], prefixNick=True)
  1750.  
  1751. isitwhiskeytime = isitwhiskytime
  1752.  
  1753. def stanford(self, irc, msg, args, channel):
  1754. """[<channel>]
  1755. Assign users to groups for a fun recreation of the Stanford Prison Experiment!"""
  1756. nicks = list(irc.state.channels[channel].users)
  1757. prisoners = []
  1758. guards = []
  1759. for nick in nicks:
  1760. if (randint(0,1) == 0) or nick == 'mbklein':
  1761. prisoners.append(nick)
  1762. else:
  1763. guards.append(nick)
  1764. irc.reply('PRISONERS: ' + ', '.join(prisoners), prefixNick=False)
  1765. irc.reply('GUARDS: ' + ', '.join(guards), prefixNick=False)
  1766. irc.reply('SHORTIMERS: mbklein', prefixNick=False)
  1767.  
  1768. stanford = wrap(stanford, ['inChannel'])
  1769.  
  1770. def kayfabe(self, irc, msg, args, channel):
  1771. """[<channel>]
  1772. Assign pro wrestling alignments"""
  1773. nicks = list(irc.state.channels[channel].users)
  1774. groups = [[],[]]
  1775. for nick in nicks:
  1776. groups[randint(0,1)].append(nick)
  1777. irc.reply('FACES: ' + ', '.join(groups[0]), prefixNick=False)
  1778. irc.reply('HEELS: ' + ', '.join(groups[1]), prefixNick=False)
  1779.  
  1780. kayfabe = wrap(kayfabe, ['inChannel'])
  1781.  
  1782. def shake(self, irc, msg, args, who):
  1783. """[<who>]
  1784.  
  1785. Deliver a Shakespearean insult (from http://www.pangloss.com/seidel/Shaker/index.html)"""
  1786. text = None
  1787. while text == None:
  1788. try:
  1789. soup = self._url2soup('http://www.pangloss.com/seidel/Shaker/index.html?')
  1790. except HTTPError, e:
  1791. irc.reply('http error %s for %s' % (e.code, url), prefixNick=True); return
  1792. except StopParsing, e:
  1793. irc.reply('parsing error %s for %s' % (e.code, url), prefixNick=True); return
  1794. text = soup.find('font').string.replace("\n"," ").strip()
  1795. attribution = soup.find('b')
  1796. if attribution != None:
  1797. text += " [%s]" % attribution.string
  1798.  
  1799. if who is None:
  1800. irc.reply(text, prefixNick=True)
  1801. else:
  1802. irc.reply(who + ": " + text, prefixNick=False)
  1803. shake = wrap(shake, [optional('text')])
  1804.  
  1805. def professor(self, irc, msg, args, first, last):
  1806. """<first_name> <last_name>
  1807.  
  1808. Generate your Professor Poopypants name"""
  1809. f1 = {'A': 'poopsie', 'B': 'lumpy', 'C': 'buttercup', 'D': 'gidget', 'E': 'crusty', 'F': 'greasy', 'G': 'fluffy', 'H': 'cheeseball', 'I': 'chim-chim', 'J': 'stinky', 'K': 'flunky', 'L': 'boobie', 'M': 'pinky', 'N': 'zippy', 'O': 'goober', 'P': 'doofus', 'Q': 'slimy', 'R': 'loopy', 'S': 'snotty', 'T': 'tulefel', 'U': 'dorkey', 'V': 'squeezit', 'W': 'oprah', 'X': 'skipper', 'Y': 'dinky', 'Z': 'zsa-zsa'}
  1810. l1 = {'A': 'apple', 'B': 'toilet', 'C': 'giggle', 'D': 'burger', 'E': 'girdle', 'F': 'barf', 'G': 'lizard', 'H': 'waffle', 'I': 'cootie', 'J': 'monkey', 'K': 'potty', 'L': 'liver', 'M': 'banana', 'N': 'rhino', 'O': 'bubble', 'P': 'hamster', 'Q': 'toad', 'R': 'gizzard', 'S': 'pizza', 'T': 'gerbil', 'U': 'chicken', 'V': 'pickle', 'W': 'chuckle', 'X': 'tofu', 'Y': 'gorilla', 'Z': 'stinker'}
  1811. l2 = {'A': 'head', 'B': 'mouth', 'C': 'face', 'D': 'nose', 'E': 'tush', 'F': 'breath', 'G': 'pants', 'H': 'shorts', 'I': 'lips', 'J': 'honker', 'K': 'butt', 'L': 'brain', 'M': 'tushie', 'N': 'chunks', 'O': 'hiney', 'P': 'biscuits', 'Q': 'toes', 'R': 'buns', 'S': 'fanny', 'T': 'sniffer', 'U': 'sprinkles', 'V': 'kisser', 'W': 'squirt', 'X': 'humperdinck', 'Y': 'brains', 'Z': 'juice'}
  1812. fname = f1[first[2].upper()]
  1813. lname = l1[last[1].upper()] + l2[last[3].upper()]
  1814. name = ('%s %s' % (fname, lname)).title()
  1815. irc.reply(name.encode('utf-8'), prefixNick=True)
  1816. professor = wrap(professor, ['somethingWithoutSpaces','somethingWithoutSpaces'])
  1817.  
  1818. def chucknorris(self, irc, msg, args):
  1819. """Grab a random Chuck Norris fact from http://www.chucknorrisfacts.com/"""
  1820. soup = self._url2soup('http://www.chucknorrisfacts.com/all-chuck-norris-facts')
  1821. # Ugliest scrape ever. Just go with it.
  1822. max_page = int(dict(soup.findAll('li',{'class' : re.compile(r'\bpager-last\b')})[0].findChildren()[0].attrs)['href'].split('=')[1])
  1823. page = randint(0,max_page)
  1824. soup = self._url2soup('http://www.chucknorrisfacts.com/all-chuck-norris-facts?page=%d' % (page))
  1825. facts = soup.findAll('div',{'class' : 'views-field-title'})
  1826. fact = (''.join(facts[randint(0,len(facts)-1)].findAll('a',text=True))).strip()
  1827. irc.reply(fact.encode('utf-8'), prefixNick=True)
  1828.  
  1829. def arch(self, irc, msg, args, thing):
  1830. """THE ARCHITECT SAYS THINGS"""
  1831. if thing is None:
  1832. irc.reply('pulls string...', action=True, prefixNick=False)
  1833. irc.reply('THE ARCHITECT SAYS...BLAH BLAH BLAH BLAH BLAH.', prefixNick=False)
  1834. else:
  1835. irc.reply('THE ARCHITECT ' + thing.upper().encode('utf-8'), prefixNick=False)
  1836. arch = wrap(arch, [optional('text')])
  1837.  
  1838. def beck(self, irc, msg, args):
  1839. """Generate a Glenn Beck conspiracy theory. Stolen from http://politicalhumor.about.com/library/bl-glenn-beck-conspiracy.htm"""
  1840. irc.reply(BeckGenerator().generate().encode('utf-8'), prefixNick=True)
  1841.  
  1842. def pac(self, irc, msg, args):
  1843. """Generate a name for your PAC and turn on the $$ pipe! (via http://is.gd/i6vKS)"""
  1844. opener = build_opener()
  1845. opener.addheaders = [('Accept', 'application/json')]
  1846. data = simplejson.load(opener.open('http://pacgenerator.sunlightfoundation.com/generate'))
  1847. pacname = data['superpac']['name']
  1848. irc.reply(pacname.encode('utf-8', 'ignore'))
  1849.  
  1850. def redact(self, irc, msg, args, opts, text):
  1851. """--chance [num]
  1852.  
  1853. Randomly redact a piece of text, blacking out 1 in <chance> non-stopwords. (Default: 1 in 4)"""
  1854. chance = 4
  1855. for (opt, arg) in opts:
  1856. if opt == 'chance':
  1857. chance = arg
  1858.  
  1859. stop_words = ['a','able','about','across','after','all','almost','also','am','among','an',
  1860. 'and','any','are','as','at','be','because','been','but','by','can','cannot',
  1861. 'could','dear','did','do','does','either','else','ever','every','for','from',
  1862. 'get','got','had','has','have','he','her','hers','him','his','how','however',
  1863. 'i','if','in','into','is','it','its','just','least','let',
  1864. 'may','me','might','most','must','my','neither','no','nor','not','of','off',
  1865. 'often','on','only','or','other','our','said','say','says',
  1866. 'she','should','since','so','some','than','that','the','their','them','then',
  1867. 'there','these','they','this','tis','to','too','twas','us','wants','was','we',
  1868. 'were','what','when','where','which','while','who','whom','why','will','with',
  1869. 'would','yet','you','your']
  1870. words = []
  1871. for word in text.split():
  1872. if (randint(1,chance) == 1) and (word.lower() not in stop_words):
  1873. words.append(re.sub(ur'\w','█',word,re.UNICODE))
  1874. else:
  1875. words.append(word)
  1876. irc.reply(' '.join(words))
  1877. redact = wrap(redact, [getopts({'chance':'int'}), 'text'])
  1878.  
  1879. def _google_search(self,q):
  1880. json = urlopen("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=%s" % quote(q)).read()
  1881. response = simplejson.loads(json)
  1882. if len(response['responseData']['results']) == 0:
  1883. response['responseData']['cursor']['estimatedResultCount'] = 0
  1884. return response
  1885.  
  1886. def intensify(self, irc, msg, args, opts, adjective):
  1887. """[--graph|--raw] [(--prefix|--suffix) <phrase>...] <adjective>
  1888.  
  1889. Calculate the frequency with which <adjective> is intensified as described in http://xkcd.com/798/
  1890. (--raw: show raw counts; --graph: display as a lame graph; --prefix/--suffix: use custom intensifiers)"""
  1891.  
  1892. graph = False
  1893. raw = False
  1894. intensifiers = []
  1895.  
  1896. for (opt,arg) in opts:
  1897. if opt == 'prefix':
  1898. intensifiers.append(arg + ' %s')
  1899. if opt == 'suffix':
  1900. intensifiers.append('%s ' + arg)
  1901. if opt == 'raw':
  1902. raw = True
  1903. if opt == 'graph':
  1904. graph = True
  1905.  
  1906. if len(intensifiers) == 0:
  1907. intensifiers = ['fucking %s','%s as shit']
  1908.  
  1909. taken = []
  1910. def _find_key(string, taken):
  1911. for char in string.replace('%s','').strip():
  1912. if not char.upper() in taken:
  1913. taken.append(char.upper())
  1914. return(char.upper())
  1915.  
  1916. counter = lambda q: float(self._google_search('"%s"' % (q))['responseData']['cursor']['estimatedResultCount'])
  1917. adjective_alone = counter(adjective)
  1918. counts = { 'RAW' : { 'count' : adjective_alone } }
  1919. for phrase in intensifiers:
  1920. c = counter((phrase % adjective).replace(' ','+'))
  1921. counts[phrase] = { 'count' : c, 'phrase' : phrase % adjective, 'marker' : _find_key(phrase, taken) }
  1922. if c > 0:
  1923. counts[phrase]['scale'] = math.log(c/adjective_alone)
  1924. counts[phrase]['string'] = '%.2f' % counts[phrase]['scale']
  1925. else:
  1926. counts[phrase]['scale'] = 0
  1927. counts[phrase]['string'] = 'NaN'
  1928.  
  1929. if graph:
  1930. graph_str = bytearray('-' * 40)
  1931. for phrase in intensifiers:
  1932. pos = int(counts[phrase]['scale'] * 2)
  1933. if graph_str[pos] != 45:
  1934. graph_str[pos] = '*'
  1935. else:
  1936. graph_str[pos] = counts[phrase]['marker']
  1937. irc.reply('[-20]%s[0]' % str(graph_str))
  1938. elif raw:
  1939. response = ["'%s': %d" % (adjective, counts['RAW']['count'])]
  1940. for phrase in intensifiers:
  1941. response.append("'%(phrase)s': %(count)d (%(string)s)" % counts[phrase])
  1942. irc.reply('; '.join(response))
  1943. else:
  1944. response = []
  1945. for phrase in intensifiers:
  1946. response.append("'%(phrase)s': %(string)s" % counts[phrase])
  1947. irc.reply('; '.join(response))
  1948. intensify = wrap(intensify, [getopts({'graph':'','raw':'','prefix':'something','suffix':'something'}), 'text'])
  1949.  
  1950. def complain(self, irc, msg, args, topic):
  1951. """ """
  1952. pgraphs = 1
  1953. gender = 'c'
  1954.  
  1955. opener = build_opener()
  1956. html = opener.open("http://www.pakin.org/complaint?" + urlencode({ 'firstname' : topic, 'pgraphs' : pgraphs, 'gender' : gender }))
  1957. html_str = html.read()
  1958. soup = BeautifulSoup(html_str, convertEntities=BeautifulSoup.HTML_ENTITIES)
  1959. irc.reply(' '.join(map(lambda x: ' '.join(x.findAll(text=True)), soup.findAll('p', limit=pgraphs))).encode('utf-8'), prefixNick=False)
  1960. complain = wrap(complain, ['text'])
  1961.  
  1962. def haverfood(self, irc, msg, args):
  1963. """get the name of a food according to Parks and Recreation's Tom Haverford from http://tomhaverfoods.com/"""
  1964. text = None
  1965. while text == None:
  1966. try:
  1967. soup = self._url2soup('http://tomhaverfoods.com')
  1968. except HTTPError, e:
  1969. irc.reply('http error %s for %s' % (e.code, url), prefixNick=True); return
  1970. except StopParsing, e:
  1971. irc.reply('parsing error %s for %s' % (e.code, url), prefixNick=True); return
  1972. food = soup.find('a')
  1973. text = food.find('p').string + food.find('h2').string
  1974. text = text.replace("\n"," ").strip()
  1975.  
  1976. irc.reply(text, prefixNick=True)
  1977.  
  1978. def quakes(self, irc, msg, args, opts, loc):
  1979. """[--min <magnitude>] [<location>]
  1980. List recent earthquakes (optionally near <location>, with magnitude >= <magnitude> [default: 3.5]) from the USGS Atom feed"""
  1981. url = "http://quakes.heroku.com/catalogs/7day-M2.5.json"
  1982. minq = 3.5
  1983. for (opt, arg) in opts:
  1984. if opt == 'min':
  1985. minq = arg
  1986.  
  1987. if (loc != None):
  1988. pref = 'Closest quakes (>=%.2f) to %s in the past 7 days: ' % (minq,loc)
  1989. url = url + ("?sort=distance&from=%s" % (loc))
  1990. else:
  1991. pref = 'Quakes (>=%.2f) in the past 7 days: ' % (minq)
  1992.  
  1993. json = urlopen(url).read()
  1994. feed = simplejson.loads(json)
  1995. responses = []
  1996. for q in feed:
  1997. if q['magnitude'] >= minq:
  1998. if loc != None:
  1999. s = "%.2f, %.1fmi away (%s, %s ago)" % (q['magnitude'], q['distance']['mi'], q['location'], q['age']['string'])
  2000. else:
  2001. s = "%.2f, %s (%s ago)" % (q['magnitude'], q['location'], q['age']['string'])
  2002. responses.append(s)
  2003.  
  2004. irc.reply(pref + '; '.join(responses))
  2005. quakes=wrap(quakes,[getopts({'min':'float'}), optional('text')])
  2006.  
  2007. def occupy(self, irc, msg, args, this, that):
  2008. """1% of <this> controls 99% of <that>"""
  2009. irc.reply(("1%% of the %s control 99%% of the %s" % (this, that)).encode('utf8'), prefixNick=False)
  2010. occupy=wrap(occupy,['something','something'])
  2011.  
  2012. def bitch(self, irc, msg, args, what):
  2013. """It's a plugin, bitches!"""
  2014. irc.reply(u'%s, bitches!' % re.sub(r'[.!?,;:]+$','',what))
  2015. bitch=wrap(bitch,['text'])
  2016.  
  2017. def cocktail(self, irc, msg, args):
  2018. """[<who>]
  2019. Mix a random cocktail from http://www.cocktaildb.com/"""
  2020.  
  2021. if len(args) > 0:
  2022. nick = ' '.join(args)
  2023. else:
  2024. nick = msg.nick
  2025. req = Request(url='http://www.cocktaildb.com/index?_action_randomRecipe=1')
  2026. resp = urlopen(req)
  2027. soup = BeautifulSoup(resp.read())
  2028. desc = soup.find('meta',{'name':'description'})['content']
  2029. (drink,rest) = re.compile('\s*:\s*').split(desc)
  2030. ingredients = re.compile('\s*,\s*').split(rest)
  2031. last = ingredients.pop()
  2032. if re.compile('[aeiouAEIOU]').match(drink) is None:
  2033. article = 'a'
  2034. else:
  2035. article = 'an'
  2036.  
  2037. text = u'mixes %s and %s to make %s %s, and sends it sliding down the bar to %s (%s)' % (u', '.join(ingredients), last, article, drink, nick, resp.url)
  2038. irc.reply(text.encode('utf-8'), action=True)
  2039.  
  2040. def compliment(self, irc, msg, args, who):
  2041. """[<who>]
  2042. Pulls an emergency compliment from http://emergencycompliment.com/"""
  2043.  
  2044. if who is None:
  2045. who = msg.nick
  2046. json = urlopen("http://emergencycompliment.com/js/compliments.js").read()
  2047. compliments = simplejson.loads(re.search('(\[.+\])',json.replace("\n",'')).group(0))
  2048. text = random.choice(compliments)['phrase']
  2049. response = "%s: %s" % (who, text)
  2050. irc.reply(response, prefixNick=False)
  2051.  
  2052. compliment = wrap(compliment,[optional('text')])
  2053.  
  2054. def sortinghat(self, irc, msg, args, who):
  2055. """ """
  2056. options = ['Gryffindor', 'Ravenclaw', 'Hufflepuff', 'Slytherin']
  2057. if who is None:
  2058. who = msg.nick
  2059. house = random.choice(options)
  2060. irc.reply('Hmm... %s... Let me see now... %s!' % (who, house.upper()), prefixNick=False)
  2061.  
  2062. sortinghat = wrap(sortinghat,[optional('text')])
  2063.  
  2064. def helperz(self, irc, msg, args, who):
  2065. """ """
  2066. irc.reply("Translation Party is hiring, you know.")
  2067.  
  2068. def rly(self, irc, msg, args, what):
  2069. """ """
  2070. reply = "%s%s?... Really?%s" % (
  2071. bool(random.getrandbits(1)) and 'Seriously, ' or '',
  2072. what,
  2073. bool(random.getrandbits(1)) and '... REALLY?' or ''
  2074. )
  2075. irc.reply(reply)
  2076.  
  2077. rly = wrap(rly, ['text'])
  2078.  
  2079. def isitrainingin(self, irc, msg, args, where):
  2080. """[<location>]
  2081. Ask http://isitraining.in/<location>"""
  2082.  
  2083. html = urlopen("http://isitraining.in/" + where).read()
  2084. soup = BeautifulSoup(html, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
  2085. answer = soup.find('h1').find(text=True)
  2086. exegesis = re.sub(r'\s+', ' ', ' '.join(soup.find('h2').findAll(text=True)))
  2087. response = u'%s. (%s)' % (answer, exegesis)
  2088. irc.reply(response.encode('utf8'), prefixNick=True)
  2089.  
  2090. isitrainingin = wrap(isitrainingin, ['text'])
  2091.  
  2092. def flip(self, irc, msg, args):
  2093. """Flip a table"""
  2094.  
  2095. if args:
  2096. string = upsidedown.transform(' '.join(args).decode('utf8'))
  2097. else:
  2098. string = u'┻━┻'
  2099.  
  2100. irc.reply(u'╯°□°╯︵{table}'.format(table=string).encode('utf8'), prefixNick=False)
  2101.  
  2102. def deflip(self, irc, msg, args):
  2103. """Puts a table back in place"""
  2104.  
  2105. if args:
  2106. string = upsidedown.transform(' '.join(args).decode('utf8'))
  2107. else:
  2108. string = u'┬──┬'
  2109.  
  2110. irc.reply(u'{table}◡ノ(° -°ノ)'.format(table=string).encode('utf8'), prefixNick=False)
  2111.  
  2112. def schmipsum(self, irc, msg, args, source, length):
  2113. """<shakespeare|bible|jane_austen|lewis_carroll|patents|nixon_tapes|college_essays|mission_statements|beatrix_potter|frankenstein> <length>
  2114. Generates filler text from real sources, courtesy of schmipsum.com"""
  2115.  
  2116. json = urlopen("http://www.schmipsum.com/ipsum/%s/%d" % (source,length)).read()
  2117. response = simplejson.loads(json)
  2118. ipsum = re.sub(r'\s+', ' ', response['ipsum'])
  2119. irc.reply(ipsum.encode('utf8'))
  2120.  
  2121. schmipsum = wrap(schmipsum, [('literal',('shakespeare','bible','jane_austen','lewis_carroll','patents','nixon_tapes','college_essays','mission_statements','beatrix_potter','frankenstein')), 'int'])
  2122. Class = Assorted
Advertisement
Add Comment
Please, Sign In to add comment