Passed
Push — master ( 3abe52...7e262d )
by Mikael
03:20
created

marvin_actions   F

Complexity

Total Complexity 94

Size/Duplication

Total Lines 552
Duplicated Lines 0 %

Test Coverage

Coverage 85.71%

Importance

Changes 0
Metric Value
eloc 334
dl 0
loc 552
ccs 258
cts 301
cp 0.8571
rs 2
c 0
b 0
f 0
wmc 94

32 Functions

Rating   Name   Duplication   Size   Complexity  
B marvinBudord() 0 18 7
A marvinHelp() 0 9 2
A videoOfToday() 0 14 2
A marvinStats() 0 9 2
A wordsAfterKeyWords() 0 14 4
A marvinExplainShell() 0 13 2
A marvinLunch() 0 22 4
B getString() 0 18 6
A marvinSun() 0 24 3
A marvinQuote() 0 9 2
A marvinSource() 0 9 2
A getAllActions() 0 28 1
A marvinVideoOfToday() 0 10 3
A marvinSmile() 0 8 2
A marvinWhoIs() 0 9 2
A marvinGoogle() 0 13 2
A marvinSayHi() 0 16 2
A getCommit() 0 13 2
B marvinWeather() 0 34 7
A nextBBQ() 0 18 3
A commitStrip() 0 15 2
A marvinCommit() 0 8 2
A marvinNameday() 0 21 4
B marvinTimeToBBQ() 0 24 6
A marvinStream() 0 8 2
A thirdFridayIn() 0 12 1
A marvinPrinciple() 0 14 3
B marvinBirthday() 0 27 6
A getJoke() 0 12 2
A marvinJoke() 0 8 2
A marvinStrip() 0 8 2
A marvinUptime() 0 8 2

How to fix   Complexity   

Complexity

Complex classes like marvin_actions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#! /usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
4
"""
5
Make actions for Marvin, one function for each action.
6
"""
7 1
from urllib.parse import quote_plus
8 1
from urllib.request import urlopen
9 1
import calendar
10 1
import datetime
11 1
import json
12 1
import logging
13 1
import random
14 1
import requests
15
16 1
from bs4 import BeautifulSoup
17
18 1
LOG = logging.getLogger("action")
19
20 1
def getAllActions():
21
    """
22
    Return all actions in an array.
23
    """
24
    return [
25
        marvinExplainShell,
26
        marvinGoogle,
27
        marvinLunch,
28
        marvinVideoOfToday,
29
        marvinWhoIs,
30
        marvinHelp,
31
        marvinSource,
32
        marvinBudord,
33
        marvinQuote,
34
        marvinStats,
35
        marvinWeather,
36
        marvinSun,
37
        marvinSayHi,
38
        marvinSmile,
39
        marvinStrip,
40
        marvinTimeToBBQ,
41
        marvinBirthday,
42
        marvinNameday,
43
        marvinUptime,
44
        marvinStream,
45
        marvinPrinciple,
46
        marvinJoke,
47
        marvinCommit
48
    ]
49
50
51
# Load all strings from file
52 1
with open("marvin_strings.json", encoding="utf-8") as f:
53 1
    STRINGS = json.load(f)
54
55
56 1
def getString(key, key1=None):
57
    """
58
    Get a string from the string database.
59
    """
60 1
    data = STRINGS[key]
61 1
    if isinstance(data, list):
62 1
        res = data[random.randint(0, len(data) - 1)]
63 1
    elif isinstance(data, dict):
64 1
        if key1 is None:
65 1
            res = data
66
        else:
67 1
            res = data[key1]
68 1
            if isinstance(res, list):
69 1
                res = res[random.randint(0, len(res) - 1)]
70 1
    elif isinstance(data, str):
71 1
        res = data
72
73 1
    return res
0 ignored issues
show
introduced by
The variable res does not seem to be defined for all execution paths.
Loading history...
74
75
76 1
def marvinSmile(row):
77
    """
78
    Make Marvin smile.
79
    """
80 1
    msg = None
81 1
    if any(r in row for r in ["smile", "le", "skratta", "smilies"]):
82 1
        msg = getString("smile")
83 1
    return msg
84
85
86 1
def wordsAfterKeyWords(words, keyWords):
87
    """
88
    Return all items in the words list after the first occurence
89
    of an item in the keyWords list.
90
    """
91 1
    kwIndex = []
92 1
    for kw in keyWords:
93 1
        if kw in words:
94 1
            kwIndex.append(words.index(kw))
95
96 1
    if not kwIndex:
97 1
        return None
98
99 1
    return words[min(kwIndex)+1:]
100
101
102 1
def marvinGoogle(row):
103
    """
104
    Let Marvin present an url to google.
105
    """
106 1
    query = wordsAfterKeyWords(row, ["google", "googla"])
107 1
    if not query:
108 1
        return None
109
110 1
    searchStr = " ".join(query)
111 1
    url = "https://www.google.se/search?q="
112 1
    url += quote_plus(searchStr)
113 1
    msg = getString("google")
114 1
    return msg.format(url)
115
116
117 1
def marvinExplainShell(row):
118
    """
119
    Let Marvin present an url to the service explain shell to
120
    explain a shell command.
121
    """
122 1
    query = wordsAfterKeyWords(row, ["explain", "förklara"])
123 1
    if not query:
124 1
        return None
125 1
    cmd = " ".join(query)
126 1
    url = "http://explainshell.com/explain?cmd="
127 1
    url += quote_plus(cmd, "/:")
128 1
    msg = getString("explainShell")
129 1
    return msg.format(url)
130
131
132 1
def marvinSource(row):
133
    """
134
    State message about sourcecode.
135
    """
136 1
    msg = None
137 1
    if any(r in row for r in ["källkod", "source"]):
138 1
        msg = getString("source")
139
140 1
    return msg
141
142
143 1
def marvinBudord(row):
144
    """
145
    What are the budord for Marvin?
146
    """
147 1
    msg = None
148 1
    if any(r in row for r in ["budord", "stentavla"]):
149 1
        if any(r in row for r in ["#1", "1"]):
150 1
            msg = getString("budord", "#1")
151 1
        elif any(r in row for r in ["#2", "2"]):
152 1
            msg = getString("budord", "#2")
153 1
        elif any(r in row for r in ["#3", "3"]):
154 1
            msg = getString("budord", "#3")
155 1
        elif any(r in row for r in ["#4", "4"]):
156 1
            msg = getString("budord", "#4")
157 1
        elif any(r in row for r in ["#5", "5"]):
158 1
            msg = getString("budord", "#5")
159
160 1
    return msg
161
162
163 1
def marvinQuote(row):
164
    """
165
    Make a quote.
166
    """
167 1
    msg = None
168 1
    if any(r in row for r in ["quote", "citat", "filosofi", "filosofera"]):
169 1
        msg = getString("hitchhiker")
170
171 1
    return msg
172
173
174 1
def videoOfToday():
175
    """
176
    Check what day it is and provide a url to a suitable video together with a greeting.
177
    """
178 1
    dayNum = datetime.date.weekday(datetime.date.today()) + 1
179 1
    msg = getString("weekdays", str(dayNum))
180 1
    video = getString("video-of-today", str(dayNum))
181
182 1
    if video:
183 1
        msg += " En passande video är " + video
184
    else:
185
        msg += " Jag har ännu ingen passande video för denna dagen."
186
187 1
    return msg
188
189
190 1
def marvinVideoOfToday(row):
191
    """
192
    Show the video of today.
193
    """
194 1
    msg = None
195 1
    if any(r in row for r in ["idag", "dagens"]):
196 1
        if any(r in row for r in ["video", "youtube", "tube"]):
197 1
            msg = videoOfToday()
198
199 1
    return msg
200
201
202 1
def marvinWhoIs(row):
203
    """
204
    Who is Marvin.
205
    """
206 1
    msg = None
207 1
    if all(r in row for r in ["vem", "är"]):
208 1
        msg = getString("whois")
209
210 1
    return msg
211
212
213 1
def marvinHelp(row):
214
    """
215
    Provide a menu.
216
    """
217 1
    msg = None
218 1
    if any(r in row for r in ["hjälp", "help", "menu", "meny"]):
219 1
        msg = getString("menu")
220
221 1
    return msg
222
223
224 1
def marvinStats(row):
225
    """
226
    Provide a link to the stats.
227
    """
228 1
    msg = None
229 1
    if any(r in row for r in ["stats", "statistik", "ircstats"]):
230 1
        msg = getString("ircstats")
231
232 1
    return msg
233
234
235 1
def marvinSayHi(row):
236
    """
237
    Say hi with a nice message.
238
    """
239 1
    msg = None
240 1
    if any(r in row for r in [
241
            "snälla", "hej", "tjena", "morsning", "morrn", "mår", "hallå",
242
            "halloj", "läget", "snäll", "duktig", "träna", "träning",
243
            "utbildning", "tack", "tacka", "tackar", "tacksam"
244
    ]):
245 1
        smile = getString("smile")
246 1
        hello = getString("hello")
247 1
        friendly = getString("friendly")
248 1
        msg = f"{smile} {hello} {friendly}"
249
250 1
    return msg
251
252
253 1
def marvinLunch(row):
254
    """
255
    Help decide where to eat.
256
    """
257 1
    lunchOptions = {
258
        'stan centrum karlskrona kna': 'lunch-karlskrona',
259
        'ängelholm angelholm engelholm': 'lunch-angelholm',
260
        'hässleholm hassleholm': 'lunch-hassleholm',
261
        'malmö malmo malmoe': 'lunch-malmo',
262
        'göteborg goteborg gbg': 'lunch-goteborg'
263
    }
264
265 1
    if any(r in row for r in ["lunch", "mat", "äta", "luncha"]):
266 1
        lunchStr = getString('lunch-message')
267
268 1
        for keys, value in lunchOptions.items():
269 1
            if any(r in row for r in keys.split(" ")):
270 1
                return lunchStr.format(getString(value))
271
272 1
        return lunchStr.format(getString('lunch-bth'))
273
274 1
    return None
275
276
277 1
def marvinSun(row):
278
    """
279
    Check when the sun goes up and down.
280
    """
281 1
    msg = None
282 1
    if any(r in row for r in ["sol", "solen", "solnedgång", "soluppgång", "sun"]):
283 1
        try:
284 1
            url = getString("sun", "url")
285 1
            r = requests.get(url, timeout=5)
286 1
            sundata = r.json()
287
            # Formats the time from the response to HH:mm instead of hh:mm:ss
288 1
            sunrise = sundata["results"]["sunrise"].split()[0][:-3]
289 1
            sunset = sundata["results"]["sunset"].split()[0][:-3]
290
            # The api uses AM/PM notation, this converts the sunset to 12 hour time
291 1
            sunsetHour = int(sunset.split(":")[0]) + 12
292 1
            sunset = str(sunsetHour) + sunset[-3:]
293 1
            msg = getString("sun", "msg").format(sunrise, sunset)
294 1
            return msg
295
296 1
        except Exception as e:
297 1
            LOG.error("Failed to get sun times: %s", e)
298 1
            return getString("sun", "error")
299
300
    return msg
301
302
303 1
def marvinWeather(row):
304
    """
305
    Check what the weather prognosis looks like.
306
    """
307
    msg = ""
308
    if any(r in row for r in ["väder", "vädret", "prognos", "prognosen", "smhi"]):
309
        forecast = ""
310
        observation = ""
311
312
        try:
313
            station_req = requests.get(getString("smhi", "station_url"), timeout=5)
314
            weather_code:int = int(station_req.json().get("value")[0].get("value"))
315
316
            weather_codes_req = requests.get(getString("smhi", "weather_codes_url"), timeout=5)
317
            weather_codes_arr: list = weather_codes_req.json().get("entry")
318
319
            current_weather_req = requests.get(getString("smhi", "current_weather_url"), timeout=5)
320
            current_w_data: list = current_weather_req.json().get("timeSeries")[0].get("parameters")
321
322
            for curr_w in current_w_data:
323
                if curr_w.get("name") == "t":
324
                    forecast = curr_w.get("values")[0]
325
326
            for code in weather_codes_arr:
327
                if code.get("key") == weather_code:
328
                    observation = code.get("value")
329
330
            msg = f"Karlskrona just nu: {forecast} °C. {observation}."
331
332
        except Exception as e:
333
            LOG.error("Failed to get weather: %s", e)
334
            msg: str = getString("smhi", "failed")
335
336
    return msg
337
338
339 1
def marvinStrip(row):
340
    """
341
    Get a comic strip.
342
    """
343 1
    msg = None
344 1
    if any(r in row for r in ["strip", "comic", "nöje", "paus"]):
345 1
        msg = commitStrip(randomize=any(r in row for r in ["rand", "random", "slump", "lucky"]))
346 1
    return msg
347
348
349 1
def commitStrip(randomize=False):
350
    """
351
    Latest or random comic strip from CommitStrip.
352
    """
353 1
    msg = getString("commitstrip", "message")
354
355 1
    if randomize:
356 1
        first = getString("commitstrip", "first")
357 1
        last = getString("commitstrip", "last")
358 1
        rand = random.randint(first, last)
359 1
        url = getString("commitstrip", "urlPage") + str(rand)
360
    else:
361 1
        url = getString("commitstrip", "url")
362
363 1
    return msg.format(url=url)
364
365
366 1
def marvinTimeToBBQ(row):
367
    """
368
    Calcuate the time to next barbecue and print a appropriate msg
369
    """
370 1
    msg = None
371 1
    if any(r in row for r in ["grilla", "grill", "grillcon", "bbq"]):
372 1
        url = getString("barbecue", "url")
373 1
        nextDate = nextBBQ()
374 1
        today = datetime.date.today()
375 1
        daysRemaining = (nextDate - today).days
376
377 1
        if daysRemaining == 0:
378 1
            msg = getString("barbecue", "today")
379 1
        elif daysRemaining == 1:
380 1
            msg = getString("barbecue", "tomorrow")
381 1
        elif 1 < daysRemaining < 14:
382 1
            msg = getString("barbecue", "week") % nextDate
383 1
        elif 14 < daysRemaining < 200:
384 1
            msg = getString("barbecue", "base") % nextDate
385
        else:
386 1
            msg = getString("barbecue", "eternity") % nextDate
387
388 1
        msg = url + ". " + msg
389 1
    return msg
390
391 1
def nextBBQ():
392
    """
393
    Calculate the next grillcon date after today
394
    """
395
396 1
    MAY = 5
397 1
    SEPTEMBER = 9
398
399 1
    after = datetime.date.today()
400 1
    spring = thirdFridayIn(after.year, MAY)
401 1
    if after <= spring:
402 1
        return spring
403
404 1
    autumn = thirdFridayIn(after.year, SEPTEMBER)
405 1
    if after <= autumn:
406 1
        return autumn
407
408 1
    return thirdFridayIn(after.year + 1, MAY)
409
410
411 1
def thirdFridayIn(y, m):
412
    """
413
    Get the third Friday in a given month and year
414
    """
415 1
    THIRD = 2
416 1
    FRIDAY = -1
417
418
    # Start the weeks on saturday to prevent fridays from previous month
419 1
    cal = calendar.Calendar(firstweekday=calendar.SATURDAY)
420
421
    # Return the friday in the third week
422 1
    return cal.monthdatescalendar(y, m)[THIRD][FRIDAY]
423
424
425 1
def marvinBirthday(row):
426
    """
427
    Check birthday info
428
    """
429
    msg = None
430
    if any(r in row for r in ["birthday", "födelsedag"]):
431
        try:
432
            url = getString("birthday", "url")
433
            soup = BeautifulSoup(urlopen(url), "html.parser")
434
            my_list = list()
435
436
            for ana in soup.findAll('a'):
437
                if ana.parent.name == 'strong':
438
                    my_list.append(ana.getText())
439
440
            my_list.pop()
441
            my_strings = ', '.join(my_list)
442
            if not my_strings:
443
                msg = getString("birthday", "nobody")
444
            else:
445
                msg = getString("birthday", "somebody").format(my_strings)
446
447
        except Exception as e:
448
            LOG.error("Failed to get birthday: %s", e)
449
            msg = getString("birthday", "error")
450
451
    return msg
452
453 1
def marvinNameday(row):
454
    """
455
    Check current nameday
456
    """
457 1
    msg = None
458 1
    if any(r in row for r in ["nameday", "namnsdag"]):
459 1
        try:
460 1
            now = datetime.datetime.now()
461 1
            raw_url = "http://api.dryg.net/dagar/v2.1/{year}/{month}/{day}"
462 1
            url = raw_url.format(year=now.year, month=now.month, day=now.day)
463 1
            r = requests.get(url, timeout=5)
464 1
            nameday_data = r.json()
465 1
            names = nameday_data["dagar"][0]["namnsdag"]
466 1
            if names:
467 1
                msg = getString("nameday", "somebody").format(",".join(names))
468
            else:
469 1
                msg = getString("nameday", "nobody")
470 1
        except Exception as e:
471 1
            LOG.error("Failed to get nameday: %s", e)
472 1
            msg = getString("nameday", "error")
473 1
    return msg
474
475 1
def marvinUptime(row):
476
    """
477
    Display info about uptime tournament
478
    """
479 1
    msg = None
480 1
    if "uptime" in row:
481 1
        msg = getString("uptime", "info")
482 1
    return msg
483
484 1
def marvinStream(row):
485
    """
486
    Display info about stream
487
    """
488 1
    msg = None
489 1
    if any(r in row for r in ["stream", "streama", "ström", "strömma"]):
490 1
        msg = getString("stream", "info")
491 1
    return msg
492
493 1
def marvinPrinciple(row):
494
    """
495
    Display one selected software principle, or provide one as random
496
    """
497 1
    msg = None
498 1
    if any(r in row for r in ["principle", "princip", "principer"]):
499 1
        principles = getString("principle")
500 1
        principleKeys = list(principles.keys())
501 1
        matchedKeys = [k for k in row if k in principleKeys]
502 1
        if matchedKeys:
503 1
            msg = principles[matchedKeys.pop()]
504
        else:
505 1
            msg = principles[random.choice(principleKeys)]
506 1
    return msg
507
508 1
def getJoke():
509
    """
510
    Retrieves joke from api.chucknorris.io/jokes/random?category=dev
511
    """
512 1
    try:
513 1
        url = getString("joke", "url")
514 1
        r = requests.get(url, timeout=5)
515 1
        joke_data = r.json()
516 1
        return joke_data["value"]
517 1
    except Exception as e:
518 1
        LOG.error("Failed to get joke: %s", e)
519 1
        return getString("joke", "error")
520
521 1
def marvinJoke(row):
522
    """
523
    Display a random Chuck Norris joke
524
    """
525 1
    msg = None
526 1
    if any(r in row for r in ["joke", "skämt", "chuck norris", "chuck", "norris"]):
527 1
        msg = getJoke()
528 1
    return msg
529
530 1
def getCommit():
531
    """
532
    Retrieves random commit message from whatthecommit.com/index.html
533
    """
534 1
    try:
535 1
        url = getString("commit", "url")
536 1
        r = requests.get(url, timeout=5)
537 1
        res = r.text.strip()
538 1
        msg = f"Använd detta meddelandet: '{res}'"
539 1
        return msg
540 1
    except Exception as e:
541 1
        LOG.error("Failed to get commit message: %s", e)
542 1
        return getString("commit", "error")
543
544 1
def marvinCommit(row):
545
    """
546
    Display a random commit message
547
    """
548 1
    msg = None
549 1
    if any(r in row for r in ["commit", "-m"]):
550 1
        msg = getCommit()
551
    return msg
552