marvin_actions.marvinNameday()   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 22
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 4

Importance

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