A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

979 satır
36 KiB

  1. # -*- coding: utf-8 -*-
  2. ######
  3. ###### NOTE:
  4. ###### This is an old commands file from the previous version of EarwigBot.
  5. ###### It is not used by the new EarwigBot and is simply here for reference
  6. ###### when developing new commands.
  7. ######
  8. ### EarwigBot
  9. ## Import basics.
  10. import sys, socket, string, time, codecs, os, traceback, thread, re, urllib, web, math, unicodedata
  11. ## Import our functions.
  12. import config
  13. ## Set up constants.
  14. HOST, PORT, NICK, IDENT, REALNAME, CHANS, REPORT_CHAN, WELCOME_CHAN, HOST2, CHAN2, OWNER, ADMINS, ADMINS_R, PASS = config.host, config.port, config.nick, config.ident, config.realname, config.chans, config.report_chan, config.welcome_chan, config.host2, config.chan2, config.owner, config.admins, config.admin_readable, config.password
  15. def get_commandList():
  16. return {'quiet': 'quiet',
  17. 'welcome': 'welcome',
  18. 'greet': 'welcome',
  19. 'linker': 'linker',
  20. 'auth': 'auth',
  21. 'access': 'access',
  22. 'join': 'join',
  23. 'part': 'part',
  24. 'restart': 'restart',
  25. 'quit': 'quit',
  26. 'die': 'quit',
  27. 'msg': 'msg',
  28. 'me': 'me',
  29. 'calc': 'calc',
  30. 'dice': 'dice',
  31. 'tock': 'tock',
  32. 'beats': 'beats',
  33. 'copyvio': 'copyvio',
  34. 'copy': 'copyvio',
  35. 'copyright': 'copyvio',
  36. 'dict': 'dictionary',
  37. 'dictionary': 'dictionary',
  38. 'ety': 'etymology',
  39. 'etymology': 'etymology',
  40. 'lang': 'langcode',
  41. 'langcode': 'langcode',
  42. 'num': 'number',
  43. 'number': 'number',
  44. 'count': 'number',
  45. 'c': 'number',
  46. 'nick': 'nick',
  47. 'op': 'op',
  48. 'deop': 'deop',
  49. 'voice': 'voice',
  50. 'devoice': 'devoice',
  51. 'pend': 'pending',
  52. 'pending': 'pending',
  53. 'sub': 'submissions',
  54. 'submissions': 'submissions',
  55. 'praise': 'praise',
  56. 'leonard': 'leonard',
  57. 'groovedog': 'groovedog',
  58. 'earwig': 'earwig',
  59. 'macmed': 'macmed',
  60. 'cubs197': 'cubs197',
  61. 'sparksboy': 'sparksboy',
  62. 'tim_song': 'tim_song',
  63. 'tim': 'tim_song',
  64. 'blurpeace': 'blurpeace',
  65. 'sausage': 'sausage',
  66. 'mindstormskid': 'mindstormskid',
  67. 'mcjohn': 'mcjohn',
  68. 'fetchcomms': 'fetchcomms',
  69. 'trout': 'trout',
  70. 'kill': 'kill',
  71. 'destroy': 'kill',
  72. 'murder': 'kill',
  73. 'fish': 'fish',
  74. 'report': 'report',
  75. 'commands': 'commands',
  76. 'help': 'help',
  77. 'doc': 'help',
  78. 'documentation': 'help',
  79. 'mysql': 'mysql',
  80. 'remind': 'reminder',
  81. 'reminder': 'reminder',
  82. 'notes': 'notes',
  83. 'note': 'notes',
  84. 'about': 'notes',
  85. 'data': 'notes',
  86. 'database': 'notes',
  87. 'hash': 'hash',
  88. 'lookup': 'lookup',
  89. 'ip': 'lookup'
  90. }
  91. def main(command, line, line2, nick, chan, host, auth, notice, say, reply, s):
  92. try:
  93. parse(command, line, line2, nick, chan, host, auth, notice, say, reply, s)
  94. except Exception:
  95. trace = traceback.format_exc() # Traceback.
  96. print trace # Print.
  97. lines = list(reversed(trace.splitlines())) # Convert lines to process traceback....
  98. report2 = [lines[0].strip()]
  99. for line in lines:
  100. line = line.strip()
  101. if line.startswith('File "/'):
  102. report2.append(line[0].lower() + line[1:])
  103. break
  104. else: report2.append('source unknown')
  105. say(report2[0] + ' (' + report2[1] + ')', chan)
  106. def parse(command, line, line2, nick, chan, host, auth, notice, say, reply, s):
  107. authy = auth(host)
  108. if command == "access":
  109. a = 'The bot\'s owner is "%s".' % OWNER
  110. b = 'The bot\'s admins are "%s".' % ', '.join(ADMINS_R)
  111. reply(a, chan, nick)
  112. reply(b, chan, nick)
  113. return
  114. if command == "join":
  115. if authy == "owner" or authy == "admin":
  116. try:
  117. channel = line2[4]
  118. except Exception:
  119. channel = chan
  120. s.send("JOIN %s\r\n" % channel)
  121. else:
  122. reply("You aren't authorized to use that command.", chan, nick)
  123. return
  124. if command == "part":
  125. if authy == "owner" or authy == "admin":
  126. try:
  127. channel = line2[4]
  128. except Exception:
  129. channel = chan
  130. s.send("PART %s\r\n" % channel)
  131. else:
  132. reply("You aren't authorized to use that command.", chan, nick)
  133. return
  134. if command == "restart":
  135. import thread
  136. if authy == "owner":
  137. s.send("QUIT\r\n")
  138. time.sleep(5)
  139. os.system("nice -15 python main.py")
  140. exit()
  141. else:
  142. reply("Only the owner, %s, can stop the bot. This incident will be reported." % OWNER, chan, nick)
  143. return
  144. if command == "quit" or command == "die":
  145. if authy != "owner":
  146. if command != "suicide":
  147. reply("Only the owner, %s, can stop the bot. This incident will be reported." % OWNER, chan, nick)
  148. else:
  149. say("\x01ACTION hands %s a gun... have fun :D\x01" % nick, nick)
  150. else:
  151. if command == "suicide":
  152. say("\x01ACTION stabs himself with a knife.\x01", chan)
  153. time.sleep(0.2)
  154. try:
  155. s.send("QUIT :%s\r\n" % ' '.join(line2[4:]))
  156. except Exception:
  157. s.send("QUIT\r\n")
  158. __import__('os')._exit(0)
  159. return
  160. if command == "msg":
  161. if authy == "owner" or authy == "admin":
  162. say(' '.join(line2[5:]), line2[4])
  163. else:
  164. reply("You aren't authorized to use that command.", chan, nick)
  165. return
  166. if command == "me":
  167. if authy == "owner" or authy == "admin":
  168. say("\x01ACTION %s\x01" % ' '.join(line2[5:]), line2[4])
  169. else:
  170. reply("You aren't authorized to use that command.", chan, nick)
  171. return
  172. if command == "calc":
  173. r_result = re.compile(r'(?i)<A NAME=results>(.*?)</A>')
  174. r_tag = re.compile(r'<\S+.*?>')
  175. subs = [
  176. (' in ', ' -> '),
  177. (' over ', ' / '),
  178. (u'£', 'GBP '),
  179. (u'€', 'EUR '),
  180. ('\$', 'USD '),
  181. (r'\bKB\b', 'kilobytes'),
  182. (r'\bMB\b', 'megabytes'),
  183. (r'\bGB\b', 'kilobytes'),
  184. ('kbps', '(kilobits / second)'),
  185. ('mbps', '(megabits / second)')
  186. ]
  187. try:
  188. q = ' '.join(line2[4:])
  189. except Exception:
  190. say("0?", chan)
  191. return
  192. query = q[:]
  193. for a, b in subs:
  194. query = re.sub(a, b, query)
  195. query = query.rstrip(' \t')
  196. precision = 5
  197. if query[-3:] in ('GBP', 'USD', 'EUR', 'NOK'):
  198. precision = 2
  199. query = web.urllib.quote(query.encode('utf-8'))
  200. uri = 'http://futureboy.us/fsp/frink.fsp?fromVal='
  201. bytes = web.get(uri + query)
  202. m = r_result.search(bytes)
  203. if m:
  204. result = m.group(1)
  205. result = r_tag.sub('', result) # strip span.warning tags
  206. result = result.replace('&gt;', '>')
  207. result = result.replace('(undefined symbol)', '(?) ')
  208. if '.' in result:
  209. try: result = str(round(float(result), precision))
  210. except ValueError: pass
  211. if not result.strip():
  212. result = '?'
  213. elif ' in ' in q:
  214. result += ' ' + q.split(' in ', 1)[1]
  215. say(q + ' = ' + result[:350], chan)
  216. else: reply("Sorry, can't calculate that.", chan, nick)
  217. return
  218. if command == "dice":
  219. import random
  220. try:
  221. set = range(int(line2[4]), int(line2[5]) + 1)
  222. except Exception:
  223. set = range(1, 7)
  224. num = random.choice(set)
  225. reply("You rolled a %s." % num, chan, nick)
  226. if len(set) < 30:
  227. say("Set consisted of %s." % set, nick)
  228. else:
  229. say("Set consisted of %s... and %s others." % (set[:30], len(set) - 30), nick)
  230. return
  231. if command == "tock":
  232. u = urllib.urlopen('http://tycho.usno.navy.mil/cgi-bin/timer.pl')
  233. info = u.info()
  234. u.close()
  235. say('"' + info['Date'] + '" - tycho.usno.navy.mil', chan)
  236. return
  237. if command == "beats":
  238. beats = ((time.time() + 3600) % 86400) / 86.4
  239. beats = int(math.floor(beats))
  240. say('@%03i' % beats, chan)
  241. return
  242. if command == "copyvio" or command == "copy" or command == "copyright":
  243. url = "http://en.wikipedia.org/wiki/User:EarwigBot/AfC copyvios"
  244. query = urllib.urlopen(url)
  245. data = query.read()
  246. url = "http://toolserver.org/~earwig/earwigbot/pywikipedia/error.txt"
  247. query = urllib.urlopen(url)
  248. data2 = query.read()
  249. if "critical" in data2:
  250. text = "AfC copyvio situation is CRITICAL: Major disaster."
  251. elif "exceed" in data2:
  252. text = "AfC copyvio situation is CRITICAL: Queries exceeded error."
  253. elif "spam" in data2:
  254. text = "AfC copyvio situation is CRITICAL: Spamfilter error."
  255. elif "<h3>" in data:
  256. text = "AfC copyvio situation is BAD: Unsolved copyvios at [[User:EarwigBot/AfC copyvios]]"
  257. else:
  258. text = "AfC copyvio situation is OK: OK."
  259. reply(text, chan, nick)
  260. return
  261. if command == "dict" or command == "dictionary":
  262. def trim(thing):
  263. if thing.endswith('&nbsp;'):
  264. thing = thing[:-6]
  265. return thing.strip(' :.')
  266. r_li = re.compile(r'(?ims)<li>.*?</li>')
  267. r_tag = re.compile(r'<[^>]+>')
  268. r_parens = re.compile(r'(?<=\()(?:[^()]+|\([^)]+\))*(?=\))')
  269. r_word = re.compile(r'^[A-Za-z0-9\' -]+$')
  270. uri = 'http://encarta.msn.com/dictionary_/%s.html'
  271. r_info = re.compile(r'(?:ResultBody"><br /><br />(.*?)&nbsp;)|(?:<b>(.*?)</b>)')
  272. try:
  273. word = line2[4]
  274. except Exception:
  275. reply("Please enter a word.", chan, nick)
  276. return
  277. word = urllib.quote(word.encode('utf-8'))
  278. bytes = web.get(uri % word)
  279. results = {}
  280. wordkind = None
  281. for kind, sense in r_info.findall(bytes):
  282. kind, sense = trim(kind), trim(sense)
  283. if kind: wordkind = kind
  284. elif sense:
  285. results.setdefault(wordkind, []).append(sense)
  286. result = word.encode('utf-8') + ' - '
  287. for key in sorted(results.keys()):
  288. if results[key]:
  289. result += (key or '') + ' 1. ' + results[key][0]
  290. if len(results[key]) > 1:
  291. result += ', 2. ' + results[key][1]
  292. result += '; '
  293. result = result.rstrip('; ')
  294. if result.endswith('-') and (len(result) < 30):
  295. reply('Sorry, no definition found.', chan, nick)
  296. else: say(result, chan)
  297. return
  298. if command == "ety" or command == "etymology":
  299. etyuri = 'http://etymonline.com/?term=%s'
  300. etysearch = 'http://etymonline.com/?search=%s'
  301. r_definition = re.compile(r'(?ims)<dd[^>]*>.*?</dd>')
  302. r_tag = re.compile(r'<(?!!)[^>]+>')
  303. r_whitespace = re.compile(r'[\t\r\n ]+')
  304. abbrs = [
  305. 'cf', 'lit', 'etc', 'Ger', 'Du', 'Skt', 'Rus', 'Eng', 'Amer.Eng', 'Sp',
  306. 'Fr', 'N', 'E', 'S', 'W', 'L', 'Gen', 'J.C', 'dial', 'Gk',
  307. '19c', '18c', '17c', '16c', 'St', 'Capt', 'obs', 'Jan', 'Feb', 'Mar',
  308. 'Apr', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'c', 'tr', 'e', 'g'
  309. ]
  310. t_sentence = r'^.*?(?<!%s)(?:\.(?= [A-Z0-9]|\Z)|\Z)'
  311. r_sentence = re.compile(t_sentence % ')(?<!'.join(abbrs))
  312. def unescape(s):
  313. s = s.replace('&gt;', '>')
  314. s = s.replace('&lt;', '<')
  315. s = s.replace('&amp;', '&')
  316. return s
  317. def text(html):
  318. html = r_tag.sub('', html)
  319. html = r_whitespace.sub(' ', html)
  320. return unescape(html).strip()
  321. try:
  322. word = line2[4]
  323. except Exception:
  324. reply("Please enter a word.", chan, nick)
  325. return
  326. def ety(word):
  327. if len(word) > 25:
  328. raise ValueError("Word too long: %s[...]" % word[:10])
  329. word = {'axe': 'ax/axe'}.get(word, word)
  330. bytes = web.get(etyuri % word)
  331. definitions = r_definition.findall(bytes)
  332. if not definitions:
  333. return None
  334. defn = text(definitions[0])
  335. m = r_sentence.match(defn)
  336. if not m:
  337. return None
  338. sentence = m.group(0)
  339. try:
  340. sentence = unicode(sentence, 'iso-8859-1')
  341. sentence = sentence.encode('utf-8')
  342. except: pass
  343. maxlength = 275
  344. if len(sentence) > maxlength:
  345. sentence = sentence[:maxlength]
  346. words = sentence[:-5].split(' ')
  347. words.pop()
  348. sentence = ' '.join(words) + ' [...]'
  349. sentence = '"' + sentence.replace('"', "'") + '"'
  350. return sentence + ' - ' + (etyuri % word)
  351. try:
  352. result = ety(word.encode('utf-8'))
  353. except IOError:
  354. msg = "Can't connect to etymonline.com (%s)" % (etyuri % word)
  355. reply(msg, chan, nick)
  356. return
  357. except AttributeError:
  358. result = None
  359. if result is not None:
  360. reply(result, chan, nick)
  361. else:
  362. uri = etysearch % word
  363. msg = 'Can\'t find the etymology for "%s". Try %s' % (word, uri)
  364. reply(msg, chan, nick)
  365. return
  366. if command == "num" or command == "number" or command == "count" or command == "c":
  367. try:
  368. params = string.lower(line2[4])
  369. except Exception:
  370. params = False
  371. if params == "old" or params == "afc" or params == "a":
  372. number = unicode(int(len(re.findall("title=", urllib.urlopen("http://en.wikipedia.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Pending_AfC_submissions&cmlimit=500").read()))) - 2)
  373. reply("There are currently %s pending AfC submissions." % number, chan, nick)
  374. elif params == "redirect" or params == "redir" or params == "redirs" or params == "redirects" or params == "r":
  375. redir_data = urllib.urlopen("http://en.wikipedia.org/w/index.php?title=Wikipedia:Articles_for_creation/Redirects").read()
  376. redirs = (string.count(redir_data, "<h2>") - 1) - (string.count(redir_data, '<table class="navbox collapsible collapsed" style="text-align: left; border: 0px; margin-top: 0.2em;">'))
  377. reply("There are currently %s open redirect requests." % redirs, chan, nick)
  378. elif params == "files" or params == "ffu" or params == "file" or params == "image" or params == "images" or params == "ifu" or params == "f":
  379. file_data = re.sub("<h2>Contents</h2>", "", urllib.urlopen("http://en.wikipedia.org/w/index.php?title=Wikipedia:Files_for_upload").read())
  380. files = (string.count(file_data, "<h2>") - 1) - (string.count(file_data, '<table class="navbox collapsible collapsed" style="text-align: left; border: 0px; margin-top: 0.2em;">'))
  381. reply("There are currently %s open file upload requests." % files, chan, nick)
  382. elif params == "aggregate" or params == "agg":
  383. subs = unicode(int(len(re.findall("title=", urllib.urlopen("http://en.wikipedia.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Pending_AfC_submissions&cmlimit=500").read()))) - 2)
  384. redir_data = urllib.urlopen("http://en.wikipedia.org/w/index.php?title=Wikipedia:Articles_for_creation/Redirects").read()
  385. file_data = re.sub("<h2>Contents</h2>", "", urllib.urlopen("http://en.wikipedia.org/w/index.php?title=Wikipedia:Files_for_upload").read())
  386. redirs = (string.count(redir_data, "<h2><span class=\"editsection\">")) - (string.count(redir_data, '<table class="navbox collapsible collapsed" style="text-align: left; border: 0px; margin-top: 0.2em;">'))
  387. files = (string.count(file_data, "<h2>") - 1) - (string.count(file_data, '<table class="navbox collapsible collapsed" style="text-align: left; border: 0px; margin-top: 0.2em;">'))
  388. aggregate = (int(subs) * 5) + (int(redirs) * 2) + (int(files) * 2)
  389. if aggregate == 0:
  390. stat = "clear"
  391. elif aggregate < 60:
  392. stat = "almost clear"
  393. elif aggregate < 125:
  394. stat = "small backlog"
  395. elif aggregate < 175:
  396. stat = "average backlog"
  397. elif aggregate < 250:
  398. stat = "backlogged"
  399. elif aggregate < 300:
  400. stat = "heavily backlogged"
  401. else:
  402. stat = "severely backlogged"
  403. reply("Aggregate is currently %s (%s)." % (aggregate, stat), chan, nick)
  404. else:
  405. subs = unicode(int(len(re.findall("title=", urllib.urlopen("http://en.wikipedia.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Pending_AfC_submissions&cmlimit=500").read()))) - 2)
  406. redir_data = urllib.urlopen("http://en.wikipedia.org/w/index.php?title=Wikipedia:Articles_for_creation/Redirects").read()
  407. file_data = re.sub("<h2>Contents</h2>", "", urllib.urlopen("http://en.wikipedia.org/w/index.php?title=Wikipedia:Files_for_upload").read())
  408. redirs = (string.count(redir_data, "<h2><span class=\"editsection\">")) - (string.count(redir_data, '<table class="navbox collapsible collapsed" style="text-align: left; border: 0px; margin-top: 0.2em;">'))
  409. files = (string.count(file_data, "<h2>") - 1) - (string.count(file_data, '<table class="navbox collapsible collapsed" style="text-align: left; border: 0px; margin-top: 0.2em;">'))
  410. reply("There are currently %s pending submissions, %s open redirect requests, and %s open file upload requests." % (subs, redirs, files), chan, nick)
  411. return
  412. if command == "nick":
  413. if authy == "owner":
  414. try:
  415. new_nick = line2[4]
  416. except Exception:
  417. reply("Please specify a nick to change to.", chan, nick)
  418. return
  419. s.send("NICK %s\r\n" % new_nick)
  420. else:
  421. reply("You aren't authorized to use that command.", chan, nick)
  422. return
  423. if command == "op" or command == "deop" or command == "voice" or command == "devoice":
  424. if authy == "owner" or authy == "admin":
  425. try:
  426. user = line2[4]
  427. except Exception:
  428. user = nick
  429. say("%s %s %s" % (command, chan, user), "ChanServ")
  430. else:
  431. reply("You aren't authorized to use that command.", chan, nick)
  432. return
  433. if command == "pend" or command == "pending":
  434. say("Pending submissions status page: <http://en.wikipedia.org/wiki/WP:AFC/S>.", chan)
  435. say("Pending submissions category: <http://en.wikipedia.org/wiki/Category:Pending_AfC_submissions>.", chan)
  436. return
  437. if command == "sub" or command == "submissions":
  438. try:
  439. number = int(line2[4])
  440. except Exception:
  441. reply("Please enter a number.", chan, nick)
  442. return
  443. do_url = False
  444. try:
  445. if "url" in line2[5:]: do_url = True
  446. except Exception:
  447. pass
  448. url = "http://en.wikipedia.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Pending_AfC_submissions&cmlimit=500&cmsort=timestamp"
  449. query = urllib.urlopen(url)
  450. data = query.read()
  451. pages = re.findall("title=&quot;(.*?)&quot;", data)
  452. try:
  453. pages.remove("Wikipedia:Articles for creation/Redirects")
  454. except Exception:
  455. pass
  456. try:
  457. pages.remove("Wikipedia:Files for upload")
  458. except Exception:
  459. pass
  460. pages.reverse()
  461. pages = pages[:number]
  462. if not do_url:
  463. s = string.join(pages, "]], [[")
  464. s = "[[%s]]" % s
  465. else:
  466. s = string.join(pages, ">, <http://en.wikipedia.org/wiki/")
  467. s = "<http://en.wikipedia.org/wiki/%s>" % s
  468. s = re.sub(" ", "_", s)
  469. s = re.sub(">,_<", ">, <", s)
  470. report = "\x02First %s pending AfC submissions:\x0F %s" % (number, s)
  471. say(report, chan)
  472. return
  473. if command == "praise" or command == "leonard" or command == "groovedog" or command == "earwig" or command == "macmed" or command == "cubs197" or command == "sparksboy" or command == "tim_song" or command == "tim" or command == "sausage" or command == "mindstormskid" or command == "mcjohn" or command == "fetchcomms" or command == "blurpeace":
  474. bad = False
  475. if command == "leonard":
  476. special = "AfC redirect reviewer"
  477. user = "Leonard^Bloom"
  478. elif command == "groovedog":
  479. special = "heh"
  480. user = "GrooveDog"
  481. elif command == "earwig":
  482. special = "Python programmer"
  483. user = "Earwig"
  484. elif command == "macmed":
  485. special = "CSD tagger"
  486. user = "MacMed"
  487. elif command == "mindstormskid":
  488. special = "Lego fanatic"
  489. user = "MindstormsKid"
  490. elif command == "cubs197":
  491. special = "IRC dude"
  492. user = "Cubs197"
  493. elif command == "sparksboy":
  494. special = "pet owner"
  495. user = "SparksBoy"
  496. elif command == "tim_song" or command == "tim":
  497. special = "JavaScript programmer"
  498. user = "Tim_Song"
  499. elif command == "sausage":
  500. special = "helper"
  501. user = "chzz"
  502. elif command == "mcjohn":
  503. special = "edit summary writer"
  504. user = "McJohn"
  505. elif command == "fetchcomms":
  506. special = "n00b"
  507. user = "Fetchcomms"
  508. elif command == "blurpeace":
  509. special = "Commons admin"
  510. user = "Blurpeace"
  511. else:
  512. say("Only a true fool would use that command, %s." % nick, chan)
  513. # say("The users who you can praise are: Leonard^Bloom, GrooveDog, Earwig, MacMed, Cubs197, SparksBoy, MindstormsKid, Chzz, McJohn, Tim_Song, Fetchcomms, and Blurpeace.", chan)
  514. return
  515. if not bad:
  516. say("\x02%s\x0F is the bestest %s evah!" % (user, special), chan)
  517. if bad:
  518. say("\x02%s\x0F is worstest %s evah!" % (user, special), chan)
  519. return
  520. if command == "trout":
  521. try:
  522. user = line2[4]
  523. user = ' '.join(line2[4:])
  524. except Exception:
  525. reply("Hahahahahahahaha...", chan, nick)
  526. return
  527. normal = unicodedata.normalize('NFKD', unicode(string.lower(user)))
  528. if "itself" in normal:
  529. reply("I'm not that stupid ;)", chan, nick)
  530. return
  531. elif "earwigbot" in normal:
  532. reply("I'm not that stupid ;)", chan, nick)
  533. elif "earwig" not in normal and "ear wig" not in normal:
  534. text = 'slaps %s around a bit with a large trout.' % user
  535. msg = '\x01ACTION %s\x01' % text
  536. say(msg, chan)
  537. else:
  538. reply("I refuse to hurt anything with \"Earwig\" in its name :P", chan, nick)
  539. return
  540. if command == "kill" or command == "destroy" or command == "murder":
  541. reply("Who do you think I am? The Mafia?", chan, nick)
  542. return
  543. if command == "fish":
  544. try:
  545. user = line2[4]
  546. fish = ' '.join(line2[5:])
  547. except Exception:
  548. reply("Hahahahahahahaha...", chan, nick)
  549. return
  550. normal = unicodedata.normalize('NFKD', unicode(string.lower(user)))
  551. if "itself" in normal:
  552. reply("I'm not that stupid ;)", chan, nick)
  553. return
  554. elif "earwigbot" in normal:
  555. reply("I'm not that stupid ;)", chan, nick)
  556. elif "earwig" not in normal and "ear wig" not in normal:
  557. text = 'slaps %s around a bit with a %s.' % (user, fish)
  558. msg = '\x01ACTION %s\x01' % text
  559. say(msg, chan)
  560. else:
  561. reply("I refuse to hurt anything with \"Earwig\" in its name :P", chan, nick)
  562. return
  563. if command == "report":
  564. def find_status(name="", talk=False):
  565. enname = re.sub(" ", "_", name)
  566. if talk == True:
  567. enname = "Wikipedia_talk:Articles_for_creation/%s" % enname
  568. if talk == False:
  569. enname = "Wikipedia:Articles_for_creation/%s" % enname
  570. url = "http://en.wikipedia.org/w/api.php?action=query&titles=%s&prop=revisions&rvprop=content" % enname
  571. query = urllib.urlopen(url)
  572. data = query.read()
  573. status = ""
  574. if "{{AFC submission|D" in data or "{{AFC submission|d" in data:
  575. reason = re.findall("(D|d)\|(.*?)\|", data)
  576. if reason[0][1] != "reason":
  577. status = "Declined, reason is '%s'" % reason[0][1]
  578. if reason[0][1] == "reason":
  579. status = "Declined, reason is a custom reason"
  580. if "{{AFC submission|H" in data or "{{AFC submission|h" in data:
  581. reason = re.findall("(H|h)\|(.*?)\|", data)
  582. if reason[0][1] != "reason":
  583. status = "Held, reason is '%s'" % reason[0][1]
  584. if reason[0][1] == "reason":
  585. status = "Held, reason is a custom reason"
  586. if "{{AFC submission||" in data:
  587. status = "Pending"
  588. if "{{AFC submission|R" in data or "{{AFC submission|r" in data:
  589. status = "Reviewing"
  590. if not status:
  591. exist = exists(name=enname)
  592. if exist == True:
  593. status = "Accepted"
  594. if exist == False:
  595. status = "Not found"
  596. return status
  597. def exists(name=""):
  598. url = "http://en.wikipedia.org/wiki/%s" % name
  599. query = urllib.urlopen(url)
  600. data = query.read()
  601. if "Wikipedia does not have a" in data:
  602. return False
  603. return True
  604. def get_submitter(name="", talk=False):
  605. enname = re.sub(" ", "_", name)
  606. if talk == True:
  607. enname = "Wikipedia_talk:Articles_for_creation/%s" % enname
  608. if talk == False:
  609. enname = "Wikipedia:Articles_for_creation/%s" % enname
  610. url = "http://en.wikipedia.org/w/api.php?action=query&titles=%s&prop=revisions&rvprop=user&rvdir=newer&rvlimit=1" % enname
  611. query = urllib.urlopen(url)
  612. data = query.read()
  613. extract = re.findall("user=&quot;(.*?)&quot;", data)
  614. if "anon=" in data:
  615. anon = True
  616. else:
  617. anon = False
  618. try:
  619. return extract[0], anon
  620. except BaseException:
  621. print extract
  622. return "", anon
  623. try:
  624. rawSub = line2[4]
  625. rawSub = ' '.join(line2[4:])
  626. except Exception:
  627. reply("You need to specify a submission name in order to use %s!" % command, chan, nick)
  628. return
  629. talk = False
  630. if "[[" in rawSub and "]]" in rawSub:
  631. name = re.sub("\[\[(.*)\]\]", "\\1", rawSub)
  632. name = re.sub(" ", "_", name)
  633. name = urllib.quote(name, ":/")
  634. name = "http://en.wikipedia.org/wiki/%s" % name
  635. if "talk:" in name:
  636. talk = True
  637. elif "http://" in rawSub:
  638. name = rawSub
  639. if "talk:" in name:
  640. talk = True
  641. elif "en.wikipedia.org" in rawSub:
  642. name = "http://%s" % rawSub
  643. if "talk:" in name:
  644. talk = True
  645. elif "Wikipedia:" in rawSub or "Wikipedia_talk:" in rawSub or "Wikipedia talk:" in rawSub:
  646. name = re.sub(" ", "_", rawSub)
  647. name = urllib.quote(name, ":/")
  648. name = "http://en.wikipedia.org/wiki/%s" % name
  649. if "talk:" in name:
  650. talk = True
  651. else:
  652. url = "http://en.wikipedia.org/wiki/"
  653. pagename = re.sub(" ", "_", rawSub)
  654. pagename = urllib.quote(pagename, ":/")
  655. pagename = "Wikipedia:Articles_for_creation/%s" % pagename
  656. page = urllib.urlopen("%s%s" % (url, pagename))
  657. text = page.read()
  658. name = "http://en.wikipedia.org/wiki/%s" % pagename
  659. if "Wikipedia does not have a" in text:
  660. pagename = re.sub(" ", "_", rawSub)
  661. pagename = urllib.quote(pagename, ":/")
  662. pagename = "Wikipedia_talk:Articles_for_creation/%s" % pagename
  663. page = urllib.urlopen("%s%s" % (url, pagename))
  664. name = "http://en.wikipedia.org/wiki/%s" % pagename
  665. talk = True
  666. unname = re.sub("http://en.wikipedia.org/wiki/Wikipedia:Articles_for_creation/", "", name)
  667. unname = re.sub("http://en.wikipedia.org/wiki/Wikipedia_talk:Articles_for_creation/", "", unname)
  668. unname = re.sub("_", " ", unname)
  669. if "talk" in unname:
  670. talk = True
  671. submitter, anon = get_submitter(name=unname, talk=talk)
  672. status = find_status(name=unname, talk=talk)
  673. if submitter != "":
  674. if anon == True:
  675. submitter_page = "Special:Contributions/%s" % submitter
  676. if anon == False:
  677. unsubmit = re.sub(" ", "_", submitter)
  678. unsubmit = urllib.quote(unsubmit, ":/")
  679. submitter_page = "User:%s" % unsubmit
  680. if status == "Accepted":
  681. submitterm = "Reviewer"
  682. else:
  683. submitterm = "Submitter"
  684. line1 = "\x02AfC submission report for %s:" % unname
  685. line2 = "\x02URL: \x0301\x0F%s" % name
  686. if submitter != "":
  687. line3 = "\x02%s: \x0F\x0302%s (\x0301\x0Fhttp://en.wikipedia.org/wiki/%s)." % (submitterm, submitter, submitter_page)
  688. line4 = "\x02Status: \x0F\x0302%s." % status
  689. say(line1, chan)
  690. time.sleep(0.1)
  691. say(line2, chan)
  692. time.sleep(0.1)
  693. if submitter != "":
  694. say(line3, chan)
  695. time.sleep(0.1)
  696. say(line4, chan)
  697. return
  698. if command == "commands":
  699. if chan.startswith("#"):
  700. reply("Please use that command in a private message.", chan, nick)
  701. return
  702. others2 = get_commandList().values()
  703. others = []
  704. for com in others2:
  705. if com == "copyvio" or com == "number" or com == "pending" or com == "report" or com == "submissions" or com == "access" or com == "help" or com == "join" or com == "linker" or com == "nick" or com == "op" or com == "part" or com == "quiet" or com == "quit" or com == "restart" or com == "voice" or com == "welcome" or com == "fish" or com == "praise" or com == "trout" or com == "notes":
  706. continue
  707. if com in others: continue
  708. others.append(com)
  709. others.sort()
  710. say("\x02AFC commands:\x0F copyvio, number, pending, report, submissions.", chan)
  711. time.sleep(0.1)
  712. say("\x02Bot operation and channel maintaince commands:\x0F access, help, join, linker, nick, op, part, quiet, quit, restart, voice, welcome.", chan)
  713. time.sleep(0.1)
  714. say("\x02Fun commands:\x0F fish, praise, trout, and numerous easter eggs", chan)
  715. time.sleep(0.1)
  716. say("\x02Other commands:\x0F %s" % ', '.join(others), chan)
  717. time.sleep(0.1)
  718. say("The bot maintains a mini-wiki. Type \"!notes help\" for more information.", chan)
  719. time.sleep(0.1)
  720. say("See http://enwp.org/User:The_Earwig/Bots/IRC for details. For help on a specific command, type '!help command'.", chan)
  721. return
  722. if command == "help" or command == "doc" or command == "documentation":
  723. try:
  724. com = line2[4]
  725. except Exception:
  726. reply("Hi, I'm a bot that does work for Articles for Creation. You can find information about me at http://enwp.org/User:The_Earwig/Bots/IRC. Say \"!commands\" to me in a private message for some of my abilities. Earwig is my owner and creator, and you can contact him at http://enwp.org/User_talk:The_Earwig.", chan, nick)
  727. return
  728. say("Sorry, command documentation has not been implemented yet.", chan)
  729. return
  730. if command == "mysql":
  731. if authy != "owner":
  732. reply("You aren't authorized to use this command.", chan, nick)
  733. return
  734. import MySQLdb
  735. try:
  736. strings = line2[4]
  737. strings = ' '.join(line2[4:])
  738. if "db:" in strings:
  739. database = re.findall("db\:(.*?)\s", strings)[0]
  740. else:
  741. database = "enwiki_p"
  742. if "time:" in strings:
  743. times = int(re.findall("time\:(.*?)\s", strings)[0])
  744. else:
  745. times = 60
  746. file = re.findall("file\:(.*?)\s", strings)[0]
  747. sqlquery = re.findall("query\:(.*?)\Z", strings)[0]
  748. except Exception:
  749. reply("You did not specify enough data for the bot to continue.", chan, nick)
  750. return
  751. database2 = database[:-2] + "-p"
  752. db = MySQLdb.connect(db=database, host="%s.rrdb.toolserver.org" % database2, read_default_file="/home/earwig/.my.cnf")
  753. db.query(sqlquery)
  754. r = db.use_result()
  755. data = r.fetch_row(0)
  756. try:
  757. f = codecs.open("/home/earwig/public_html/reports/%s/%s" % (database[:-2], file), 'r')
  758. reply("A file already exists with that name.", chan, nick)
  759. return
  760. except Exception:
  761. pass
  762. f = codecs.open("/home/earwig/public_html/reports/%s/%s" % (database[:-2], file), 'a', 'utf-8')
  763. for line in data:
  764. new_line = []
  765. for l in line:
  766. new_line.append(str(l))
  767. f.write(' '.join(new_line) + "\n")
  768. f.close()
  769. reply("Query completed successfully. See http://toolserver.org/~earwig/reports/%s/%s. I will delete the report in %s seconds." % (database[:-2], file, times), chan, nick)
  770. time.sleep(times)
  771. os.remove("/home/earwig/public_html/reports/%s/%s" % (database[:-2], file))
  772. return
  773. if command == "remind" or command == "reminder":
  774. try:
  775. times = int(line2[4])
  776. content = ' '.join(line2[5:])
  777. except Exception:
  778. reply("Please specify a time and a note in the following format: !remind <time> <note>.", chan, nick)
  779. return
  780. reply("Set reminder for \"%s\" in %s seconds." % (content, times), chan, nick)
  781. time.sleep(times)
  782. reply(content, chan, nick)
  783. return
  784. if command == "notes" or command == "note" or command == "about" or command == "data" or command == "database":
  785. try:
  786. action = line2[4]
  787. except BaseException:
  788. reply("What do you want me to do? Type \"!notes help\" for more information.", chan, nick)
  789. return
  790. import MySQLdb
  791. db = MySQLdb.connect(db="u_earwig_ircbot", host="sql", read_default_file="/home/earwig/.my.cnf")
  792. specify = ' '.join(line2[5:])
  793. if action == "help" or action == "manual":
  794. shortCommandList = "read, write, change, undo, delete, move, author, category, list, report, developer"
  795. if specify == "read":
  796. say("To read an entry, type \"!notes read <entry>\".", chan)
  797. elif specify == "write":
  798. say("To write a new entry, type \"!notes write <entry> <content>\". This will create a new entry only if one does not exist, see the below command...", chan)
  799. elif specify == "change":
  800. say("To change an entry, type \"!notes change <entry> <new content>\". The old entry will be stored in the database, so it can be undone later.", chan)
  801. elif specify == "undo":
  802. say("To undo a change, type \"!notes undo <entry>\".", chan)
  803. elif specify == "delete":
  804. say("To delete an entry, type \"!notes delete <entry>\". For security reasons, only bot admins can do this.", chan)
  805. elif specify == "move":
  806. say("To move an entry, type \"!notes move <old_title> <new_title>\".", chan)
  807. elif specify == "author":
  808. say("To return the author of an entry, type \"!notes author <entry>\".", chan)
  809. elif specify == "category" or specify == "cat":
  810. say("To change an entry's category, type \"!notes category <entry> <category>\".", chan)
  811. elif specify == "list":
  812. say("To list all categories in the database, type \"!notes list\". Type \"!notes list <category>\" to get all entries in a certain category.", chan)
  813. elif specify == "report":
  814. say("To give some statistics about the mini-wiki, including some debugging information, type \"!notes report\" in a PM.", chan)
  815. elif specify == "developer":
  816. say("To do developer work, such as writing to the database directly, type \"!notes developer <command>\". This can only be done by the bot owner.", chan)
  817. else:
  818. db.query("SELECT * FROM version;")
  819. r = db.use_result()
  820. data = r.fetch_row(0)
  821. version = data[0]
  822. reply("The Earwig Mini-Wiki: running v%s." % version, chan, nick)
  823. reply("The full list of commands, for reference, are: %s." % shortCommandList, chan, nick)
  824. reply("For an explaination of a certain command, type \"!notes help <command>\".", chan, nick)
  825. reply("You can also access the database from the Toolserver: http://toolserver.org/~earwig/cgi-bin/irc_database.py", chan, nick)
  826. time.sleep(0.4)
  827. return
  828. elif action == "read":
  829. specify = string.lower(specify)
  830. if " " in specify: specify = string.split(specify, " ")[0]
  831. if not specify or "\"" in specify:
  832. reply("Please include the name of the entry you would like to read after the command, e.g. !notes read earwig", chan, nick)
  833. return
  834. try:
  835. db.query("SELECT entry_content FROM entries WHERE entry_title = \"%s\";" % specify)
  836. r = db.use_result()
  837. data = r.fetch_row(0)
  838. entry = data[0][0]
  839. say("Entry \"\x02%s\x0F\": %s" % (specify, entry), chan)
  840. except Exception:
  841. reply("There is no entry titled \"\x02%s\x0F\"." % specify, chan, nick)
  842. return
  843. elif action == "delete" or action == "remove":
  844. specify = string.lower(specify)
  845. if " " in specify: specify = string.split(specify, " ")[0]
  846. if not specify or "\"" in specify:
  847. reply("Please include the name of the entry you would like to delete after the command, e.g. !notes delete earwig", chan, nick)
  848. return
  849. if authy == "owner" or authy == "admin":
  850. try:
  851. db.query("DELETE from entries where entry_title = \"%s\";" % specify)
  852. r = db.use_result()
  853. db.commit()
  854. reply("The entry on \"\x02%s\x0F\" has been removed." % specify, chan, nick)
  855. except Exception:
  856. phenny.reply("Unable to remove the entry on \"\x02%s\x0F\", because it doesn't exist." % specify, chan, nick)
  857. else:
  858. reply("Only bot admins can remove entries.", chan, nick)
  859. return
  860. elif action == "developer":
  861. if authy == "owner":
  862. db.query(specify)
  863. r = db.use_result()
  864. try:
  865. print r.fetch_row(0)
  866. except Exception:
  867. pass
  868. db.commit()
  869. reply("Done.", chan, nick)
  870. else:
  871. reply("Only the bot owner can modify the raw database.", chan, nick)
  872. return
  873. elif action == "write":
  874. try:
  875. write = line2[5]
  876. content = ' '.join(line2[6:])
  877. except Exception:
  878. reply("Please include some content in your entry.", chan, nick)
  879. return
  880. db.query("SELECT * from entries WHERE entry_title = \"%s\";" % write)
  881. r = db.use_result()
  882. data = r.fetch_row(0)
  883. if data:
  884. reply("An entry on %s already exists; please use \"!notes change %s %s\"." % (write, write, content), chan, nick)
  885. return
  886. content2 = content.replace('"', '\\' + '"')
  887. db.query("INSERT INTO entries (entry_title, entry_author, entry_category, entry_content, entry_content_old) VALUES (\"%s\", \"%s\", \"uncategorized\", \"%s\", NULL);" % (write, nick, content2))
  888. db.commit()
  889. reply("You have written an entry titled \"\x02%s\x0F\", with the following content: \"%s\"" % (write, content), chan, nick)
  890. return
  891. elif action == "change":
  892. reply("NotImplementedError", chan, nick)
  893. elif action == "undo":
  894. reply("NotImplementedError", chan, nick)
  895. elif action == "move":
  896. reply("NotImplementedError", chan, nick)
  897. elif action == "author":
  898. try:
  899. entry = line2[5]
  900. except Exception:
  901. reply("Please include the name of the entry you would like to get information for after the command, e.g. !notes author earwig", chan, nick)
  902. return
  903. db.query("SELECT entry_author from entries WHERE entry_title = \"%s\";" % entry)
  904. r = db.use_result()
  905. data = r.fetch_row(0)
  906. if data:
  907. say("The author of \"\x02%s\x0F\" is \x02%s\x0F." % (entry, data[0][0]), chan)
  908. return
  909. reply("There is no entry titled \"\x02%s\x0F\"." % entry, chan, nick)
  910. return
  911. elif action == "cat" or action == "category":
  912. reply("NotImplementedError", chan, nick)
  913. elif action == "list":
  914. reply("NotImplementedError", chan, nick)
  915. elif action == "report":
  916. reply("NotImplementedError", chan, nick)
  917. if command == "hash":
  918. import hashlib
  919. try:
  920. hashVia = line2[4]
  921. hashText = line2[5]
  922. hashText = ' '.join(line2[5:])
  923. except Exception:
  924. reply("Please provide a string and method to hash by.", chan, nick)
  925. return
  926. try:
  927. hashed = eval("hashlib.%s(\"%s\").hexdigest()" % (hashVia, hashText))
  928. reply(hashed, chan, nick)
  929. except Exception:
  930. try:
  931. hashing = hashlib.new(hashVia)
  932. hashing.update(hashText)
  933. hashed = hashing.hexdigest()
  934. reply(hashed, chan, nick)
  935. except Exception:
  936. reply("Error.", chan, nick)
  937. if command == "langcode" or command == "lang" or command == "language":
  938. try:
  939. lang = line2[4]
  940. except Exception:
  941. reply("Please specify an ISO code.", chan, nick)
  942. return
  943. data = urllib.urlopen("http://toolserver.org/~earwig/cgi-bin/swmt.py?action=iso").read()
  944. data = string.split(data, "\n")
  945. result = False
  946. for datum in data:
  947. if datum.startswith(lang):
  948. result = re.findall(".*? (.*)", datum)[0]
  949. break
  950. if result:
  951. reply(result, chan, nick)
  952. return
  953. reply("Not found.", chan, nick)
  954. return
  955. if command == "lookup" or command == "ip":
  956. try:
  957. hexIP = line2[4]
  958. except Exception:
  959. reply("Please specify a hex IP address.", chan, nick)
  960. return
  961. hexes = [hexIP[:2], hexIP[2:4], hexIP[4:6], hexIP[6:8]]
  962. hashes = []
  963. for hexHash in hexes:
  964. newHex = int(hexHash, 16)
  965. hashes.append(newHex)
  966. normalizedIP = "%s.%s.%s.%s" % (hashes[0], hashes[1], hashes[2], hashes[3])
  967. reply(normalizedIP, chan, nick)
  968. return