Test Failed
Pull Request — master (#79)
by
unknown
04:25
created

marvin_actions.marvinStats()   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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