Completed
Push — master ( fb0b4b...cb28ba )
by Matt
55s
created

meme.compare_description()   A

Complexity

Conditions 2

Size

Total Lines 8

Duplication

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