meme.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. from event import Event
  2. import random
  3. import difflib
  4. import time
  5. import re
  6. try:
  7. import requests
  8. except ImportError:
  9. print "Warning: meme module requires requests."
  10. requests = object
  11. try:
  12. from meme_credentials import MemeCredentials as mc
  13. except ImportError:
  14. print "Warning: meme module requires credentials in modules/meme_credentials.py"
  15. class PhonyMc:
  16. imgflip_userid = "None"
  17. imgflip_password = "None"
  18. mc = PhonyMc()
  19. """
  20. the imgflip api requires credentials, which are bad to put directly into source code. in order to use this module, you will need a file in modules/ called meme_credentials, whose content follows this pattern:
  21. class MemeCredentials:
  22. imgflip_userid = "YOUR_USERID"
  23. imgflip_password = "YOUR_PASSWORD"
  24. Dassit.
  25. """
  26. class meme:
  27. def __init__(self, events=None, printer_handle=None, bot=None, say=None):
  28. self.events = events
  29. self.printer = printer_handle
  30. self.interests = ['__privmsg__']# should be first event in the listing.. so lines being added is a priority
  31. self.bot = bot
  32. self.say = say
  33. self.imgflip_userid = mc.imgflip_userid
  34. self.imgflip_password = mc.imgflip_password
  35. self.top_memes_list = self.get_top_memes()
  36. self.cmd = ".meme"
  37. self.help = ".meme [nickname]"
  38. self.ignore_list = [self.bot.NICK, 'TSDBot', 'Bonk-Bot']
  39. self.ignore_nicks = self.create_ignore_nicks_tuple()
  40. self.RATE_LIMIT = 30 #rate limit in seconds
  41. self.bot.mem_store['meme'] = {}
  42. for event in events:
  43. if event._type in self.interests:
  44. event.subscribe(self)
  45. def get_top_memes(self):
  46. """Makes an API call to imgflip to get top 100 most popular memes. Returns a list of results"""
  47. url = "https://api.imgflip.com/get_memes"
  48. try:
  49. top_memes = requests.get(url)
  50. except ConnectionError, e:
  51. self.bot.debug_print("ConnectionError in get_top_memes(): ")
  52. self.bot.debug_print(str(e))
  53. #check for HTTP errors
  54. try:
  55. top_memes.raise_for_status()
  56. except requests.exceptions.HTTPError, e:
  57. self.bot.debug_print("HTTPError in get_top_memes(): ")
  58. self.bot.debug_print(str(e))
  59. return
  60. #return list if successful
  61. try:
  62. top_memes_list = top_memes.json()['data']['memes']
  63. return top_memes_list
  64. except KeyError, e:
  65. self.bot.debug_print("KeyError in get_top_memes(): ")
  66. self.bot.debug_print(str(e))
  67. return
  68. def create_ignore_nicks_tuple(self):
  69. """creates a tuple with all nicks from self.ignore_list in <>"""
  70. nick_list = []
  71. for nick in self.ignore_list:
  72. nick_list.append("<"+nick+">")
  73. return tuple(nick_list)
  74. def get_random_meme_id(self):
  75. """Selects a random id from the top_memes_list"""
  76. try:
  77. return random.choice(self.top_memes_list)['id']
  78. except KeyError, e:
  79. self.bot.debug_print("KeyError in get_random_meme_id(): ")
  80. self.bot.debug_print(str(e))
  81. return
  82. def compare_description(self, meme_name, user_description):
  83. """compares two strings. if greater than 67% similarity, returns true"""
  84. comparison = difflib.SequenceMatcher()
  85. comparison.set_seq1(meme_name.lower())
  86. comparison.set_seq2(user_description.lower())
  87. if comparison.ratio() >= 0.67:
  88. return True
  89. return False
  90. def get_line(self, array_of_lines):
  91. """Given an array of lines from which to pick, randomly
  92. select an appropriate line, clean it up, and return the string."""
  93. line = ""
  94. #our counter so we don't get caught in an infinite loop when too few good lines exist
  95. counter = 0
  96. #make sure we get a line from someone speaking
  97. try:
  98. while not line.startswith("<") and counter < 20:
  99. counter += 1
  100. line = random.choice(array_of_lines)
  101. if not self.is_valid_line(line):
  102. line = ""
  103. except Exception as e:
  104. self.bot.debug_print("Error in get_random_line(): ")
  105. self.bot.debug_print(str(e))
  106. return
  107. #format the string for use in the meme and return it
  108. return self.format_string(line)
  109. def is_valid_line(self, line):
  110. """Given a line from the qdb buffer, return True if certain conditions are met
  111. that make it good for meme selection. Return False if not"""
  112. formatted_line = self.format_string(line) #strips the nick off the beginning
  113. if (formatted_line.startswith(".") or #reject .commands
  114. formatted_line.startswith("#") or #reject #commands meant for BonkBot
  115. formatted_line.startswith("s/") or #reject s// substitution lines
  116. self.contains_url(line) or #reject lines with URLs
  117. line.startswith(self.ignore_nicks) or #reject lines spoken by bots
  118. "TSDBot" in line): #reject any line with "TSDBot" due to mass printout summoning
  119. return False
  120. return True
  121. def get_user_lines(self, channel, nick):
  122. """Given a specific nick and channel, create a list of all their lines in the buffer"""
  123. line_list = []
  124. for line in self.bot.mem_store['qdb'][channel]:
  125. if line.lower().startswith("<"+nick.lower()+">"):
  126. line_list.append(line)
  127. return line_list
  128. def format_string(self, line):
  129. """Given an appropriate line, strip out <nick>. Otherwise return unmodified line"""
  130. if line.startswith("<"):
  131. return line.split("> ", 1)[1]
  132. else:
  133. return line
  134. def create_meme(self, meme_id, top_line, bottom_line):
  135. """Given a meme id from imgflip and two lines, top and bottom, submit a request
  136. to imgflip for a new meme and return the URL"""
  137. url = "https://api.imgflip.com/caption_image"
  138. payload = {'template_id':meme_id, 'username':self.imgflip_userid,
  139. 'password':self.imgflip_password, 'text0':top_line, 'text1':bottom_line}
  140. try:
  141. meme = requests.post(url, payload)
  142. except ConnectionError, e:
  143. self.bot.debug_print("ConnectionError in create_meme(): ")
  144. self.bot.debug_print(str(e))
  145. return
  146. #check for HTTP errors
  147. try:
  148. meme.raise_for_status()
  149. except request.exceptions.HTTPError, e:
  150. self.bot.debug_print("HTTPError in create_meme(): ")
  151. self.bot.debug_print(str(e))
  152. return
  153. try:
  154. return meme.json()['data']['url']
  155. except KeyError, e:
  156. self.bot.debug_print("KeyError in create_meme(): ")
  157. self.bot.debug_print("User: " + self.imgflip_userid + " Password: " + self.imgflip_password)
  158. self.bot.debug_print(str(e))
  159. self.bot.debug_print(str(meme.json()))
  160. return
  161. def get_last_meme_time(self, nick):
  162. """Given a channel name, return the last time .meme was called in that channel, return 0 if never used"""
  163. try:
  164. return self.bot.mem_store['meme'][nick]
  165. except KeyError:
  166. self.set_last_meme_time(nick)
  167. return 0
  168. def set_last_meme_time(self, nick):
  169. """Upon calling meme, set the last time it was used by that nick"""
  170. self.bot.mem_store['meme'][nick] = int(time.time())
  171. return
  172. def contains_url(self, line):
  173. """Given a string, returns True if there is a url present"""
  174. urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
  175. if urls:
  176. return True
  177. return False
  178. def check_rate(self, nick):
  179. """Check to see if the given nick has allowed enough time to pass before calling meme again. Return True and set
  180. the new last meme time if true. Warn nick and return False if not."""
  181. time_diff = int(time.time()) - self.get_last_meme_time(nick)
  182. if time_diff > self.RATE_LIMIT:
  183. self.set_last_meme_time(nick)
  184. return True
  185. else:
  186. self.say(nick, "WOOP WOOP! Meme Police! You must wait " + str(self.RATE_LIMIT - time_diff) + " seconds to use .meme again.")
  187. return False
  188. def get_random_flavor(self):
  189. """Change up the flavor text when returning memes. It got boring before"""
  190. flavors = ["Tip top: ",
  191. "Only the dankest: ",
  192. "It's a trap!: ",
  193. "[10] outta 10: ",
  194. "A mastapeece: ",
  195. "Not sure if want: ",
  196. "*holds up spork*: ",
  197. "Da Fuq?: "
  198. ]
  199. return random.choice(flavors)
  200. def handle(self, event):
  201. if event.msg.startswith(".meme"):
  202. #just return help. we won't bother people with rate limits for this
  203. if event.msg == ".meme help":
  204. self.say(event.channel, "Top meme descriptions here: https://api.imgflip.com/popular_meme_ids")
  205. self.say(event.channel, "Usage: .meme [[description of meme image] [| nick of user to pull lines from]]")
  206. return
  207. #check the rate first, then continue with processing
  208. if not self.check_rate(event.user):
  209. return
  210. #just a random meme please
  211. if event.msg == ".meme":
  212. line_array = self.bot.mem_store['qdb'][event.channel]
  213. top_line = self.get_line(line_array)
  214. bottom_line = self.get_line(line_array)
  215. #more detail requested
  216. else:
  217. nick = event.msg[5:].strip().split(None)[0]
  218. line_array = self.get_user_lines(event.channel, nick)
  219. if not line_array:
  220. self.say(event.channel, "That memer hasn't spoken or doesn't exist. Using randoms.")
  221. line_array = self.bot.mem_store['qdb'][event.channel]
  222. top_line = self.get_line(line_array)
  223. bottom_line = self.get_line(line_array)
  224. meme_id = self.get_random_meme_id()
  225. meme_url = self.create_meme(meme_id, top_line, bottom_line)
  226. if meme_url:
  227. self.say(event.channel, self.get_random_flavor() + meme_url)
  228. else:
  229. self.say(event.channel, "It's all ogre. Memery broken.")
  230. return