Completed
Push — master ( 3fb799...a93322 )
by Matt
01:03
created

meme.get_last_meme_time()   A

Complexity

Conditions 2

Size

Total Lines 7

Duplication

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