meme.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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 ceredentials 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 [[meme name] [| nick to use for history]]"
  38. self.ignore_list = [self.bot.NICK, 'TSDBot', 'Bonk-Bot']
  39. self.ignore_nicks = self.create_ignore_nicks_tuple()
  40. self.RATE_LIMIT = 300 #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_specific_meme_id(self, user_description):
  91. """finds a meme_id based on user's description. if not found, selects randomly"""
  92. try:
  93. for meme in self.top_memes_list:
  94. if self.compare_description(meme['name'], user_description):
  95. return meme['id']
  96. except (IndexError, KeyError), e:
  97. self.bot.debug_print("KeyError in get_specific_meme_id(): ")
  98. self.bot.debug_print(str(e))
  99. return None
  100. def get_line(self, array_of_lines):
  101. """Given an array of lines from which to pick, randomly
  102. select an appropriate line, clean it up, and return the string."""
  103. line = ""
  104. #our counter so we don't get caught in an infinite loop when too few good lines exist
  105. counter = 0
  106. #make sure we get a line from someone speaking
  107. try:
  108. while not line.startswith("<") and counter < 20:
  109. counter += 1
  110. line = random.choice(array_of_lines)
  111. formatted_line = self.format_string(line)
  112. #discard any lines with .commands or said by ignored nicks. those aren't any fun
  113. #also discard lines that contain a URL
  114. if formatted_line.startswith(".") or line.startswith(self.ignore_nicks) or self.contains_url(line):
  115. line = ""
  116. except KeyError, e:
  117. self.bot.debug_print("KeyError in get_random_line(): ")
  118. self.bot.debug_print(str(e))
  119. return
  120. #format the string for use in the meme and return it
  121. return formatted_line
  122. def get_user_lines(self, channel, nick):
  123. """Given a specific nick and channel, create a list of all their lines in the buffer"""
  124. line_list = []
  125. for line in self.bot.mem_store['qdb'][channel]:
  126. if line.lower().startswith("<"+nick.lower()+">"):
  127. line_list.append(line)
  128. return line_list
  129. def format_string(self, line):
  130. """Given an appropriate line, strip out <nick>"""
  131. if line.startswith("<"):
  132. return line.split("> ", 1)[1]
  133. else:
  134. return line
  135. def create_meme(self, meme_id, top_line, bottom_line):
  136. """Given a meme id from imgflip and two lines, top and bottom, submit a request
  137. to imgflip for a new meme and return the URL"""
  138. url = "https://api.imgflip.com/caption_image"
  139. payload = {'template_id':meme_id, 'username':self.imgflip_userid,
  140. 'password':self.imgflip_password, 'text0':top_line, 'text1':bottom_line}
  141. try:
  142. meme = requests.post(url, payload)
  143. except ConnectionError, e:
  144. self.bot.debug_print("ConnectionError in create_meme(): ")
  145. self.bot.debug_print(str(e))
  146. return
  147. #check for HTTP errors
  148. try:
  149. meme.raise_for_status()
  150. except request.exceptions.HTTPError, e:
  151. self.bot.debug_print("HTTPError in create_meme(): ")
  152. self.bot.debug_print(str(e))
  153. return
  154. try:
  155. return meme.json()['data']['url']
  156. except KeyError, e:
  157. self.bot.debug_print("KeyError in create_meme(): ")
  158. self.bot.debug_print(str(e))
  159. return
  160. def get_last_meme_time(self, nick):
  161. """Given a channel name, return the last time .meme was called in that channel, return 0 if never used"""
  162. try:
  163. return self.bot.mem_store['meme'][nick]
  164. except KeyError:
  165. self.set_last_meme_time(nick)
  166. return 0
  167. def set_last_meme_time(self, nick):
  168. """Upon calling meme, set the last time it was used by that nick"""
  169. self.bot.mem_store['meme'][nick] = int(time.time())
  170. return
  171. def contains_url(self, line):
  172. """Given a string, returns True if there is a url present"""
  173. urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
  174. if urls:
  175. return True
  176. return False
  177. def check_rate(self, nick):
  178. """Check to see if the given nick has allowed enough time to pass before calling meme again. Return True and set
  179. the new last meme time if true. Warn nick and return False if not."""
  180. time_diff = int(time.time()) - self.get_last_meme_time(nick)
  181. if time_diff > self.RATE_LIMIT:
  182. self.set_last_meme_time(nick)
  183. return True
  184. else:
  185. self.say(nick, "WOOP WOOP! Meme Police! You must wait " + str(self.RATE_LIMIT - time_diff) + " seconds to use .meme again.")
  186. return False
  187. def get_random_flavor(self):
  188. """Change up the flavor text when returning memes. It got boring before"""
  189. flavors = ["Tip top: ",
  190. "Only the dankest: ",
  191. "It's a trap!: ",
  192. "[10] outta 10: ",
  193. "A mastapeece: ",
  194. "Not sure if want: ",
  195. "*holds up spork*: ",
  196. "Da Fuq?: "
  197. ]
  198. return random.choice(flavors)
  199. def handle(self, event):
  200. if event.msg.startswith(".meme"):
  201. #just return help. we won't bother people with rate limits for this
  202. if event.msg == ".meme help":
  203. self.say(event.channel, "Top meme descriptions here: https://api.imgflip.com/popular_meme_ids")
  204. self.say(event.channel, "Usage: .meme [[description of meme image] [| nick of user to pull lines from]]")
  205. return
  206. #check the rate first, then continue with processing
  207. if self.check_rate(event.user):
  208. #just a random meme please
  209. if event.msg == ".meme":
  210. line_array = self.bot.mem_store['qdb'][event.channel]
  211. top_line = self.get_line(line_array)
  212. bottom_line = self.get_line(line_array)
  213. meme_id = self.get_random_meme_id()
  214. meme_url = self.create_meme(meme_id, top_line, bottom_line)
  215. if(meme_url):
  216. self.say(event.channel, self.get_random_flavor() + meme_url)
  217. else:
  218. self.say(event.channel, "Error making memes. What a bummer.")
  219. return
  220. #more detail requested
  221. else:
  222. args = event.msg[5:].split("|",1)
  223. if args[0].strip():
  224. meme_id = self.get_specific_meme_id(args[0])
  225. if not meme_id:
  226. self.say(event.channel, "Bruh, I couldn't find that meme. We'll do a rando.")
  227. meme_id = self.get_random_meme_id()
  228. else:
  229. meme_id = self.get_random_meme_id()
  230. if len(args) > 1:
  231. line_array = self.get_user_lines(event.channel, args[1].strip())
  232. if not line_array:
  233. self.say(event.channel, "That memer hasn't spoken or doesn't exist. Using randoms.")
  234. line_array = self.bot.mem_store['qdb'][event.channel]
  235. else:
  236. line_array = self.bot.mem_store['qdb'][event.channel]
  237. top_line = self.get_line(line_array)
  238. bottom_line = self.get_line(line_array)
  239. meme_url = self.create_meme(meme_id, top_line, bottom_line)
  240. if meme_url:
  241. self.say(event.channel, self.get_random_flavor() + meme_url)
  242. else:
  243. self.say(event.channel, "It's all ogre. Memery broken.")
  244. return