recap.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. # -*- coding: utf-8 -*-
  2. from event import Event
  3. import random
  4. import string
  5. import time
  6. import re
  7. import sys
  8. if sys.version_info > (3,0,0):
  9. try:
  10. from .basemodule import BaseModule
  11. except (ImportError, SystemError):
  12. from modules.basemodule import BaseModule
  13. else:
  14. try:
  15. from basemodule import BaseModule
  16. except (ImportError, SystemError):
  17. from modules.basemodule import BaseModule
  18. class recap(BaseModule):
  19. def post_init(self):
  20. self.interests = ['__privmsg__']# should be first event in the listing.. so lines being added is a priority
  21. self.cmd = ".recap"
  22. self.help = ".recap"
  23. self.ignore_list = [self.bot.NICK, 'TSDBot', 'Bonk-Bot']
  24. self.ignore_nicks = self.create_ignore_nicks_tuple()
  25. self.RATE_LIMIT = 600 #rate limit in seconds
  26. self.MIN_WORDS = 4 #we want at least this many words for a valid line
  27. self.RECAP_LENGTH = 4 #number of lines to include in recap
  28. self.bot.mem_store['recap'] = {}
  29. for event in self.events:
  30. if event._type in self.interests:
  31. event.subscribe(self)
  32. def create_ignore_nicks_tuple(self):
  33. """creates a tuple with all nicks from self.ignore_list in <>"""
  34. nick_list = []
  35. for nick in self.ignore_list:
  36. nick_list.append("<"+nick+">")
  37. return tuple(nick_list)
  38. def get_lines(self, channel):
  39. """Given a channel, searches the qdb buffer for 4 random, suitable lines."""
  40. try:
  41. #create a copy of the channel buffer
  42. lines = list(self.bot.mem_store['qdb'][channel])
  43. #shuffle that copy up randomly
  44. random.shuffle(lines)
  45. recap = []
  46. while len(lines)>0 and len(recap) < self.RECAP_LENGTH:
  47. #as long as we have lines in the buffer and haven't chosen the desired number
  48. #keep popping lines off the top of the scrambled buffer
  49. #this ensures no duplicates ever are chosen
  50. line = lines.pop()
  51. #test for validity and add to our array of valid lines
  52. if self.valid_line(line):
  53. parts = line.split(None, 1)
  54. recap.append(self.scramble_nick(parts[0]) + " " + self.dramatize_line(parts[1]))
  55. return recap
  56. except Exception as e:
  57. self.bot.debug_print("Error getting channel buffer in get_lines")
  58. self.bot.debug_print(str(e))
  59. return False
  60. def valid_line(self, line):
  61. """Returns True if a given line matches all requirements for validity:
  62. Not an action line, longer than minimum length, not spoken by ignored nicks, no URLs"""
  63. #easy check to see if it's a line of someone speaking
  64. if line.startswith("<"):
  65. if not (line.startswith(self.ignore_nicks) or
  66. "TSDBot" in line or #rejects all printout requests, because that's a lotta noise
  67. self.contains_url(line) or
  68. len(line.split()) < self.MIN_WORDS or
  69. line.split(None,1)[1].startswith((".","#","s/"))):
  70. return True
  71. return False
  72. def dramatize_line(self, line):
  73. """Pass a valid line in, return line with some random type of dramatic formatting"""
  74. drama = random.randint(0,750) #choose a random number
  75. line = line.strip()
  76. try:
  77. if drama in range(100,200):
  78. return line + "??"
  79. elif drama in range(200,300):
  80. return line + "..."
  81. elif drama in range(300,400):
  82. return line.upper()
  83. elif drama in range(400,500):
  84. return line + "!!"
  85. elif drama in range(500,550):
  86. return line.upper() + "!!"
  87. elif drama in range(550,600):
  88. return line.upper() + "?!?"
  89. elif drama in range(600,700):
  90. return line + " ~"
  91. elif drama in range(700,750):
  92. listline = list(line)
  93. for x in range(0, len(listline), 2):
  94. listline[x] = listline[x].upper()
  95. return ''.join(listline)
  96. elif drama == 69:
  97. return '( ͡° ͜ʖ ͡°) (ง ͠° ͟ل͜ ͡°)ง ᕦ( ͡° ͜ʖ ͡°)ᕤ ( ͡~ ͜ʖ ͡°)'
  98. elif drama == 750:
  99. return line[::-1] #reversed string
  100. else:
  101. return line
  102. except:
  103. return line
  104. def scramble_nick(self, nick):
  105. """Given a valid nick in the format <nickname>, scramble a vowel in the nick to avoid beeping the user"""
  106. try:
  107. vowels = 'aeiou'
  108. nick_vowels = []
  109. nick_letters = list(nick[1:-1]) #grab the nick from between <> and conver to a list to make changes
  110. #create a list of tuples. each tuple is (index of a vowel in nick, the vowel at that index)
  111. for i,v in enumerate(nick_letters):
  112. if v.lower() in vowels:
  113. nick_vowels.append((i,v))
  114. #randomly choose one of the vowels in the nick to replace
  115. sel = random.choice(nick_vowels)
  116. #randomly select any vowel
  117. repl = random.choice(vowels)
  118. #keep doing the previous line until we get something different from what we're replacing
  119. while repl == sel[1].lower():
  120. repl = random.choice(vowels)
  121. #if the chosen letter to be replaced is upper case, make sure the replacement is too
  122. if nick_letters[sel[0]].isupper():
  123. nick_letters[sel[0]] = repl.upper()
  124. else:
  125. nick_letters[sel[0]] = repl
  126. #take that list of individual characters and slam it all back together into a string surrounded by <>
  127. nick = '<' + ''.join(nick_letters) + '>'
  128. #take the old nick out of the submitted line and replace it with the new scramble one
  129. return nick
  130. except IndexError: # no vowels, probably
  131. #self.bot.debug_print("Error scrambling nick. Just moving on. Nick was: " + original_nick, error=True)
  132. return nick #if there's any problems at all, just don't scramble the nick. odd cases like no vowels
  133. def contains_url(self, line):
  134. """Given a string, returns True if there is a url present"""
  135. urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
  136. if urls:
  137. return True
  138. return False
  139. def check_rate(self, channel):
  140. """Check to see if the given channel has allowed enough time to pass before calling recap again. Return True
  141. and set the new time limit if true. Return False if not."""
  142. try:
  143. if self.get_timediff(channel) <= 0:
  144. self.bot.mem_store['recap'][channel] = int(time.time()) + self.RATE_LIMIT
  145. return True
  146. else:
  147. return False
  148. except KeyError:
  149. self.bot.mem_store['recap'][channel] = int(time.time()) + self.RATE_LIMIT
  150. return True
  151. def reset_timer(self, channel):
  152. """If there's an error getting a recap, call this to reset lockdown timer"""
  153. self.bot.mem_store['recap'][channel] = int(time.time())
  154. def get_timediff(self, channel):
  155. """Return how much time remains in the function lockdown"""
  156. return self.bot.mem_store['recap'][channel] - int(time.time())
  157. def get_episode(self):
  158. """Return a list with two elements: a random show title and episode name"""
  159. titles = ["Internet Relay Chat",
  160. "Multiplayer Notepad",
  161. "Wise Use of Work Hours",
  162. "The Kanbo Korner",
  163. "2 Grils?",
  164. "Exodus",
  165. "Tex's Tricks",
  166. "Top Fun",
  167. "Big Anime Robots",
  168. "The Meme Machine",
  169. "The Botpocalypse",
  170. "IRC",
  171. "St. Paddy's",
  172. "Get Bonked",
  173. "Schooly's Skills",
  174. "D O R D R A Y",
  175. "GET BIG",
  176. "AYYYY",
  177. "Corgis"
  178. ]
  179. episodes = ["The Mystery of DeeJ",
  180. "Paddy's Big Goodbye",
  181. "Hickory Dickory...Dead",
  182. "BoneKin Dies",
  183. "Dr. DeeJ and Mr. DorJ",
  184. "The Double Dorj",
  185. "Bonk-Bot's Crash Test Dummies",
  186. "IRC Finds a Dead Body",
  187. "Beach Party",
  188. "Everyone Gets Sucked Back in Time",
  189. "Brass Tax",
  190. "Return to Bonk Mountain",
  191. "The Incredible Bonk",
  192. "Paddy Gets Big",
  193. "Dawn of the New Age",
  194. "Tex Goes to Work",
  195. "Planet of the IRC",
  196. "Nart Gets His GED",
  197. "High School Drama",
  198. "TD Moves Out",
  199. "TDSpiral Paints a Picture",
  200. "Kapowaz Wins",
  201. "Banana Gets High",
  202. "Dragon's Laird",
  203. "StarLaird",
  204. "Kanboface",
  205. "Eternity, Loyalty, Honesty",
  206. "Paddy on Parole",
  207. "The HBO Beauty Contest, Pt. 2",
  208. "Snipe Reviews Halo 5",
  209. "1-800-GET-GOOD",
  210. "A Baby Wheel",
  211. "BoneKin Ruins the Creative Process",
  212. "The 80 Proof Spoof",
  213. "IRC Forgets to Set Their Holiday Nicks",
  214. "Hot Diggety Dorj",
  215. "Yapok Talks",
  216. "Hellmitre Argues With His Bot",
  217. "Pybot Strikes Back",
  218. "Where's Schooly?",
  219. "Heavy Is the BanHammer",
  220. "Sunbreaker or Sunbroken?",
  221. "Testing in Production",
  222. "BoneKin Codes a New Module and Forgets How to Use Git",
  223. "The Curse of the Spiduh",
  224. "Snipe Quits Halo (Part 2)",
  225. "Monopoly Is A Fun Game",
  226. "RAWR",
  227. "Jennos Has A Cow",
  228. "Pybot Steals TSDBot's Modules",
  229. "Paddy on Patrol",
  230. "Schooly's Return",
  231. "The PUNisher",
  232. "Dr. GV, PhD, although I guess if he was a medical doctor he wouldn't have a PhD? Or maybe they can, I don't know. I know he'd be called 'Dr.' though. I think they should make that clearer, like in the dictionary or wherever they spell things out like that. But I guess it wouldn't be an English thing it'd be a medical licensing and terminology thing? Uuuuuuugggggghhhh it's already so late and I was supposed to go to bed 23 minutes ago but then t"
  233. ]
  234. return [random.choice(titles), random.choice(episodes)]
  235. def handle(self, event):
  236. if event.msg == ".recap":
  237. #check the rate first, then continue with processing
  238. if self.check_rate(event.channel):
  239. episode = self.get_episode()
  240. recap = self.get_lines(event.channel)
  241. if not recap:
  242. self.reset_timer(event.channel)
  243. self.say(event.channel, "Error processing recap request")
  244. return
  245. self.say(event.channel, "Previously on \"" + episode[0] + "\": ")
  246. for r in recap:
  247. self.say(event.channel, r)
  248. self.say(event.channel, "Tonight's episode: \"" + episode[1] + "\"")
  249. else:
  250. timediff = str(self.get_timediff(event.channel))
  251. self.say(event.user, "Recap is on lockdown for " + timediff + " more seconds.")