meme.set_last_meme_time()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 4
rs 10
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) or   #reject lines spoken by bots
132
            "TSDBot" in line):    #reject any line with "TSDBot" due to mass printout summoning
133
            return False
134
        return True
135
136
    def get_user_lines(self, channel, nick):
137
        """Given a specific nick and channel, create a list of all their lines in the buffer"""
138
        line_list = []
139
        for line in self.bot.mem_store['qdb'][channel]:
140
            if line.lower().startswith("<"+nick.lower()+">"):
141
                line_list.append(line)
142
        return line_list
143
144
145
    def format_string(self, line):
146
        """Given an appropriate line, strip out <nick>. Otherwise return unmodified line"""
147
        if line.startswith("<"):
148
            return line.split("> ", 1)[1]
149
        else:
150
            return line
151
152
    def create_meme(self, meme_id, top_line, bottom_line):
153
        """Given a meme id from imgflip and two lines, top and bottom, submit a request
154
        to imgflip for a new meme and return the URL"""
155
        url = "https://api.imgflip.com/caption_image"
156
        payload = {'template_id':meme_id, 'username':self.imgflip_userid, 
157
                   'password':self.imgflip_password, 'text0':top_line, 'text1':bottom_line}
158
        try:
159
            meme = requests.post(url, payload)
160
        except ConnectionError, e:
161
            self.bot.debug_print("ConnectionError in create_meme(): ")
162
            self.bot.debug_print(str(e))
163
            return
164
        #check for HTTP errors
165
        try:
166
            meme.raise_for_status()
167
        except request.exceptions.HTTPError, e:
168
            self.bot.debug_print("HTTPError in create_meme(): ")
169
            self.bot.debug_print(str(e))
170
            return
171
        try:
172
            return meme.json()['data']['url']
173
        except KeyError, e:
174
            self.bot.debug_print("KeyError in create_meme(): ")
175
            self.bot.debug_print("User: " + self.imgflip_userid + " Password: " + self.imgflip_password)
176
            self.bot.debug_print(str(e))
177
            self.bot.debug_print(str(meme.json()))
178
            return
179
180
    def get_last_meme_time(self, nick):
181
        """Given a channel name, return the last time .meme was called in that channel, return 0 if never used"""
182
        try:
183
            return self.bot.mem_store['meme'][nick]
184
        except KeyError:
185
            self.set_last_meme_time(nick)
186
            return 0
187
188
    def set_last_meme_time(self, nick):
189
        """Upon calling meme, set the last time it was used by that nick"""
190
        self.bot.mem_store['meme'][nick] = int(time.time())
191
        return
192
193
    def contains_url(self, line):
194
        """Given a string, returns True if there is a url present"""
195
        urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
196
        if urls:
197
            return True
198
        return False
199
200
    def check_rate(self, nick):
201
        """Check to see if the given nick has allowed enough time to pass before calling meme again. Return True and set
202
           the new last meme time if true. Warn nick and return False if not."""
203
        time_diff = int(time.time()) - self.get_last_meme_time(nick)
204
        if time_diff > self.RATE_LIMIT:
205
            self.set_last_meme_time(nick)
206
            return True
207
        else:
208
            self.say(nick, "WOOP WOOP! Meme Police! You must wait " + str(self.RATE_LIMIT - time_diff) + " seconds to use .meme again.")
209
            return False
210
      
211
    def get_random_flavor(self):
212
        """Change up the flavor text when returning memes. It got boring before"""
213
        flavors = ["Tip top: ",
214
                   "Only the dankest: ",
215
                   "It's a trap!: ",
216
                   "[10] outta 10: ",
217
                   "A mastapeece: ",
218
                   "Not sure if want: ",
219
                   "*holds up spork*: ",
220
                   "Da Fuq?: "
221
                  ]
222
        return random.choice(flavors)
223
224
225
226
    def handle(self, event):
227
        if event.msg.startswith(".meme"):
228
            #just return help. we won't bother people with rate limits for this
229
            if event.msg == ".meme help":
230
                self.say(event.channel, "Top meme descriptions here: https://api.imgflip.com/popular_meme_ids")
231
                self.say(event.channel, "Usage: .meme [[description of meme image] [| nick of user to pull lines from]]")
232
                return
233
            
234
            #check the rate first, then continue with processing
235
            if not self.check_rate(event.user):
236
              return
237
            #just a random meme please
238
            if event.msg == ".meme":
239
                line_array = self.bot.mem_store['qdb'][event.channel]
240
                top_line = self.get_line(line_array)
241
                bottom_line = self.get_line(line_array)
242
            #more detail requested
243
            else:
244
                nick = event.msg[5:].strip().split(None)[0]
245
                line_array = self.get_user_lines(event.channel, nick)
246
                if not line_array:
247
                    self.say(event.channel, "That memer hasn't spoken or doesn't exist. Using randoms.")
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
252
            meme_id = self.get_random_meme_id()
253
            meme_url = self.create_meme(meme_id, top_line, bottom_line)
254
            if meme_url:
255
                self.say(event.channel, self.get_random_flavor() + meme_url)
256
            else:
257
                self.say(event.channel, "It's all ogre. Memery broken.")
258
                return
259