fork download
  1. #!/usr/bin/python
  2. import cookielib
  3. import re
  4. import mechanize
  5. import time
  6. from pprint import pprint as pp
  7. from bs4 import BeautifulSoup
  8. from bs4.element import NavigableString
  9. from imagesearch import imageSearch
  10. import cleverbot
  11. import os
  12. import random
  13. import textanalyzer
  14. from configobj import ConfigObj
  15. import socket
  16. import urllib
  17. import datetime
  18.  
  19.  
  20. loginurl = "http://w...content-available-to-author-only...d.com/login"
  21. accounturl = 'https://a...content-available-to-author-only...d.com/login'
  22. searchurl = 'http://p...content-available-to-author-only...d.com/search.php'
  23. #showposturl = 'http://p...content-available-to-author-only...d.com/showpost.php?p='
  24. showposturl = "http://a...content-available-to-author-only...s.net/pw/forumfetch.php?link=showpost.php%3Fp%3D"
  25. showthreadurl = 'http://p...content-available-to-author-only...d.com/showthread.php?t='
  26. showthreadurlforpost = 'http://p...content-available-to-author-only...d.com/showthread.php?p='
  27. showarchivethreadurl = "http://a...content-available-to-author-only...s.net/pw/forumfetch.php?link=archive%2Findex.php%2Ft-"
  28. chartApiHour = "http://c...content-available-to-author-only...s.com/chart?chf=bg,s,1E1713&chxr=1,0,23&chxs=0,EFEFEF,12,0,lt,EFEFEF&chxt=x&chbh=a&chs=440x130&cht=bvg&chco=76A4FB&chtt=Posts+by+Hour:&chts=EFEFEF,13,l&chds=a&chd=t:"
  29. chartApiWeekday = "http://c...content-available-to-author-only...s.com/chart?chf=bg,s,1E1713&chxr=1,0,7&chxs=0,EFEFEF,12,0,lt,EFEFEF&chxt=x&chbh=a&chs=440x130&cht=bvg&chco=76A4FB&chtt=Posts+by+Day:&chts=EFEFEF,13,l&chds=a&chxl=0:|Mon|Tue|Wed|Thu|Fri|Sat|Sun&chd=t:"
  30. chartApiThread = "http://c...content-available-to-author-only...s.com/chart?chf=bg,s,1E1713&chxs=0,EFEFEF,12,0,lt,EFEFEF&chbh=a&chs=440x110&cht=bvg&chco=76A4FB&chts=EFEFEF,13,l&chds=a&chd=t:"
  31.  
  32. baseurl = 'http://p...content-available-to-author-only...d.com/'
  33.  
  34. LOGIN = 'XXXXXXX'
  35. PASS = 'XXXXXXX'
  36. BOTNAME = "SweetieBot"
  37. MAX_SLEEP_INTERVAL = 480
  38.  
  39. br=mechanize.Browser()
  40.  
  41. config = ConfigObj('sweetiebot.ini')
  42. settings = {
  43. "cleverbotSessions": {}
  44. }
  45.  
  46. def getTextContentForTag(tag):
  47. if tag.name == "a" and tag.text.startswith("http"):
  48. return ""
  49. text = ""
  50. for content in tag.contents:
  51. if type(content) == NavigableString:
  52. text += content
  53. elif content.name != "div":
  54. text += getTextContentForTag(content)
  55. return text
  56.  
  57. class Post:
  58. def __init__(self, postId, html=""):
  59. self.postId = postId
  60.  
  61. retry_count = 0
  62. print "Reading post#", postId
  63.  
  64. if html=="":
  65. html = br.open(showposturl+ str(postId)).read()
  66. soup = BeautifulSoup(html, "html5lib")
  67. tag = soup.find("div",{"class": "bigusername"})
  68. if type(tag) == type(None):
  69. print "HTML IS", html
  70. raise Exception("Can't find username")
  71. self.name = tag.text
  72. self.time = soup.find("div",{"class": "time"}).text
  73.  
  74. self.admin = False
  75. if type(tag.contents[0].contents[0]) != NavigableString or self.name == "Asterelle - Sanctuary":
  76. self.admin = True
  77.  
  78. tag = soup.find("div",{"id": "post_message_"+str(postId)})
  79. #tag = soup.find("div",id=re.compile("post_message_"))
  80. self.text = getTextContentForTag(tag) + "\n"
  81. #print "POST", postId, "\n", self.text
  82.  
  83. self.postCount = re.search("<div class=\"smallfont\">Posts: (.*?)</div>", html).group(1)
  84. self.joinDate = re.search("<div class=\"smallfont\">Join Date: (.*?)</div>", html).group(1)
  85.  
  86. def getThreadId(self):
  87. html = br.open(showthreadurlforpost+ str(self.postId)).read()
  88. match = re.search("showthread\.php\?t=([0-9]*)", html)
  89. return match.group(1)
  90.  
  91.  
  92. class Thread:
  93. def __init__(self, threadId, loadAllPosts = False):
  94. self.threadId = threadId
  95. retry_count = 0
  96. print "scanning thread", threadId
  97. html = br.open(showthreadurl+ str(threadId)).read()
  98. soup = BeautifulSoup(html, "html5lib")
  99. tags = soup.findAll("div",{"class": "PW-postbit"})
  100. self.posts = [Post(re.search("showpost\.php\?p=([0-9]*)", str(tag)).group(1),
  101. str(tag)) for tag in tags]
  102.  
  103. def doLogin():
  104. response = br.open(loginurl)
  105. br.select_form("frm_login")
  106. for control in br.form.controls:
  107. if control.type == 'text':
  108. br.form[control.name] = LOGIN
  109. if control.type == 'password':
  110. br.form[control.name] = PASS
  111. br.form.action = accounturl
  112. response = br.submit()
  113.  
  114. def init():
  115. cj = cookielib.MozillaCookieJar('./cookies.txt')
  116.  
  117. def setup():
  118. br.set_cookiejar(cj)
  119. br.set_handle_equiv(True)
  120. #br.set_handle_gzip(True)
  121. br.set_handle_redirect(True)
  122. br.set_handle_referer(True)
  123. br.set_handle_robots(False)
  124. br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]
  125.  
  126. # Want debugging messages?
  127. #br.set_debug_http(True)
  128. #br.set_debug_redirects(True)
  129. #br.set_debug_responses(True)
  130.  
  131. if os.path.isfile('./cookies.txt'):
  132. cj.load('./cookies.txt', ignore_discard=False)
  133. setup()
  134. #doLogin()
  135. else:
  136. setup()
  137. #doLogin()
  138. cj.save(ignore_expires=False, ignore_discard=False)
  139.  
  140. def doSearch(name):
  141. br.open(searchurl)
  142. br.select_form("vbform")
  143. br.form['searchuser'] = name
  144. br.find_control("exactname").items[0].selected = True
  145. br.form['showposts'] = ['1']
  146. response = br.submit()
  147. return response.read()
  148.  
  149.  
  150. def findPostId(name):
  151. html = doSearch(name)
  152. matches = re.findall('#post([0-9]*)"', html)
  153. if len(matches) == 0:
  154. print "Can't Find: ", name
  155. else:
  156. print "Found", name, matches[0]
  157.  
  158. def getPostIdsFromSearchPage(html):
  159. matches = re.findall('#post([0-9]*)"', html)
  160. return matches
  161.  
  162. def getThreadIdsFromSearchPage(html):
  163. matches = re.findall('thread_title_([0-9]*)"', html)
  164. return matches
  165.  
  166. def extractText(postId):
  167. try:
  168. return Post(postId).text
  169. except:
  170. return ""
  171.  
  172. def getNextPageLinkFromSearchPage(page):
  173. for link in br.links(text_regex='>'):
  174. return link.url
  175. return ""
  176.  
  177. def getTextForName(name):
  178. page = doSearch(name)
  179. postIds = getPostIdsFromSearchPage(page)
  180. nextPageLink = getNextPageLinkFromSearchPage(page)
  181.  
  182. MAX_CHAR_COUNT = 35000
  183. MAX_POST_COUNT = 150
  184.  
  185.  
  186. result = {}
  187.  
  188. today = time.strftime("%m-%d-%Y", time.localtime(time.time()-3600*3))
  189. yesterday = time.strftime("%m-%d-%Y", time.localtime(time.time()-3600*(3+24)))
  190. def getPostTime(timeStr):
  191. timeStr = timeStr.replace("Today", today).replace("Yesterday", yesterday)
  192. posttime = time.strptime(timeStr, "%m-%d-%Y, %I:%M %p")
  193. return posttime
  194.  
  195. result['postByHour'] = [0]*24
  196. result['postByWeekday'] = [0]*7
  197. result['postCount'] = 0
  198. result['textSample'] = ""
  199.  
  200.  
  201. while len(result['textSample']) < MAX_CHAR_COUNT:
  202. for postId in postIds:
  203. result['post'] = Post(postId)
  204. result['textSample'] += result['post'].text
  205. result['postCount'] += 1
  206. posttime = getPostTime(result['post'].time)
  207. result['postByHour'][posttime.tm_hour] += 1
  208. result['postByWeekday'][posttime.tm_wday] += 1
  209. if len(result['textSample']) > MAX_CHAR_COUNT or result['postCount'] >= MAX_POST_COUNT:
  210. break
  211.  
  212. if len(result['textSample']) < MAX_CHAR_COUNT and nextPageLink != "" and result['postCount'] < MAX_POST_COUNT:
  213. print " Going to " + nextPageLink
  214. page = br.open(baseurl + nextPageLink).read()
  215. postIds = getPostIdsFromSearchPage(page)
  216. nextPageLink = getNextPageLinkFromSearchPage(page)
  217. else:
  218. break
  219. return result
  220.  
  221. def doReply(postId, text):
  222. url = baseurl + "newreply.php?do=newreply&p=" + str(postId)
  223. print "REPLYING WITH: ", text
  224. if config["quietmode"] == 'True':
  225. print "QUIET MODE DETECTED"
  226. return
  227. try:
  228. br.open(url)
  229. br.select_form(name="vbform")
  230. except:
  231. doLogin()
  232. br.open(url)
  233. br.select_form(name="vbform")
  234. br.form['message'] = text
  235. if __name__=="__main__":
  236. br.submit()
  237. print "Reply sent"
  238. time.sleep(46)
  239.  
  240. def quotePost(post, text):
  241. return "[QUOTE=" + post.name + ";" + str(post.postId) +"]" + text + "[/QUOTE]\n"
  242.  
  243. def getThreadStats(threadid):
  244. print "thread id is", threadid
  245. stats = {"threadid":threadid, "users": {}, "posts": 0}
  246. page = 1
  247. maxPage = 1
  248.  
  249. posttext = ""
  250. dates = []
  251. while page <= maxPage:
  252. print "Loading thread", threadid, "page", page
  253. html = br.open(showarchivethreadurl + threadid + "-p-" + str(page) +".html")
  254. soup = BeautifulSoup(html, "html5lib")
  255. users = soup.findAll("div", {"class" : "username"})
  256. if len(users) == 0:
  257. return stats
  258. for user in users:
  259. stats["users"][user.text] = stats["users"].get(user.text,0) + 1
  260. stats["posts"] += 1
  261. tags = soup.findAll("div", {"class":"posttext"})
  262. posttext += "\n".join([getTextContentForTag(tag) for tag in tags])
  263.  
  264. dates.extend([datetime.datetime.strptime(date.text, "%m-%d-%Y, %I:%M %p") for date in soup.findAll("div", {"class" : "date"})])
  265.  
  266. if page == 1:
  267. stats['title'] = soup.find('p', {"class":"largefont"}).find('a').text
  268.  
  269. tag = soup.find("div", {"id":"pagenumbers"})
  270. if type(tag) != type(None):
  271. maxPage = int(tag.findAll("a")[-1].text)
  272. page += 1
  273. stats['startdate'] = dates[0]
  274. stats['enddate'] = dates[-1]
  275. def toSeconds(delta):
  276. return float(delta.days*86400+delta.seconds)
  277.  
  278. timedifference = toSeconds(dates[-1] - dates[0])
  279. normalizedDates = [toSeconds(date - dates[0])/timedifference for date in dates]
  280. buckets = max(min(100, stats['posts']/10), min(10, stats['posts']))
  281. activity = [0]*buckets
  282. for value in normalizedDates:
  283. activity[int(value*(len(activity) - 1))] += 1
  284. stats['activity'] = activity
  285. textanalyzer.calcStats(stats, posttext)
  286. return stats
  287.  
  288. def getMessageForThreadStats(stats):
  289. startdate = stats['startdate']
  290. enddate = stats['enddate']
  291. age = (enddate - startdate).days
  292. message = "[b]"+BOTNAME + "[/b] has finished reading the thread: [b][url="+showthreadurl+stats['threadid']+"]"+stats['title']+"[/url][/b]!\n\n"
  293. message += "[b]General Stats:[/b]\n"
  294. message += "First Post: [color=white]%s[/color]\n" % (datetime.datetime.strftime(startdate, "%m-%d-%Y"))
  295. message += "Last Post: [color=white]%s[/color]\n" % (datetime.datetime.strftime(enddate, "%m-%d-%Y"))
  296. message += "Thread Lifetime: [color=white]%d days[/color]\n" % (age + 1)
  297. message += "Total Posts: [color=white]%d[/color]\n" % (stats['posts'])
  298. message += "Average Posts Per Day: [color=white]%.1f[/color]\n\n" % (float(stats['posts'])/float(age + 1))
  299.  
  300. message += "[b]Activity[/b]:\n"
  301. message += "[img]%s[/img]\n\n" % (chartApiThread + ",".join([str(a) for a in stats['activity']]))
  302.  
  303. users = stats['users'].items()
  304. users.sort(key=lambda x: -x[1])
  305. message += "[b]Top "+str(min(20,len(users)))+" Frequent Posters (count):[/b]\n"
  306. message += "\n".join(["%s ([color=white]%d[/color])" % item for item in users[:20]]) + "\n\n"
  307. results = stats['textanalysis']
  308. message += "[b]Writing Stats:[/b]\n"
  309. message += "Total Words: [color=white]%d[/color] \n" % (results['words'])
  310. message += "Total Sentences: [color=white]%d[/color] \n" % (results['sentences'])
  311. message += "Average Words per Post: [color=white]%5.1f[/color]\n" % (float(results['words'])/float(stats['posts']))
  312. message += "Average Words per Sentence: [color=white]%5.1f[/color]\n" % (float(results['words'])/float(results['sentences']))
  313. message += "Average Sentences per Post: [color=white]%5.1f[/color]\n" % (float(results['sentences'])/float(stats['posts']))
  314. message += "Average Syllables per Word: [color=white]%5.1f[/color]\n" % (float(results['syllables'])/float(results['words']))
  315. message += "Average Letters per Word: [color=white]%5.1f[/color]\n" % (float(results['characters'])/float(results['words']))
  316. message += "\n"
  317. message += "[b]Most Used Words (count): [color=gray](common words excluded)[/color][/b]\n"
  318. message += "\n".join(["%s ([color=white]%d[/color])" % (word[0].replace("#", "'"), word[1]) for word in results['favoritewords0']]) + "\n"
  319.  
  320. for i in range(3):
  321. message += "\n[b]Most Used "+str(i+3)+"-Word Phrases (count):[/b]\n"
  322. message += "\n".join(['"%s ([color=white]%d[/color])"' % (word[0].replace("#", "'"), word[1]) for word in results['favoritewords'+str(i+2)][:3]])
  323.  
  324. return message
  325.  
  326. def doAnalyzeThreadErrorReply(post, command, title, threadid):
  327. message = BOTNAME + " couldn't find the thread you're talking about :(\n"
  328. if len(threadid):
  329. message += "Is the thread's id really: '"+ threadid +"'?\n"
  330. if len(title):
  331. message += "Is the thread's title really: '"+ title +"'?\n"
  332. message += "You should try typing either: \n'" +BOTNAME+ " analyze this thread' or \n'" \
  333. +BOTNAME+ " analyze thread <Thread Title>' or\n'" \
  334. +BOTNAME+ " analyze thread <Thread ID>'"
  335. reply = quotePost(post, command) + decorate(message)
  336. doReply(post.postId, reply)
  337. return
  338.  
  339. def analyzeThreadCommand(post, command, args):
  340. print "ANALYZE THREAD from " , post.name, post.postId, command, ":", args
  341. starttime = time.time()
  342. threadid = ""
  343. title = ""
  344. if args.startswith("this thread"):
  345. threadid = post.getThreadId()
  346. else:
  347. match = re.search("thread +([0-9]+)", args)
  348. if match:
  349. threadid = match.group(1)
  350. if not match or int(threadid < 300):
  351. match = re.search("thread (.*)", args)
  352. if match:
  353. title = match.group(1).strip()
  354. print "Searching thread title", title
  355. threadids = searchForThreadByTitle('"'+title+'"')
  356. if len(threadids) > 0:
  357. threadid = threadids[0]
  358.  
  359. if len(threadid) == 0:
  360. doAnalyzeThreadErrorReply(post, command, title, threadid)
  361. return
  362. stats = getThreadStats(threadid)
  363. if len(stats['users']) == 0:
  364. try:
  365. refpost = Post(threadid)
  366. threadid = refpost.getThreadId()
  367. stats = getThreadStats(threadid)
  368. except:
  369. doAnalyzeThreadErrorReply(post, command, title, threadid)
  370. return
  371.  
  372.  
  373. message = getMessageForThreadStats(stats)
  374. message += "\n\n This took me [color=white]%5.1f[/color] s to finish" % (time.time() - starttime)
  375. reply = quotePost(post, command) + decorate(message)
  376. doReply(post.postId, reply)
  377.  
  378.  
  379. def analyzeCommand(post, command, args):
  380. print "GOT COMMAND TO ANALYZE from " , post.name, post.postId, command, ":", args
  381. starttime = time.time()
  382. possessive = "your"
  383. if args.startswith("me"):
  384. target = post.name
  385. elif args.startswith("this thread") or args.startswith("thread"):
  386. analyzeThreadCommand(post, command, args)
  387. return
  388. else:
  389. target = args.strip()
  390. target = re.sub("[,.?;!]+.*$", "", target)
  391. possessive = target + "'s"
  392. if len(target) == 0:
  393. message = "Umm, exactly who did you want " + BOTNAME + " to analyze?"
  394. reply = quotePost(post, command) + decorate(message)
  395. doReply(post.postId, reply)
  396. return
  397.  
  398. # samplePost, postCount, postByHour, text = getTextForName(target)
  399.  
  400. result = getTextForName(target)
  401.  
  402. if result['postCount'] == 0:
  403. message = BOTNAME + " couldn't find any posts for '"+target+"' :(\nCan you try again with their exact name please?"
  404. reply = quotePost(post, command) + decorate(message)
  405. doReply(post.postId, reply)
  406. return
  407.  
  408. postingHourUrl = chartApiHour + ",".join([str(x) for x in result['postByHour']])
  409. postingWeekdayUrl = chartApiWeekday + ",".join([str(x) for x in result['postByWeekday']])
  410.  
  411. print "EXTRACTED TEXT FOR", target
  412. if target not in config['users']:
  413. config['users'][target] = {}
  414. stats = {}
  415. textanalyzer.calcStats(stats, result['textSample'])
  416. results = stats['textanalysis']
  417.  
  418. months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
  419. startDate = result['post'].joinDate.split()
  420. totalDays = (datetime.date.today() - datetime.date(int(startDate[1]), months.index(startDate[0]) + 1, 1)).days + 1
  421.  
  422. config['users'][target]['samplePost'] = result['post'].postId
  423. config['users'][target]['analyzed posts'] = result['postCount']
  424. config['users'][target]['favorite word'] = results['favoritewords0'][0][0]
  425. config['users'][target]['analyzed words'] = results['words']
  426. config['users'][target]['postCount'] = result['post'].postCount
  427. config['users'][target]['joinDate'] = result['post'].joinDate
  428. config['users'][target]['totalDays'] = totalDays
  429.  
  430. message = "[b]"+BOTNAME + " has read "+possessive+" last "+str(result['postCount'])+" posts![/b]\nHere's some neat stuff "+BOTNAME+" has learned.\n\n"
  431. message += "[b]General Stats:[/b]\n"
  432. message += "Forum name: [color=white]%s[/color]\n" % (target)
  433. message += "Days since joining forums: [color=white]%d[/color]\n" % (totalDays)
  434. message += "Total Posts: [color=white]%s[/color]\n" % (result['post'].postCount)
  435. message += "Average Posts per Day: [color=white]%.1f[/color]\n\n" % (float(result['post'].postCount.replace(',',''))/float(totalDays))
  436. message += "[b]Activity:[/b]\n"
  437. message += "[img]%s[/img]\n" % (postingHourUrl)
  438. message += "[img]%s[/img]\n\n" % (postingWeekdayUrl)
  439.  
  440. message += "[b]Writing Stats (what "+BOTNAME+" read):[/b]\n"
  441. message += "Total Words: [color=white]%d[/color] \n" % (results['words'])
  442. message += "Total Sentences: [color=white]%d[/color] \n" % (results['sentences'])
  443. message += "Average Words per Post: [color=white]%5.1f[/color]\n" % (float(results['words'])/float(result['postCount']))
  444. message += "Average Words per Sentence: [color=white]%5.1f[/color]\n" % (float(results['words'])/float(results['sentences']))
  445. message += "Average Sentences per Post: [color=white]%5.1f[/color]\n" % (float(results['sentences'])/float(result['postCount']))
  446. message += "Average Syllables per Word: [color=white]%5.1f[/color]\n" % (float(results['syllables'])/float(results['words']))
  447. message += "Average Letters per Word: [color=white]%5.1f[/color]\n" % (float(results['characters'])/float(results['words']))
  448. message += "\n"
  449. message += "[b]Most Used Words (count): [color=gray](common words excluded)[/color][/b]\n"
  450. message += "\n".join(["%s ([color=white]%d[/color])" % (word[0].replace("#", "'"), word[1]) for word in results['favoritewords0']]) + "\n"
  451.  
  452. for i in range(3):
  453. message += "\n[b]Most Used "+str(i+3)+"-Word Phrases (count):[/b]\n"
  454. message += "\n".join(['"%s ([color=white]%d[/color])"' % (word[0].replace("#", "'"), word[1]) for word in results['favoritewords'+str(i+2)][:3]])
  455.  
  456. message += "\n\n This took me [color=white]%5.1f[/color] s to finish" % (time.time() - starttime)
  457.  
  458. reply = quotePost(post, command) + decorate(message)
  459. doReply(post.postId, reply)
  460.  
  461.  
  462. def getCleverBotSessionForName(name):
  463. if name in settings['cleverbotSessions']:
  464. return settings['cleverbotSessions'][name]
  465. else:
  466. keys = settings['cleverbotSessions'].keys()
  467. if len(keys) > 7:
  468. del settings['cleverbotSessions'][keys[0]]
  469. del settings['cleverbotSessions'][keys[1]]
  470. session = cleverbot.Session()
  471. print "MAKING NEW SESSION for", name
  472. settings['cleverbotSessions'][name] = session
  473. return session
  474.  
  475. def askBotCommand(post, command):
  476. session = getCleverBotSessionForName(post.name)
  477. print "matching", command
  478. text = re.search("^\s*" + BOTNAME + ",? (.*)$", command, re.IGNORECASE | re.MULTILINE)
  479. print "ASKING CLEV ", text.group(1)
  480. reply = session.Ask(text.group(1))
  481. reply = reply.replace("Cleverbot", BOTNAME).replace("cleverbot", BOTNAME)
  482.  
  483. if len(reply) < 5:
  484. reply += "[color=#1e1713](5char)[/color]"
  485.  
  486. reply = quotePost(post, command) + decorate(reply)
  487. doReply(post.postId, reply)
  488.  
  489. def checkServerStatusCommand(post, command, args):
  490. servers, message = getServerStatusMessage()
  491. reply = quotePost(post, command) + decorate(message)
  492. doReply(post.postId, reply)
  493.  
  494.  
  495. def getServerStatusMessage():
  496. #Simply change the host and port values
  497. servers = [("Sanctuary (PVE)", "pwigc2.perfectworld.com"),
  498. ("Lost City (PvP)", "pwigc3.perfectworld.com"),
  499. ("Heaven's Tear (PvE)", "pwigc4.perfectworld.com"),
  500. ("Archosaur (PvE)", "pwiwest4.perfectworld.com"),
  501. ("Harshlands (PvP)", "pwieast1.perfectworld.com"),
  502. ("Dreamweaver (PvE)", "pwieast2.perfectworld.com"),
  503. ("Raging Tide (PvE)", "pwieast3.perfectworld.com"),
  504. ("Lothranis (PvE)", "pwieu1.fr.perfectworld.eu"),
  505. ("Momaganon (PvE)", "pwieu2.de.perfectworld.eu")
  506. ]
  507. port = 29000
  508. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  509. number_servers_up = 0
  510. server_message = []
  511. for server in servers:
  512. try:
  513. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  514. s.settimeout(1.5)
  515. milli = int(round(time.time() * 1000))
  516. s.connect((server[1], 29000))
  517. milli = int(round(time.time() * 1000)) - milli
  518. s.shutdown(2)
  519. number_servers_up += 1
  520. server_message.append("[B]"+server[0] +"[/B] [color=gray](" + server[1] +")[/color]: [color=Lime]ONLINE[/color] (Ping: " + str(milli) +"ms)")
  521. except:
  522. server_message.append("[B]"+server[0] +"[/B] [color=gray](" + server[1] +")[/color]: [color=RED]OFFLINE[/color]")
  523.  
  524. message = "Here's the server status:\n\n[B][color=white]West Coast Servers:[/color][/B]\n" + "\n".join(server_message[:4]) + "\n\n"
  525. message += "[B][color=white]East Coast Servers:[/color][/B]\n" + "\n".join(server_message[4:7]) + "\n\n"
  526. message += "[B][color=white]European Servers:[/color][/B]\n" + "\n".join(server_message[7:]) + "\n"
  527.  
  528. return number_servers_up, message
  529.  
  530.  
  531. def checkForMorePatches(url):
  532. patchurl = "http://p...content-available-to-author-only...d.com/patches/manual/"
  533. match = re.search("ec_patch_([0-9]+)-([0-9]+)", url)
  534. if match:
  535. currentPatch = match.group(2)
  536. for i in range(1,15):
  537.  
  538. testurl = patchurl + "ec_patch_" + currentPatch + "-" + str(int(currentPatch)+i) + ".cup"
  539. print "checking##", testurl
  540. try:
  541. response = br.open(testurl)
  542. return response.info().getheader("Content-length"), testurl
  543. except:
  544. continue
  545. return ""
  546.  
  547. def findManualPatchCommand(post, command, args):
  548. message = getFindManualPatchMessage()
  549. reply = quotePost(post, command) + decorate(message)
  550. doReply(post.postId, reply)
  551.  
  552. def getFindManualPatchMessage():
  553. techsupportUrl = 'http://t...content-available-to-author-only...d.com/showthread.php?t=1717'
  554. html = br.open(techsupportUrl).read()
  555. soup = BeautifulSoup(html, "html5lib")
  556. currentPatchLink = soup.find("a", href=re.compile('perfectworld\.com/patches/manual'))
  557. print 'found patch url', currentPatchLink['href']
  558. currentpatch = currentPatchLink['href']
  559. patchText = currentPatchLink.text
  560. nextpatch = checkForMorePatches(currentpatch)
  561. message = ''
  562. if nextpatch == "":
  563. message = 'The latest manual patch is here: [URL="'+currentpatch+'"]'+patchText+'[/URL]'
  564. else:
  565. patchText = "Patch " + re.search("([0-9]+-[0-9]+)", nextpatch[1]).group(0)
  566. size = nextpatch[0]
  567. message = 'The latest manual patch appears to be: [URL="'+nextpatch[1]+'"]'+patchText+'[/URL] (' + size + " bytes)\n"
  568. if int(size) < 5000000:
  569. message += "It is weirdly small for some reason. \n\n"
  570. elif int(size) < 19000000:
  571. message += "That's average-sized. \n\n"
  572. elif int(size) < 35000000:
  573. message += "It is somewhat bigger than average. \n\n"
  574. else:
  575. message += "Wow this thing is big! \n\n"
  576. message += "This patch hasn't been [URL="+techsupportUrl+"]officially listed[/URL] yet."
  577.  
  578. return message
  579.  
  580. def decorate(chatText):
  581. return '[SIZE="2"][FONT="Tahoma"][COLOR="Silver"]' + chatText + '[/COLOR][/FONT][/SIZE]'
  582.  
  583. def showMeCommand(post, command, args):
  584. print "Doing image search for", args
  585. image = imageSearch(args)
  586. reply = quotePost(post, command)
  587. if image == '':
  588. reply += decorate(BOTNAME + " has no idea what you're talking about.")
  589. else:
  590. reply += decorate("Sure, no problem.\n\n" )
  591. reply += "[IMG]" + image + "[/IMG]"
  592.  
  593. doReply(post.postId, reply)
  594.  
  595. def requestDeniedReply(post, command, args):
  596. message = "Hey that's an admin-only request. "+BOTNAME+" doesn't have to listen to you!"
  597. reply = quotePost(post, command) + decorate(message)
  598. doReply(post.postId, reply)
  599.  
  600. def addToIgnoreCommand(post, command, args):
  601. if not post.admin:
  602. requestDeniedReply(post, command, args)
  603. return
  604. print "Gonna ignore", args
  605. ignorename = args
  606. users = args.split(",")
  607. message = ""
  608. for user in users:
  609. if user.strip() in config['ignorelist']:
  610. message += "You know I'd do anything for you but I already have "+user.strip()+" in my ignore list.\n"
  611. else:
  612. message += "Sure thing! I'm adding " + user.strip() + " to my ignore list.\n"
  613. config['ignorelist'].append(user.strip())
  614. config.write()
  615.  
  616.  
  617. reply = quotePost(post, command) + decorate(message)
  618. print reply
  619. doReply(post.postId, reply)
  620.  
  621. def removeFromIgnoreCommand(post, command, args):
  622. if not post.admin:
  623. requestDeniedReply(post, command, args)
  624. return
  625. print "Gonna unignore", args
  626. users = args.split(",")
  627. message = ""
  628. for user in users:
  629. if user.strip() in config['ignorelist']:
  630. message += "Anything you say! I'm removing " + user.strip() + " from my ignore list.\n"
  631. config['ignorelist'].remove(user.strip())
  632. config.write()
  633. else:
  634. message += "Hmm, are you sure I was ignoring " + user.strip() + "? I don't think I was.\n"
  635. reply = quotePost(post, command) + decorate(message)
  636. doReply(post.postId, reply)
  637.  
  638. def listIgnoreCommand(post, command, args):
  639. if not post.admin:
  640. requestDeniedReply(post, command, args)
  641. return
  642. print "Gonna list ignores"
  643. message = "I am ignoring the following trolls:\n\n"
  644. message += "\n".join(config['ignorelist'])
  645. reply = quotePost(post, command) + decorate(message)
  646. doReply(post.postId, reply)
  647.  
  648. def startTalkingCommand(post, command, args):
  649. if not post.admin:
  650. requestDeniedReply(post, command, args)
  651. return
  652. message = "Yay! Sweetiebot is so happy now! Being quiet is no fun at all :("
  653. config['quietmode'] = 'False'
  654. reply = quotePost(post, command) + decorate(message)
  655. doReply(post.postId, reply)
  656.  
  657. def beQuietCommand(post, command, args):
  658. if not post.admin:
  659. requestDeniedReply(post, command, args)
  660. return
  661. print "got bequiet", post.admin
  662. message = "Aww ok, you're the boss! If you find it in yourself to forgive me just tell me 'you can start talking again'"
  663. reply = quotePost(post, command) + decorate(message)
  664. doReply(post.postId, reply)
  665. config['quietmode'] = 'True'
  666.  
  667.  
  668. def scanPostForCommand(postId):
  669. print "scanning Post", postId
  670. try:
  671. post = Post(postId)
  672. if post.name.startswith(BOTNAME) or (post.name in config['ignorelist'] and not post.admin):
  673. return
  674. except:
  675. #post was deleted
  676. return
  677.  
  678. text = post.text
  679. print text
  680. commands = {
  681. 'analyze': analyzeCommand,
  682. 'analyse': analyzeCommand,
  683. 'show me': showMeCommand,
  684. 'find the manual patch': findManualPatchCommand,
  685. 'check server status': checkServerStatusCommand,
  686. 'what is the server status': checkServerStatusCommand,
  687. 'report server status': checkServerStatusCommand,
  688. 'ignore' : addToIgnoreCommand,
  689. 'unignore' : removeFromIgnoreCommand,
  690. 'stop ignoring' : removeFromIgnoreCommand,
  691. 'be quiet' : beQuietCommand,
  692. 'stop talking' : beQuietCommand,
  693. 'who are you ignoring' : listIgnoreCommand,
  694. 'you can start talking again' : startTalkingCommand
  695. }
  696. match = re.search("^\s*" + BOTNAME + ",? (?:can you |will you |please |could you |would you |won't you )*("+"|".join(commands.keys())+"|\S*)\W? ?(\w+.*)?", text, re.IGNORECASE | re.MULTILINE)
  697. print "match", match
  698. if match:
  699. if match.group(1).lower() in commands:
  700. commands[match.group(1).lower()](post, match.group(0), match.group(2))
  701. else:
  702. askBotCommand(post, match.group(0))
  703. return True
  704. else:
  705. return False
  706.  
  707. def doMaintenanceReply(post):
  708. message = "First non-human reply!\n\n"
  709. message += getFindManualPatchMessage()
  710. print post.text
  711. questionMatch = re.search("^(.*\?)", post.text, re.MULTILINE)
  712. if questionMatch:
  713. clever = getCleverBotSessionForName(post.name)
  714. question = questionMatch.group(1)
  715. message += quotePost(post, question)
  716. message += (clever.Ask(question)).replace("Cleverbot", BOTNAME).replace("cleverbot", BOTNAME)
  717.  
  718. reply = decorate(message)
  719. #print reply
  720. doReply(post.postId, reply)
  721.  
  722. def dayCount():
  723. return (datetime.date.today() - datetime.date.min).days
  724.  
  725.  
  726. def searchForThreadByTitle(title):
  727. br.open(searchurl)
  728. br.select_form("vbform")
  729. #The added random numbers force a cache miss!
  730. br.form['query'] = title + " " + str(random.randint(0,999)) + " " + str(random.randint(0,999))
  731. br.form['titleonly'] = ['1']
  732. response = br.submit()
  733. threadIds = getThreadIdsFromSearchPage(response.read())
  734. return threadIds
  735.  
  736. def searchForMaintenanceThread():
  737. threadIds = searchForThreadByTitle("Maintenance Discussion")
  738. print "Scanning maintenance thread minid is", config['maintenance']['threadId']
  739. for threadId in threadIds:
  740. if int(threadId) > int(config['maintenance']['threadId']):
  741. print "Found new maintenance thread mention!"
  742. maintThread = Thread(threadId)
  743. if maintThread.posts[0].admin:
  744. config['maintenance']['threadId'] = int(threadId)
  745. config['maintenance']['postId'] = int(maintThread.posts[0].postId)
  746. config['maintenance']['status'] = 'pending'
  747. config['maintenance']['date'] = dayCount()
  748. config.write()
  749. doMaintenanceReply(maintThread.posts[0])
  750.  
  751. def checkMaintenanceServerStatus():
  752. servers, message = getServerStatusMessage()
  753. print "Maint thread up! Checking server status", config['maintenance']['status'], servers
  754. reply = ""
  755. if config['maintenance']['status'] == "pending" and servers == 0:
  756. reply = "The servers are now down! Everyone start panicing! Ohh wait, this happens every maintenance.\n\n" + message
  757. config['maintenance']['status'] = "down"
  758. config.write()
  759. elif config['maintenance']['status'] == "down" and servers == 9:
  760. reply = BOTNAME + " can confirm the servers are back up! Yay!\n\n" + message
  761. config['maintenance']['status'] = "complete"
  762. config.write()
  763. if reply != "":
  764. print "Making reply!"
  765. maintThread = Thread(config['maintenance']['threadId'])
  766. doReply(maintThread.posts[0].postId, decorate(reply))
  767.  
  768.  
  769. def searchForCommand():
  770. br.open(searchurl)
  771. br.select_form("vbform")
  772. #The added random numbers force a cache miss!
  773. br.form['query'] = BOTNAME + " " + str(random.randint(0,999)) + " " + str(random.randint(0,999))
  774. br.form['showposts'] = ['1']
  775. response = br.submit()
  776. postIds = getPostIdsFromSearchPage(response.read())[::-1]
  777. print "Scanning posts minID is", config['minPostId']
  778. updated = False
  779. for postId in postIds:
  780. # print "PostID is", postId, config['minPostId']
  781. if int(postId) > int(config['minPostId']):
  782. print "Found New Post", postId, config['minPostId']
  783. updated = True
  784. scanPostForCommand(postId)
  785. setMinPostId(postId)
  786. return updated
  787.  
  788. def setMinPostId(postId):
  789. config['minPostId'] = int(postId)
  790. config.write()
  791.  
  792. init()
  793. if __name__=="__main__":
  794. if config['maintenance']['status'] == "down":
  795. if getServerStatusMessage()[0] > 0:
  796. config['maintenance']['status'] == "complete"
  797.  
  798. sleeptime = 60
  799. while True:
  800. if dayCount() - int(config['maintenance']['date']) > 5:
  801. searchForMaintenanceThread()
  802. if datetime.date.today().weekday() == 1 and time.localtime().tm_hour >= 14:
  803. sleeptime = min(sleeptime, 120)
  804. if dayCount() - int(config['maintenance']['date']) <= 1 and config['maintenance']['status'] in ["pending", "down"]:
  805. checkMaintenanceServerStatus()
  806. else:
  807. config['maintenance']['status'] == "complete"
  808.  
  809. time.sleep(10)
  810. if searchForCommand():
  811. sleeptime = 0
  812. print "Sleeping for ", sleeptime
  813. time.sleep(sleeptime)
  814. sleeptime = min(sleeptime + 30, MAX_SLEEP_INTERVAL)
  815.  
Not running #stdin #stdout 0s 0KB
stdin
Standard input is empty
stdout
Standard output is empty