Passed
Pull Request — master (#73)
by
unknown
03:11
created

marvin_actions.marvinBirthday()   B

Complexity

Conditions 6

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 36.6126

Importance

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