diff --git a/Makefile b/Makefile index 3b944c0..8c8dde9 100644 --- a/Makefile +++ b/Makefile @@ -146,7 +146,6 @@ coverage: @rm -f build/coverage/* coverage run --source=. -m unittest discover -b coverage html --directory=build/coverage --omit=test_* - rsync -a --delete build/coverage/ docs/coverage/ coverage report -m --omit=test_* diff --git a/README.md b/README.md index 2e47330..2525cd4 100755 --- a/README.md +++ b/README.md @@ -50,8 +50,6 @@ make coverage A html report of the code coverage is generated into `build/coverage/index.html`. -[View the latest published code coverage report](https://mosbth.github.io/irc2phpbb/coverage/). - Execute marvin in docker diff --git a/docs/coverage/.gitignore b/docs/coverage/.gitignore deleted file mode 100644 index ccccf14..0000000 --- a/docs/coverage/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Created by coverage.py -* diff --git a/docs/coverage/bot_py.html b/docs/coverage/bot_py.html deleted file mode 100644 index 69af81d..0000000 --- a/docs/coverage/bot_py.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - Coverage for bot.py: 57% - - - - - -
-
-

- Coverage for bot.py: - 57% -

- -

- 23 statements   - - - -

-

- « prev     - ^ index     - » next -       - coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -

- -
-
-
-

1#! /usr/bin/env python3 

-

2# -*- coding: utf-8 -*- 

-

3 

-

4""" 

-

5Module for the common base class for all Bots 

-

6""" 

-

7 

-

8import re 

-

9 

-

10class Bot(): 

-

11 """Base class for things common between different protocols""" 

-

12 def __init__(self): 

-

13 self.CONFIG = {} 

-

14 self.ACTIONS = [] 

-

15 self.GENERAL_ACTIONS = [] 

-

16 

-

17 def getConfig(self): 

-

18 """Return the current configuration""" 

-

19 return self.CONFIG 

-

20 

-

21 def setConfig(self, config): 

-

22 """Set the current configuration""" 

-

23 self.CONFIG = config 

-

24 

-

25 def registerActions(self, actions): 

-

26 """Register actions to use""" 

-

27 print("Adding actions:") 

-

28 for action in actions: 

-

29 print(" - " + action.__name__) 

-

30 self.ACTIONS.extend(actions) 

-

31 

-

32 def registerGeneralActions(self, actions): 

-

33 """Register general actions to use""" 

-

34 print("Adding general actions:") 

-

35 for action in actions: 

-

36 print(" - " + action.__name__) 

-

37 self.GENERAL_ACTIONS.extend(actions) 

-

38 

-

39 @staticmethod 

-

40 def tokenize(message): 

-

41 """Split a message into normalized tokens""" 

-

42 return re.sub("[,.?:]", " ", message).strip().lower().split() 

-
- - - diff --git a/docs/coverage/class_index.html b/docs/coverage/class_index.html deleted file mode 100644 index 4b050a8..0000000 --- a/docs/coverage/class_index.html +++ /dev/null @@ -1,179 +0,0 @@ - - - - - Coverage report - - - - - -
-
-

Coverage report: - 62% -

- -
- -
- - -
-
-

- Files - Functions - Classes -

-

- coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Fileclassstatementsmissingexcludedcoverage
bot.pyBot1410029%
bot.py(no class)900100%
discord_bot.pyDiscordBot2015025%
discord_bot.py(no class)700100%
irc_bot.pyIrcBot11110704%
irc_bot.py(no class)2300100%
main.py(no class)7216078%
marvin_actions.py(no class)30364079%
marvin_general_actions.py(no class)3414059%
Total 593226062%
-

- No items found using the specified filter. -

-
- - - diff --git a/docs/coverage/coverage_html_cb_6fb7b396.js b/docs/coverage/coverage_html_cb_6fb7b396.js deleted file mode 100644 index 1face13..0000000 --- a/docs/coverage/coverage_html_cb_6fb7b396.js +++ /dev/null @@ -1,733 +0,0 @@ -// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt - -// Coverage.py HTML report browser code. -/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ -/*global coverage: true, document, window, $ */ - -coverage = {}; - -// General helpers -function debounce(callback, wait) { - let timeoutId = null; - return function(...args) { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => { - callback.apply(this, args); - }, wait); - }; -}; - -function checkVisible(element) { - const rect = element.getBoundingClientRect(); - const viewBottom = Math.max(document.documentElement.clientHeight, window.innerHeight); - const viewTop = 30; - return !(rect.bottom < viewTop || rect.top >= viewBottom); -} - -function on_click(sel, fn) { - const elt = document.querySelector(sel); - if (elt) { - elt.addEventListener("click", fn); - } -} - -// Helpers for table sorting -function getCellValue(row, column = 0) { - const cell = row.cells[column] // nosemgrep: eslint.detect-object-injection - if (cell.childElementCount == 1) { - var child = cell.firstElementChild; - if (child.tagName === "A") { - child = child.firstElementChild; - } - if (child instanceof HTMLDataElement && child.value) { - return child.value; - } - } - return cell.innerText || cell.textContent; -} - -function rowComparator(rowA, rowB, column = 0) { - let valueA = getCellValue(rowA, column); - let valueB = getCellValue(rowB, column); - if (!isNaN(valueA) && !isNaN(valueB)) { - return valueA - valueB; - } - return valueA.localeCompare(valueB, undefined, {numeric: true}); -} - -function sortColumn(th) { - // Get the current sorting direction of the selected header, - // clear state on other headers and then set the new sorting direction. - const currentSortOrder = th.getAttribute("aria-sort"); - [...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none")); - var direction; - if (currentSortOrder === "none") { - direction = th.dataset.defaultSortOrder || "ascending"; - } - else if (currentSortOrder === "ascending") { - direction = "descending"; - } - else { - direction = "ascending"; - } - th.setAttribute("aria-sort", direction); - - const column = [...th.parentElement.cells].indexOf(th) - - // Sort all rows and afterwards append them in order to move them in the DOM. - Array.from(th.closest("table").querySelectorAll("tbody tr")) - .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (direction === "ascending" ? 1 : -1)) - .forEach(tr => tr.parentElement.appendChild(tr)); - - // Save the sort order for next time. - if (th.id !== "region") { - let th_id = "file"; // Sort by file if we don't have a column id - let current_direction = direction; - const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); - if (stored_list) { - ({th_id, direction} = JSON.parse(stored_list)) - } - localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ - "th_id": th.id, - "direction": current_direction - })); - if (th.id !== th_id || document.getElementById("region")) { - // Sort column has changed, unset sorting by function or class. - localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ - "by_region": false, - "region_direction": current_direction - })); - } - } - else { - // Sort column has changed to by function or class, remember that. - localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ - "by_region": true, - "region_direction": direction - })); - } -} - -// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. -coverage.assign_shortkeys = function () { - document.querySelectorAll("[data-shortcut]").forEach(element => { - document.addEventListener("keypress", event => { - if (event.target.tagName.toLowerCase() === "input") { - return; // ignore keypress from search filter - } - if (event.key === element.dataset.shortcut) { - element.click(); - } - }); - }); -}; - -// Create the events for the filter box. -coverage.wire_up_filter = function () { - // Populate the filter and hide100 inputs if there are saved values for them. - const saved_filter_value = localStorage.getItem(coverage.FILTER_STORAGE); - if (saved_filter_value) { - document.getElementById("filter").value = saved_filter_value; - } - const saved_hide100_value = localStorage.getItem(coverage.HIDE100_STORAGE); - if (saved_hide100_value) { - document.getElementById("hide100").checked = JSON.parse(saved_hide100_value); - } - - // Cache elements. - const table = document.querySelector("table.index"); - const table_body_rows = table.querySelectorAll("tbody tr"); - const no_rows = document.getElementById("no_rows"); - - // Observe filter keyevents. - const filter_handler = (event => { - // Keep running total of each metric, first index contains number of shown rows - const totals = new Array(table.rows[0].cells.length).fill(0); - // Accumulate the percentage as fraction - totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep: eslint.detect-object-injection - - var text = document.getElementById("filter").value; - // Store filter value - localStorage.setItem(coverage.FILTER_STORAGE, text); - const casefold = (text === text.toLowerCase()); - const hide100 = document.getElementById("hide100").checked; - // Store hide value. - localStorage.setItem(coverage.HIDE100_STORAGE, JSON.stringify(hide100)); - - // Hide / show elements. - table_body_rows.forEach(row => { - var show = false; - // Check the text filter. - for (let column = 0; column < totals.length; column++) { - cell = row.cells[column]; - if (cell.classList.contains("name")) { - var celltext = cell.textContent; - if (casefold) { - celltext = celltext.toLowerCase(); - } - if (celltext.includes(text)) { - show = true; - } - } - } - - // Check the "hide covered" filter. - if (show && hide100) { - const [numer, denom] = row.cells[row.cells.length - 1].dataset.ratio.split(" "); - show = (numer !== denom); - } - - if (!show) { - // hide - row.classList.add("hidden"); - return; - } - - // show - row.classList.remove("hidden"); - totals[0]++; - - for (let column = 0; column < totals.length; column++) { - // Accumulate dynamic totals - cell = row.cells[column] // nosemgrep: eslint.detect-object-injection - if (cell.classList.contains("name")) { - continue; - } - if (column === totals.length - 1) { - // Last column contains percentage - const [numer, denom] = cell.dataset.ratio.split(" "); - totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection - totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection - } - else { - totals[column] += parseInt(cell.textContent, 10); // nosemgrep: eslint.detect-object-injection - } - } - }); - - // Show placeholder if no rows will be displayed. - if (!totals[0]) { - // Show placeholder, hide table. - no_rows.style.display = "block"; - table.style.display = "none"; - return; - } - - // Hide placeholder, show table. - no_rows.style.display = null; - table.style.display = null; - - const footer = table.tFoot.rows[0]; - // Calculate new dynamic sum values based on visible rows. - for (let column = 0; column < totals.length; column++) { - // Get footer cell element. - const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection - if (cell.classList.contains("name")) { - continue; - } - - // Set value into dynamic footer cell element. - if (column === totals.length - 1) { - // Percentage column uses the numerator and denominator, - // and adapts to the number of decimal places. - const match = /\.([0-9]+)/.exec(cell.textContent); - const places = match ? match[1].length : 0; - const { numer, denom } = totals[column]; // nosemgrep: eslint.detect-object-injection - cell.dataset.ratio = `${numer} ${denom}`; - // Check denom to prevent NaN if filtered files contain no statements - cell.textContent = denom - ? `${(numer * 100 / denom).toFixed(places)}%` - : `${(100).toFixed(places)}%`; - } - else { - cell.textContent = totals[column]; // nosemgrep: eslint.detect-object-injection - } - } - }); - - document.getElementById("filter").addEventListener("input", debounce(filter_handler)); - document.getElementById("hide100").addEventListener("input", debounce(filter_handler)); - - // Trigger change event on setup, to force filter on page refresh - // (filter value may still be present). - document.getElementById("filter").dispatchEvent(new Event("input")); - document.getElementById("hide100").dispatchEvent(new Event("input")); -}; -coverage.FILTER_STORAGE = "COVERAGE_FILTER_VALUE"; -coverage.HIDE100_STORAGE = "COVERAGE_HIDE100_VALUE"; - -// Set up the click-to-sort columns. -coverage.wire_up_sorting = function () { - document.querySelectorAll("[data-sortable] th[aria-sort]").forEach( - th => th.addEventListener("click", e => sortColumn(e.target)) - ); - - // Look for a localStorage item containing previous sort settings: - let th_id = "file", direction = "ascending"; - const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); - if (stored_list) { - ({th_id, direction} = JSON.parse(stored_list)); - } - let by_region = false, region_direction = "ascending"; - const sorted_by_region = localStorage.getItem(coverage.SORTED_BY_REGION); - if (sorted_by_region) { - ({ - by_region, - region_direction - } = JSON.parse(sorted_by_region)); - } - - const region_id = "region"; - if (by_region && document.getElementById(region_id)) { - direction = region_direction; - } - // If we are in a page that has a column with id of "region", sort on - // it if the last sort was by function or class. - let th; - if (document.getElementById(region_id)) { - th = document.getElementById(by_region ? region_id : th_id); - } - else { - th = document.getElementById(th_id); - } - th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); - th.click() -}; - -coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; -coverage.SORTED_BY_REGION = "COVERAGE_SORT_REGION"; - -// Loaded on index.html -coverage.index_ready = function () { - coverage.assign_shortkeys(); - coverage.wire_up_filter(); - coverage.wire_up_sorting(); - - on_click(".button_prev_file", coverage.to_prev_file); - on_click(".button_next_file", coverage.to_next_file); - - on_click(".button_show_hide_help", coverage.show_hide_help); -}; - -// -- pyfile stuff -- - -coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS"; - -coverage.pyfile_ready = function () { - // If we're directed to a particular line number, highlight the line. - var frag = location.hash; - if (frag.length > 2 && frag[1] === "t") { - document.querySelector(frag).closest(".n").classList.add("highlight"); - coverage.set_sel(parseInt(frag.substr(2), 10)); - } - else { - coverage.set_sel(0); - } - - on_click(".button_toggle_run", coverage.toggle_lines); - on_click(".button_toggle_mis", coverage.toggle_lines); - on_click(".button_toggle_exc", coverage.toggle_lines); - on_click(".button_toggle_par", coverage.toggle_lines); - - on_click(".button_next_chunk", coverage.to_next_chunk_nicely); - on_click(".button_prev_chunk", coverage.to_prev_chunk_nicely); - on_click(".button_top_of_page", coverage.to_top); - on_click(".button_first_chunk", coverage.to_first_chunk); - - on_click(".button_prev_file", coverage.to_prev_file); - on_click(".button_next_file", coverage.to_next_file); - on_click(".button_to_index", coverage.to_index); - - on_click(".button_show_hide_help", coverage.show_hide_help); - - coverage.filters = undefined; - try { - coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE); - } catch(err) {} - - if (coverage.filters) { - coverage.filters = JSON.parse(coverage.filters); - } - else { - coverage.filters = {run: false, exc: true, mis: true, par: true}; - } - - for (cls in coverage.filters) { - coverage.set_line_visibilty(cls, coverage.filters[cls]); // nosemgrep: eslint.detect-object-injection - } - - coverage.assign_shortkeys(); - coverage.init_scroll_markers(); - coverage.wire_up_sticky_header(); - - document.querySelectorAll("[id^=ctxs]").forEach( - cbox => cbox.addEventListener("click", coverage.expand_contexts) - ); - - // Rebuild scroll markers when the window height changes. - window.addEventListener("resize", coverage.build_scroll_markers); -}; - -coverage.toggle_lines = function (event) { - const btn = event.target.closest("button"); - const category = btn.value - const show = !btn.classList.contains("show_" + category); - coverage.set_line_visibilty(category, show); - coverage.build_scroll_markers(); - coverage.filters[category] = show; - try { - localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters)); - } catch(err) {} -}; - -coverage.set_line_visibilty = function (category, should_show) { - const cls = "show_" + category; - const btn = document.querySelector(".button_toggle_" + category); - if (btn) { - if (should_show) { - document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls)); - btn.classList.add(cls); - } - else { - document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls)); - btn.classList.remove(cls); - } - } -}; - -// Return the nth line div. -coverage.line_elt = function (n) { - return document.getElementById("t" + n)?.closest("p"); -}; - -// Set the selection. b and e are line numbers. -coverage.set_sel = function (b, e) { - // The first line selected. - coverage.sel_begin = b; - // The next line not selected. - coverage.sel_end = (e === undefined) ? b+1 : e; -}; - -coverage.to_top = function () { - coverage.set_sel(0, 1); - coverage.scroll_window(0); -}; - -coverage.to_first_chunk = function () { - coverage.set_sel(0, 1); - coverage.to_next_chunk(); -}; - -coverage.to_prev_file = function () { - window.location = document.getElementById("prevFileLink").href; -} - -coverage.to_next_file = function () { - window.location = document.getElementById("nextFileLink").href; -} - -coverage.to_index = function () { - location.href = document.getElementById("indexLink").href; -} - -coverage.show_hide_help = function () { - const helpCheck = document.getElementById("help_panel_state") - helpCheck.checked = !helpCheck.checked; -} - -// Return a string indicating what kind of chunk this line belongs to, -// or null if not a chunk. -coverage.chunk_indicator = function (line_elt) { - const classes = line_elt?.className; - if (!classes) { - return null; - } - const match = classes.match(/\bshow_\w+\b/); - if (!match) { - return null; - } - return match[0]; -}; - -coverage.to_next_chunk = function () { - const c = coverage; - - // Find the start of the next colored chunk. - var probe = c.sel_end; - var chunk_indicator, probe_line; - while (true) { - probe_line = c.line_elt(probe); - if (!probe_line) { - return; - } - chunk_indicator = c.chunk_indicator(probe_line); - if (chunk_indicator) { - break; - } - probe++; - } - - // There's a next chunk, `probe` points to it. - var begin = probe; - - // Find the end of this chunk. - var next_indicator = chunk_indicator; - while (next_indicator === chunk_indicator) { - probe++; - probe_line = c.line_elt(probe); - next_indicator = c.chunk_indicator(probe_line); - } - c.set_sel(begin, probe); - c.show_selection(); -}; - -coverage.to_prev_chunk = function () { - const c = coverage; - - // Find the end of the prev colored chunk. - var probe = c.sel_begin-1; - var probe_line = c.line_elt(probe); - if (!probe_line) { - return; - } - var chunk_indicator = c.chunk_indicator(probe_line); - while (probe > 1 && !chunk_indicator) { - probe--; - probe_line = c.line_elt(probe); - if (!probe_line) { - return; - } - chunk_indicator = c.chunk_indicator(probe_line); - } - - // There's a prev chunk, `probe` points to its last line. - var end = probe+1; - - // Find the beginning of this chunk. - var prev_indicator = chunk_indicator; - while (prev_indicator === chunk_indicator) { - probe--; - if (probe <= 0) { - return; - } - probe_line = c.line_elt(probe); - prev_indicator = c.chunk_indicator(probe_line); - } - c.set_sel(probe+1, end); - c.show_selection(); -}; - -// Returns 0, 1, or 2: how many of the two ends of the selection are on -// the screen right now? -coverage.selection_ends_on_screen = function () { - if (coverage.sel_begin === 0) { - return 0; - } - - const begin = coverage.line_elt(coverage.sel_begin); - const end = coverage.line_elt(coverage.sel_end-1); - - return ( - (checkVisible(begin) ? 1 : 0) - + (checkVisible(end) ? 1 : 0) - ); -}; - -coverage.to_next_chunk_nicely = function () { - if (coverage.selection_ends_on_screen() === 0) { - // The selection is entirely off the screen: - // Set the top line on the screen as selection. - - // This will select the top-left of the viewport - // As this is most likely the span with the line number we take the parent - const line = document.elementFromPoint(0, 0).parentElement; - if (line.parentElement !== document.getElementById("source")) { - // The element is not a source line but the header or similar - coverage.select_line_or_chunk(1); - } - else { - // We extract the line number from the id - coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); - } - } - coverage.to_next_chunk(); -}; - -coverage.to_prev_chunk_nicely = function () { - if (coverage.selection_ends_on_screen() === 0) { - // The selection is entirely off the screen: - // Set the lowest line on the screen as selection. - - // This will select the bottom-left of the viewport - // As this is most likely the span with the line number we take the parent - const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement; - if (line.parentElement !== document.getElementById("source")) { - // The element is not a source line but the header or similar - coverage.select_line_or_chunk(coverage.lines_len); - } - else { - // We extract the line number from the id - coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); - } - } - coverage.to_prev_chunk(); -}; - -// Select line number lineno, or if it is in a colored chunk, select the -// entire chunk -coverage.select_line_or_chunk = function (lineno) { - var c = coverage; - var probe_line = c.line_elt(lineno); - if (!probe_line) { - return; - } - var the_indicator = c.chunk_indicator(probe_line); - if (the_indicator) { - // The line is in a highlighted chunk. - // Search backward for the first line. - var probe = lineno; - var indicator = the_indicator; - while (probe > 0 && indicator === the_indicator) { - probe--; - probe_line = c.line_elt(probe); - if (!probe_line) { - break; - } - indicator = c.chunk_indicator(probe_line); - } - var begin = probe + 1; - - // Search forward for the last line. - probe = lineno; - indicator = the_indicator; - while (indicator === the_indicator) { - probe++; - probe_line = c.line_elt(probe); - indicator = c.chunk_indicator(probe_line); - } - - coverage.set_sel(begin, probe); - } - else { - coverage.set_sel(lineno); - } -}; - -coverage.show_selection = function () { - // Highlight the lines in the chunk - document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight")); - for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) { - coverage.line_elt(probe).querySelector(".n").classList.add("highlight"); - } - - coverage.scroll_to_selection(); -}; - -coverage.scroll_to_selection = function () { - // Scroll the page if the chunk isn't fully visible. - if (coverage.selection_ends_on_screen() < 2) { - const element = coverage.line_elt(coverage.sel_begin); - coverage.scroll_window(element.offsetTop - 60); - } -}; - -coverage.scroll_window = function (to_pos) { - window.scroll({top: to_pos, behavior: "smooth"}); -}; - -coverage.init_scroll_markers = function () { - // Init some variables - coverage.lines_len = document.querySelectorAll("#source > p").length; - - // Build html - coverage.build_scroll_markers(); -}; - -coverage.build_scroll_markers = function () { - const temp_scroll_marker = document.getElementById("scroll_marker") - if (temp_scroll_marker) temp_scroll_marker.remove(); - // Don't build markers if the window has no scroll bar. - if (document.body.scrollHeight <= window.innerHeight) { - return; - } - - const marker_scale = window.innerHeight / document.body.scrollHeight; - const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10); - - let previous_line = -99, last_mark, last_top; - - const scroll_marker = document.createElement("div"); - scroll_marker.id = "scroll_marker"; - document.getElementById("source").querySelectorAll( - "p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par" - ).forEach(element => { - const line_top = Math.floor(element.offsetTop * marker_scale); - const line_number = parseInt(element.querySelector(".n a").id.substr(1)); - - if (line_number === previous_line + 1) { - // If this solid missed block just make previous mark higher. - last_mark.style.height = `${line_top + line_height - last_top}px`; - } - else { - // Add colored line in scroll_marker block. - last_mark = document.createElement("div"); - last_mark.id = `m${line_number}`; - last_mark.classList.add("marker"); - last_mark.style.height = `${line_height}px`; - last_mark.style.top = `${line_top}px`; - scroll_marker.append(last_mark); - last_top = line_top; - } - - previous_line = line_number; - }); - - // Append last to prevent layout calculation - document.body.append(scroll_marker); -}; - -coverage.wire_up_sticky_header = function () { - const header = document.querySelector("header"); - const header_bottom = ( - header.querySelector(".content h2").getBoundingClientRect().top - - header.getBoundingClientRect().top - ); - - function updateHeader() { - if (window.scrollY > header_bottom) { - header.classList.add("sticky"); - } - else { - header.classList.remove("sticky"); - } - } - - window.addEventListener("scroll", updateHeader); - updateHeader(); -}; - -coverage.expand_contexts = function (e) { - var ctxs = e.target.parentNode.querySelector(".ctxs"); - - if (!ctxs.classList.contains("expanded")) { - var ctxs_text = ctxs.textContent; - var width = Number(ctxs_text[0]); - ctxs.textContent = ""; - for (var i = 1; i < ctxs_text.length; i += width) { - key = ctxs_text.substring(i, i + width).trim(); - ctxs.appendChild(document.createTextNode(contexts[key])); - ctxs.appendChild(document.createElement("br")); - } - ctxs.classList.add("expanded"); - } -}; - -document.addEventListener("DOMContentLoaded", () => { - if (document.body.classList.contains("indexfile")) { - coverage.index_ready(); - } - else { - coverage.pyfile_ready(); - } -}); diff --git a/docs/coverage/discord_bot_py.html b/docs/coverage/discord_bot_py.html deleted file mode 100644 index 5bca38c..0000000 --- a/docs/coverage/discord_bot_py.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - Coverage for discord_bot.py: 44% - - - - - -
-
-

- Coverage for discord_bot.py: - 44% -

- -

- 27 statements   - - - -

-

- « prev     - ^ index     - » next -       - coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -

- -
-
-
-

1#! /usr/bin/env python3 

-

2# -*- coding: utf-8 -*- 

-

3 

-

4""" 

-

5Module for the Discord bot. 

-

6 

-

7Connecting, sending and receiving messages and doing custom actions. 

-

8""" 

-

9 

-

10import discord 

-

11 

-

12from bot import Bot 

-

13 

-

14class DiscordBot(discord.Client, Bot): 

-

15 """Bot implementing the discord protocol""" 

-

16 def __init__(self): 

-

17 Bot.__init__(self) 

-

18 self.CONFIG = { 

-

19 "token": "" 

-

20 } 

-

21 intents = discord.Intents.default() 

-

22 intents.message_content = True 

-

23 discord.Client.__init__(self, intents=intents) 

-

24 

-

25 def begin(self): 

-

26 """Start the bot""" 

-

27 self.run(self.CONFIG.get("token")) 

-

28 

-

29 async def checkMarvinActions(self, message): 

-

30 """Check if Marvin should perform any actions""" 

-

31 words = self.tokenize(message.content) 

-

32 if self.user.name.lower() in words: 

-

33 for action in self.ACTIONS: 

-

34 response = action(words) 

-

35 if response: 

-

36 await message.channel.send(response) 

-

37 else: 

-

38 for action in self.GENERAL_ACTIONS: 

-

39 response = action(words) 

-

40 if response: 

-

41 await message.channel.send(response) 

-

42 

-

43 async def on_message(self, message): 

-

44 """Hook run on every message""" 

-

45 print(f"#{message.channel.name} <{message.author}> {message.content}") 

-

46 if message.author.name == self.user.name: 

-

47 # don't react to own messages 

-

48 return 

-

49 await self.checkMarvinActions(message) 

-
- - - diff --git a/docs/coverage/favicon_32_cb_58284776.png b/docs/coverage/favicon_32_cb_58284776.png deleted file mode 100644 index 8649f04..0000000 Binary files a/docs/coverage/favicon_32_cb_58284776.png and /dev/null differ diff --git a/docs/coverage/function_index.html b/docs/coverage/function_index.html deleted file mode 100644 index f8ad54f..0000000 --- a/docs/coverage/function_index.html +++ /dev/null @@ -1,699 +0,0 @@ - - - - - Coverage report - - - - - -
-
-

Coverage report: - 62% -

- -
- -
- - -
-
-

- Files - Functions - Classes -

-

- coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Filefunctionstatementsmissingexcludedcoverage
bot.pyBot.__init__300100%
bot.pyBot.getConfig1100%
bot.pyBot.setConfig1100%
bot.pyBot.registerActions4400%
bot.pyBot.registerGeneralActions4400%
bot.pyBot.tokenize100100%
bot.py(no function)900100%
discord_bot.pyDiscordBot.__init__500100%
discord_bot.pyDiscordBot.begin1100%
discord_bot.pyDiscordBot.checkMarvinActions101000%
discord_bot.pyDiscordBot.on_message4400%
discord_bot.py(no function)700100%
irc_bot.pyIrcBot.__init__400100%
irc_bot.pyIrcBot.connectToServer232300%
irc_bot.pyIrcBot.sendPrivMsg4400%
irc_bot.pyIrcBot.sendMsg2200%
irc_bot.pyIrcBot.decode_irc181800%
irc_bot.pyIrcBot.receive8800%
irc_bot.pyIrcBot.ircLogAppend5500%
irc_bot.pyIrcBot.ircLogWriteToFile2200%
irc_bot.pyIrcBot.readincoming121200%
irc_bot.pyIrcBot.mainLoop111100%
irc_bot.pyIrcBot.begin2200%
irc_bot.pyIrcBot.checkIrcActions4400%
irc_bot.pyIrcBot.checkMarvinActions161600%
irc_bot.py(no function)2300100%
main.pyprintVersion200100%
main.pymergeOptionsWithConfigFile91089%
main.pyparseOptions1700100%
main.pydetermineProtocol400100%
main.pycreateBot500100%
main.pymain141400%
main.py(no function)211095%
marvin_actions.pygetAllActions1100%
marvin_actions.pysetConfig1100%
marvin_actions.pygetString1200100%
marvin_actions.pymarvinSmile500100%
marvin_actions.pywordsAfterKeyWords700100%
marvin_actions.pymarvinGoogle800100%
marvin_actions.pymarvinExplainShell800100%
marvin_actions.pymarvinSource400100%
marvin_actions.pymarvinBudord132085%
marvin_actions.pymarvinQuote400100%
marvin_actions.pyvideoOfToday71086%
marvin_actions.pymarvinVideoOfToday500100%
marvin_actions.pymarvinWhoIs400100%
marvin_actions.pymarvinHelp400100%
marvin_actions.pymarvinStats400100%
marvin_actions.pymarvinIrcLog400100%
marvin_actions.pymarvinSayHi700100%
marvin_actions.pymarvinLunch800100%
marvin_actions.pymarvinListen161600%
marvin_actions.pymarvinSun111100%
marvin_actions.pymarvinWeather9900%
marvin_actions.pymarvinStrip400100%
marvin_actions.pycommitStrip800100%
marvin_actions.pymarvinTimeToBBQ1700100%
marvin_actions.pynextBBQ1000100%
marvin_actions.pythirdFridayIn400100%
marvin_actions.pymarvinBirthday171700%
marvin_actions.pymarvinNameday152087%
marvin_actions.pymarvinUptime400100%
marvin_actions.pymarvinStream400100%
marvin_actions.pymarvinPrinciple900100%
marvin_actions.pygetJoke72071%
marvin_actions.pymarvinJoke400100%
marvin_actions.pygetCommit72071%
marvin_actions.pymarvinCommit500100%
marvin_actions.py(no function)4600100%
marvin_general_actions.pysetConfig1100%
marvin_general_actions.pygetString121200%
marvin_general_actions.pygetAllGeneralActions1100%
marvin_general_actions.pymarvinMorning900100%
marvin_general_actions.py(no function)1100100%
Total 593226062%
-

- No items found using the specified filter. -

-
- - - diff --git a/docs/coverage/index.html b/docs/coverage/index.html deleted file mode 100644 index 6ba1b1a..0000000 --- a/docs/coverage/index.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - Coverage report - - - - - -
-
-

Coverage report: - 62% -

- -
- -
- - -
-
-

- Files - Functions - Classes -

-

- coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Filestatementsmissingexcludedcoverage
bot.py2310057%
discord_bot.py2715044%
irc_bot.py134107020%
main.py7216078%
marvin_actions.py30364079%
marvin_general_actions.py3414059%
Total593226062%
-

- No items found using the specified filter. -

-
- - - diff --git a/docs/coverage/irc_bot_py.html b/docs/coverage/irc_bot_py.html deleted file mode 100644 index 220f36c..0000000 --- a/docs/coverage/irc_bot_py.html +++ /dev/null @@ -1,337 +0,0 @@ - - - - - Coverage for irc_bot.py: 20% - - - - - -
-
-

- Coverage for irc_bot.py: - 20% -

- -

- 134 statements   - - - -

-

- « prev     - ^ index     - » next -       - coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -

- -
-
-
-

1#! /usr/bin/env python3 

-

2# -*- coding: utf-8 -*- 

-

3 

-

4""" 

-

5Module for the IRC bot. 

-

6 

-

7Connecting, sending and receiving messages and doing custom actions. 

-

8 

-

9Keeping a log and reading incoming material. 

-

10""" 

-

11from collections import deque 

-

12from datetime import datetime 

-

13import json 

-

14import os 

-

15import re 

-

16import shutil 

-

17import socket 

-

18 

-

19import chardet 

-

20 

-

21from bot import Bot 

-

22 

-

23class IrcBot(Bot): 

-

24 """Bot implementing the IRC protocol""" 

-

25 def __init__(self): 

-

26 super().__init__() 

-

27 self.CONFIG = { 

-

28 "server": None, 

-

29 "port": 6667, 

-

30 "channel": None, 

-

31 "nick": "marvin", 

-

32 "realname": "Marvin The All Mighty dbwebb-bot", 

-

33 "ident": None, 

-

34 "irclogfile": "irclog.txt", 

-

35 "irclogmax": 20, 

-

36 "dirIncoming": "incoming", 

-

37 "dirDone": "done", 

-

38 "lastfm": None, 

-

39 } 

-

40 

-

41 # Socket for IRC server 

-

42 self.SOCKET = None 

-

43 

-

44 # Keep a log of the latest messages 

-

45 self.IRCLOG = None 

-

46 

-

47 

-

48 def connectToServer(self): 

-

49 """Connect to the IRC Server""" 

-

50 

-

51 # Create the socket & Connect to the server 

-

52 server = self.CONFIG["server"] 

-

53 port = self.CONFIG["port"] 

-

54 

-

55 if server and port: 

-

56 self.SOCKET = socket.socket() 

-

57 print("Connecting: {SERVER}:{PORT}".format(SERVER=server, PORT=port)) 

-

58 self.SOCKET.connect((server, port)) 

-

59 else: 

-

60 print("Failed to connect, missing server or port in configuration.") 

-

61 return 

-

62 

-

63 # Send the nick to server 

-

64 nick = self.CONFIG["nick"] 

-

65 if nick: 

-

66 msg = 'NICK {NICK}\r\n'.format(NICK=nick) 

-

67 self.sendMsg(msg) 

-

68 else: 

-

69 print("Ignore sending nick, missing nick in configuration.") 

-

70 

-

71 # Present yourself 

-

72 realname = self.CONFIG["realname"] 

-

73 self.sendMsg('USER {NICK} 0 * :{REALNAME}\r\n'.format(NICK=nick, REALNAME=realname)) 

-

74 

-

75 # This is my nick, i promise! 

-

76 ident = self.CONFIG["ident"] 

-

77 if ident: 

-

78 self.sendMsg('PRIVMSG nick IDENTIFY {IDENT}\r\n'.format(IDENT=ident)) 

-

79 else: 

-

80 print("Ignore identifying with password, ident is not set.") 

-

81 

-

82 # Join a channel 

-

83 channel = self.CONFIG["channel"] 

-

84 if channel: 

-

85 self.sendMsg('JOIN {CHANNEL}\r\n'.format(CHANNEL=channel)) 

-

86 else: 

-

87 print("Ignore joining channel, missing channel name in configuration.") 

-

88 

-

89 def sendPrivMsg(self, message, channel): 

-

90 """Send and log a PRIV message""" 

-

91 if channel == self.CONFIG["channel"]: 

-

92 self.ircLogAppend(user=self.CONFIG["nick"].ljust(8), message=message) 

-

93 

-

94 msg = "PRIVMSG {CHANNEL} :{MSG}\r\n".format(CHANNEL=channel, MSG=message) 

-

95 self.sendMsg(msg) 

-

96 

-

97 def sendMsg(self, msg): 

-

98 """Send and occasionally print the message sent""" 

-

99 print("SEND: " + msg.rstrip('\r\n')) 

-

100 self.SOCKET.send(msg.encode()) 

-

101 

-

102 def decode_irc(self, raw, preferred_encs=None): 

-

103 """ 

-

104 Do character detection. 

-

105 You can send preferred encodings as a list through preferred_encs. 

-

106 http://stackoverflow.com/questions/938870/python-irc-bot-and-encoding-issue 

-

107 """ 

-

108 if preferred_encs is None: 

-

109 preferred_encs = ["UTF-8", "CP1252", "ISO-8859-1"] 

-

110 

-

111 changed = False 

-

112 enc = None 

-

113 for enc in preferred_encs: 

-

114 try: 

-

115 res = raw.decode(enc) 

-

116 changed = True 

-

117 break 

-

118 except Exception: 

-

119 pass 

-

120 

-

121 if not changed: 

-

122 try: 

-

123 enc = chardet.detect(raw)['encoding'] 

-

124 res = raw.decode(enc) 

-

125 except Exception: 

-

126 res = raw.decode(enc, 'ignore') 

-

127 

-

128 return res 

-

129 

-

130 def receive(self): 

-

131 """Read incoming message and guess encoding""" 

-

132 try: 

-

133 buf = self.SOCKET.recv(2048) 

-

134 lines = self.decode_irc(buf) 

-

135 lines = lines.split("\n") 

-

136 buf = lines.pop() 

-

137 except Exception as err: 

-

138 print("Error reading incoming message. " + err) 

-

139 

-

140 return lines 

-

141 

-

142 def ircLogAppend(self, line=None, user=None, message=None): 

-

143 """Read incoming message and guess encoding""" 

-

144 if not user: 

-

145 user = re.search(r"(?<=:)\w+", line[0]).group(0) 

-

146 

-

147 if not message: 

-

148 message = ' '.join(line[3:]).lstrip(':') 

-

149 

-

150 self.IRCLOG.append({ 

-

151 'time': datetime.now().strftime("%H:%M").rjust(5), 

-

152 'user': user, 

-

153 'msg': message 

-

154 }) 

-

155 

-

156 def ircLogWriteToFile(self): 

-

157 """Write IRClog to file""" 

-

158 with open(self.CONFIG["irclogfile"], 'w', encoding="UTF-8") as f: 

-

159 json.dump(list(self.IRCLOG), f, indent=2) 

-

160 

-

161 def readincoming(self): 

-

162 """ 

-

163 Read all files in the directory incoming, send them as a message if 

-

164 they exists and then move the file to directory done. 

-

165 """ 

-

166 if not os.path.isdir(self.CONFIG["dirIncoming"]): 

-

167 return 

-

168 

-

169 listing = os.listdir(self.CONFIG["dirIncoming"]) 

-

170 

-

171 for infile in listing: 

-

172 filename = os.path.join(self.CONFIG["dirIncoming"], infile) 

-

173 

-

174 with open(filename, "r", encoding="UTF-8") as f: 

-

175 for msg in f: 

-

176 self.sendPrivMsg(msg, self.CONFIG["channel"]) 

-

177 

-

178 try: 

-

179 shutil.move(filename, self.CONFIG["dirDone"]) 

-

180 except Exception: 

-

181 os.remove(filename) 

-

182 

-

183 def mainLoop(self): 

-

184 """For ever, listen and answer to incoming chats""" 

-

185 self.IRCLOG = deque([], self.CONFIG["irclogmax"]) 

-

186 

-

187 while 1: 

-

188 # Write irclog 

-

189 self.ircLogWriteToFile() 

-

190 

-

191 # Check in any in the incoming directory 

-

192 self.readincoming() 

-

193 

-

194 for line in self.receive(): 

-

195 print(line) 

-

196 words = line.strip().split() 

-

197 

-

198 if not words: 

-

199 continue 

-

200 

-

201 self.checkIrcActions(words) 

-

202 self.checkMarvinActions(words) 

-

203 

-

204 def begin(self): 

-

205 """Start the bot""" 

-

206 self.connectToServer() 

-

207 self.mainLoop() 

-

208 

-

209 def checkIrcActions(self, words): 

-

210 """ 

-

211 Check if Marvin should take action on any messages defined in the 

-

212 IRC protocol. 

-

213 """ 

-

214 if words[0] == "PING": 

-

215 self.sendMsg("PONG {ARG}\r\n".format(ARG=words[1])) 

-

216 

-

217 if words[1] == 'INVITE': 

-

218 self.sendMsg('JOIN {CHANNEL}\r\n'.format(CHANNEL=words[3])) 

-

219 

-

220 def checkMarvinActions(self, words): 

-

221 """Check if Marvin should perform any actions""" 

-

222 if words[1] == 'PRIVMSG' and words[2] == self.CONFIG["channel"]: 

-

223 self.ircLogAppend(words) 

-

224 

-

225 if words[1] == 'PRIVMSG': 

-

226 raw = ' '.join(words[3:]) 

-

227 row = self.tokenize(raw) 

-

228 

-

229 if self.CONFIG["nick"] in row: 

-

230 for action in self.ACTIONS: 

-

231 msg = action(row) 

-

232 if msg: 

-

233 self.sendPrivMsg(msg, words[2]) 

-

234 break 

-

235 else: 

-

236 for action in self.GENERAL_ACTIONS: 

-

237 msg = action(row) 

-

238 if msg: 

-

239 self.sendPrivMsg(msg, words[2]) 

-

240 break 

-
- - - diff --git a/docs/coverage/keybd_closed_cb_ce680311.png b/docs/coverage/keybd_closed_cb_ce680311.png deleted file mode 100644 index ba119c4..0000000 Binary files a/docs/coverage/keybd_closed_cb_ce680311.png and /dev/null differ diff --git a/docs/coverage/main_py.html b/docs/coverage/main_py.html deleted file mode 100644 index 12a7a21..0000000 --- a/docs/coverage/main_py.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - Coverage for main.py: 78% - - - - - -
-
-

- Coverage for main.py: - 78% -

- -

- 72 statements   - - - -

-

- « prev     - ^ index     - » next -       - coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -

- -
-
-
-

1#! /usr/bin/env python3 

-

2# -*- coding: utf-8 -*- 

-

3 

-

4""" 

-

5An IRC bot that answers random questions, keeps a log from the IRC-chat, 

-

6easy to integrate in a webpage and montores a phpBB forum for latest topics 

-

7by loggin in to the forum and checking the RSS-feed. 

-

8 

-

9You need to install additional modules. 

-

10 

-

11# Install needed modules in local directory 

-

12pip3 install --target modules/ feedparser beautifulsoup4 chardet 

-

13 

-

14Modules in modules/ will be loaded automatically. If you want to use a 

-

15different directory you can start the program like this instead: 

-

16 

-

17PYTHONPATH=modules python3 main.py 

-

18 

-

19# To get help 

-

20PYTHONPATH=modules python3 main.py --help 

-

21 

-

22# Example of using options 

-

23--server=irc.bsnet.se --channel=#db-o-webb 

-

24--server=irc.bsnet.se --port=6667 --channel=#db-o-webb 

-

25--nick=marvin --ident=secret 

-

26 

-

27# Configuration 

-

28Check out the file 'marvin_config_default.json' on how to configure, instead 

-

29of using cli-options. The default configfile is 'marvin_config.json' but you 

-

30can change that using cli-options. 

-

31 

-

32# Make own actions 

-

33Check the file 'marvin_strings.json' for the file where most of the strings 

-

34are defined and check out 'marvin_actions.py' to see how to write your own 

-

35actions. Its just a small function. 

-

36 

-

37# Read from incoming 

-

38Marvin reads messages from the incoming/ directory, if it exists, and writes 

-

39it out the the irc channel. 

-

40""" 

-

41 

-

42import argparse 

-

43import json 

-

44import os 

-

45import sys 

-

46 

-

47from discord_bot import DiscordBot 

-

48from irc_bot import IrcBot 

-

49 

-

50import marvin_actions 

-

51import marvin_general_actions 

-

52 

-

53# 

-

54# General stuff about this program 

-

55# 

-

56PROGRAM = "marvin" 

-

57AUTHOR = "Mikael Roos" 

-

58EMAIL = "mikael.t.h.roos@gmail.com" 

-

59VERSION = "0.3.0" 

-

60MSG_VERSION = "{program} version {version}.".format(program=PROGRAM, version=VERSION) 

-

61 

-

62 

-

63 

-

64def printVersion(): 

-

65 """ 

-

66 Print version information and exit. 

-

67 """ 

-

68 print(MSG_VERSION) 

-

69 sys.exit(0) 

-

70 

-

71 

-

72def mergeOptionsWithConfigFile(options, configFile): 

-

73 """ 

-

74 Read information from config file. 

-

75 """ 

-

76 if os.path.isfile(configFile): 

-

77 with open(configFile, encoding="UTF-8") as f: 

-

78 data = json.load(f) 

-

79 

-

80 options.update(data) 

-

81 res = json.dumps(options, sort_keys=True, indent=4, separators=(',', ': ')) 

-

82 

-

83 msg = "Read configuration from config file '{file}'. Current configuration is:\n{config}" 

-

84 print(msg.format(config=res, file=configFile)) 

-

85 

-

86 else: 

-

87 print("Config file '{file}' is not readable, skipping.".format(file=configFile)) 

-

88 

-

89 return options 

-

90 

-

91 

-

92def parseOptions(options): 

-

93 """ 

-

94 Merge default options with incoming options and arguments and return them as a dictionary. 

-

95 """ 

-

96 

-

97 parser = argparse.ArgumentParser() 

-

98 parser.add_argument("protocol", choices=["irc", "discord"], nargs="?", default="irc") 

-

99 parser.add_argument("-v", "--version", action="store_true") 

-

100 parser.add_argument("--config") 

-

101 

-

102 for key, value in options.items(): 

-

103 parser.add_argument(f"--{key}", type=type(value)) 

-

104 

-

105 args = vars(parser.parse_args()) 

-

106 if args["version"]: 

-

107 printVersion() 

-

108 if args["config"]: 

-

109 mergeOptionsWithConfigFile(options, args["config"]) 

-

110 

-

111 for parameter in options: 

-

112 if args[parameter]: 

-

113 options[parameter] = args[parameter] 

-

114 

-

115 res = json.dumps(options, sort_keys=True, indent=4, separators=(',', ': ')) 

-

116 print("Configuration updated after cli options:\n{config}".format(config=res)) 

-

117 

-

118 return options 

-

119 

-

120 

-

121def determineProtocol(): 

-

122 """Parse the argument to determine what protocol to use""" 

-

123 parser = argparse.ArgumentParser() 

-

124 parser.add_argument("protocol", choices=["irc", "discord"], nargs="?", default="irc") 

-

125 arg, _ = parser.parse_known_args() 

-

126 return arg.protocol 

-

127 

-

128 

-

129def createBot(protocol): 

-

130 """Return an instance of a bot with the requested implementation""" 

-

131 if protocol == "irc": 

-

132 return IrcBot() 

-

133 if protocol == "discord": 

-

134 return DiscordBot() 

-

135 raise ValueError(f"Unsupported protocol: {protocol}") 

-

136 

-

137 

-

138def main(): 

-

139 """ 

-

140 Main function to carry out the work. 

-

141 """ 

-

142 protocol = determineProtocol() 

-

143 bot = createBot(protocol) 

-

144 options = bot.getConfig() 

-

145 options.update(mergeOptionsWithConfigFile(options, "marvin_config.json")) 

-

146 config = parseOptions(options) 

-

147 bot.setConfig(config) 

-

148 marvin_actions.setConfig(options) 

-

149 marvin_general_actions.setConfig(options) 

-

150 actions = marvin_actions.getAllActions() 

-

151 general_actions = marvin_general_actions.getAllGeneralActions() 

-

152 bot.registerActions(actions) 

-

153 bot.registerGeneralActions(general_actions) 

-

154 bot.begin() 

-

155 

-

156 sys.exit(0) 

-

157 

-

158 

-

159if __name__ == "__main__": 

-

160 main() 

-
- - - diff --git a/docs/coverage/marvin_actions_py.html b/docs/coverage/marvin_actions_py.html deleted file mode 100644 index 861fb1b..0000000 --- a/docs/coverage/marvin_actions_py.html +++ /dev/null @@ -1,680 +0,0 @@ - - - - - Coverage for marvin_actions.py: 79% - - - - - -
-
-

- Coverage for marvin_actions.py: - 79% -

- -

- 303 statements   - - - -

-

- « prev     - ^ index     - » next -       - coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -

- -
-
-
-

1#! /usr/bin/env python3 

-

2# -*- coding: utf-8 -*- 

-

3 

-

4""" 

-

5Make actions for Marvin, one function for each action. 

-

6""" 

-

7from urllib.parse import quote_plus 

-

8from urllib.request import urlopen 

-

9import calendar 

-

10import datetime 

-

11import json 

-

12import random 

-

13import requests 

-

14 

-

15from bs4 import BeautifulSoup 

-

16 

-

17 

-

18def 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 marvinStats, 

-

33 marvinIrcLog, 

-

34 marvinListen, 

-

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 

-

52with open("marvin_strings.json", encoding="utf-8") as f: 

-

53 STRINGS = json.load(f) 

-

54 

-

55# Configuration loaded 

-

56CONFIG = None 

-

57 

-

58def setConfig(config): 

-

59 """ 

-

60 Keep reference to the loaded configuration. 

-

61 """ 

-

62 global CONFIG 

-

63 CONFIG = config 

-

64 

-

65 

-

66def getString(key, key1=None): 

-

67 """ 

-

68 Get a string from the string database. 

-

69 """ 

-

70 data = STRINGS[key] 

-

71 if isinstance(data, list): 

-

72 res = data[random.randint(0, len(data) - 1)] 

-

73 elif isinstance(data, dict): 

-

74 if key1 is None: 

-

75 res = data 

-

76 else: 

-

77 res = data[key1] 

-

78 if isinstance(res, list): 

-

79 res = res[random.randint(0, len(res) - 1)] 

-

80 elif isinstance(data, str): 

-

81 res = data 

-

82 

-

83 return res 

-

84 

-

85 

-

86def marvinSmile(row): 

-

87 """ 

-

88 Make Marvin smile. 

-

89 """ 

-

90 msg = None 

-

91 if any(r in row for r in ["smile", "le", "skratta", "smilies"]): 

-

92 smilie = getString("smile") 

-

93 msg = "{SMILE}".format(SMILE=smilie) 

-

94 return msg 

-

95 

-

96 

-

97def wordsAfterKeyWords(words, keyWords): 

-

98 """ 

-

99 Return all items in the words list after the first occurence 

-

100 of an item in the keyWords list. 

-

101 """ 

-

102 kwIndex = [] 

-

103 for kw in keyWords: 

-

104 if kw in words: 

-

105 kwIndex.append(words.index(kw)) 

-

106 

-

107 if not kwIndex: 

-

108 return None 

-

109 

-

110 return words[min(kwIndex)+1:] 

-

111 

-

112 

-

113def marvinGoogle(row): 

-

114 """ 

-

115 Let Marvin present an url to google. 

-

116 """ 

-

117 query = wordsAfterKeyWords(row, ["google", "googla"]) 

-

118 if not query: 

-

119 return None 

-

120 

-

121 searchStr = " ".join(query) 

-

122 url = "https://www.google.se/search?q=" 

-

123 url += quote_plus(searchStr) 

-

124 msg = getString("google") 

-

125 return msg.format(url) 

-

126 

-

127 

-

128def marvinExplainShell(row): 

-

129 """ 

-

130 Let Marvin present an url to the service explain shell to 

-

131 explain a shell command. 

-

132 """ 

-

133 query = wordsAfterKeyWords(row, ["explain", "förklara"]) 

-

134 if not query: 

-

135 return None 

-

136 cmd = " ".join(query) 

-

137 url = "http://explainshell.com/explain?cmd=" 

-

138 url += quote_plus(cmd, "/:") 

-

139 msg = getString("explainShell") 

-

140 return msg.format(url) 

-

141 

-

142 

-

143def marvinSource(row): 

-

144 """ 

-

145 State message about sourcecode. 

-

146 """ 

-

147 msg = None 

-

148 if any(r in row for r in ["källkod", "source"]): 

-

149 msg = getString("source") 

-

150 

-

151 return msg 

-

152 

-

153 

-

154def marvinBudord(row): 

-

155 """ 

-

156 What are the budord for Marvin? 

-

157 """ 

-

158 msg = None 

-

159 if any(r in row for r in ["budord", "stentavla"]): 

-

160 if any(r in row for r in ["#1", "1"]): 

-

161 msg = getString("budord", "#1") 

-

162 elif any(r in row for r in ["#2", "2"]): 

-

163 msg = getString("budord", "#2") 

-

164 elif any(r in row for r in ["#3", "3"]): 

-

165 msg = getString("budord", "#3") 

-

166 elif any(r in row for r in ["#4", "4"]): 

-

167 msg = getString("budord", "#4") 

-

168 elif any(r in row for r in ["#5", "5"]): 

-

169 msg = getString("budord", "#5") 

-

170 

-

171 return msg 

-

172 

-

173 

-

174def marvinQuote(row): 

-

175 """ 

-

176 Make a quote. 

-

177 """ 

-

178 msg = None 

-

179 if any(r in row for r in ["quote", "citat", "filosofi", "filosofera"]): 

-

180 msg = getString("hitchhiker") 

-

181 

-

182 return msg 

-

183 

-

184 

-

185def videoOfToday(): 

-

186 """ 

-

187 Check what day it is and provide a url to a suitable video together with a greeting. 

-

188 """ 

-

189 dayNum = datetime.date.weekday(datetime.date.today()) + 1 

-

190 msg = getString("weekdays", str(dayNum)) 

-

191 video = getString("video-of-today", str(dayNum)) 

-

192 

-

193 if video: 

-

194 msg += " En passande video är " + video 

-

195 else: 

-

196 msg += " Jag har ännu ingen passande video för denna dagen." 

-

197 

-

198 return msg 

-

199 

-

200 

-

201def marvinVideoOfToday(row): 

-

202 """ 

-

203 Show the video of today. 

-

204 """ 

-

205 msg = None 

-

206 if any(r in row for r in ["idag", "dagens"]): 

-

207 if any(r in row for r in ["video", "youtube", "tube"]): 

-

208 msg = videoOfToday() 

-

209 

-

210 return msg 

-

211 

-

212 

-

213def marvinWhoIs(row): 

-

214 """ 

-

215 Who is Marvin. 

-

216 """ 

-

217 msg = None 

-

218 if all(r in row for r in ["vem", "är"]): 

-

219 msg = getString("whois") 

-

220 

-

221 return msg 

-

222 

-

223 

-

224def marvinHelp(row): 

-

225 """ 

-

226 Provide a menu. 

-

227 """ 

-

228 msg = None 

-

229 if any(r in row for r in ["hjälp", "help", "menu", "meny"]): 

-

230 msg = getString("menu") 

-

231 

-

232 return msg 

-

233 

-

234 

-

235def marvinStats(row): 

-

236 """ 

-

237 Provide a link to the stats. 

-

238 """ 

-

239 msg = None 

-

240 if any(r in row for r in ["stats", "statistik", "ircstats"]): 

-

241 msg = getString("ircstats") 

-

242 

-

243 return msg 

-

244 

-

245 

-

246def marvinIrcLog(row): 

-

247 """ 

-

248 Provide a link to the irclog 

-

249 """ 

-

250 msg = None 

-

251 if any(r in row for r in ["irc", "irclog", "log", "irclogg", "logg", "historik"]): 

-

252 msg = getString("irclog") 

-

253 

-

254 return msg 

-

255 

-

256 

-

257def marvinSayHi(row): 

-

258 """ 

-

259 Say hi with a nice message. 

-

260 """ 

-

261 msg = None 

-

262 if any(r in row for r in [ 

-

263 "snälla", "hej", "tjena", "morsning", "morrn", "mår", "hallå", 

-

264 "halloj", "läget", "snäll", "duktig", "träna", "träning", 

-

265 "utbildning", "tack", "tacka", "tackar", "tacksam" 

-

266 ]): 

-

267 smile = getString("smile") 

-

268 hello = getString("hello") 

-

269 friendly = getString("friendly") 

-

270 msg = "{} {} {}".format(smile, hello, friendly) 

-

271 

-

272 return msg 

-

273 

-

274 

-

275def marvinLunch(row): 

-

276 """ 

-

277 Help decide where to eat. 

-

278 """ 

-

279 lunchOptions = { 

-

280 'stan centrum karlskrona kna': 'lunch-karlskrona', 

-

281 'ängelholm angelholm engelholm': 'lunch-angelholm', 

-

282 'hässleholm hassleholm': 'lunch-hassleholm', 

-

283 'malmö malmo malmoe': 'lunch-malmo', 

-

284 'göteborg goteborg gbg': 'lunch-goteborg' 

-

285 } 

-

286 

-

287 if any(r in row for r in ["lunch", "mat", "äta", "luncha"]): 

-

288 lunchStr = getString('lunch-message') 

-

289 

-

290 for keys, value in lunchOptions.items(): 

-

291 if any(r in row for r in keys.split(" ")): 

-

292 return lunchStr.format(getString(value)) 

-

293 

-

294 return lunchStr.format(getString('lunch-bth')) 

-

295 

-

296 return None 

-

297 

-

298 

-

299def marvinListen(row): 

-

300 """ 

-

301 Return music last listened to. 

-

302 """ 

-

303 msg = None 

-

304 if any(r in row for r in ["lyssna", "lyssnar", "musik"]): 

-

305 

-

306 if not CONFIG["lastfm"]: 

-

307 return getString("listen", "disabled") 

-

308 

-

309 url = "http://ws.audioscrobbler.com/2.0/" 

-

310 

-

311 try: 

-

312 params = dict( 

-

313 method="user.getrecenttracks", 

-

314 user=CONFIG["lastfm"]["user"], 

-

315 api_key=CONFIG["lastfm"]["apikey"], 

-

316 format="json", 

-

317 limit="1" 

-

318 ) 

-

319 

-

320 resp = requests.get(url=url, params=params, timeout=5) 

-

321 data = json.loads(resp.text) 

-

322 

-

323 artist = data["recenttracks"]["track"][0]["artist"]["#text"] 

-

324 title = data["recenttracks"]["track"][0]["name"] 

-

325 link = data["recenttracks"]["track"][0]["url"] 

-

326 

-

327 msg = getString("listen", "success").format(artist=artist, title=title, link=link) 

-

328 

-

329 except Exception: 

-

330 msg = getString("listen", "failed") 

-

331 

-

332 return msg 

-

333 

-

334 

-

335def marvinSun(row): 

-

336 """ 

-

337 Check when the sun goes up and down. 

-

338 """ 

-

339 msg = None 

-

340 if any(r in row for r in ["sol", "solen", "solnedgång", "soluppgång"]): 

-

341 try: 

-

342 soup = BeautifulSoup(urlopen('http://www.timeanddate.com/sun/sweden/jonkoping')) 

-

343 spans = soup.find_all("span", {"class": "three"}) 

-

344 sunrise = spans[0].text 

-

345 sunset = spans[1].text 

-

346 msg = getString("sun").format(sunrise, sunset) 

-

347 

-

348 except Exception: 

-

349 msg = getString("sun-no") 

-

350 

-

351 return msg 

-

352 

-

353 

-

354def marvinWeather(row): 

-

355 """ 

-

356 Check what the weather prognosis looks like. 

-

357 """ 

-

358 msg = None 

-

359 if any(r in row for r in ["väder", "vädret", "prognos", "prognosen", "smhi"]): 

-

360 url = getString("smhi", "url") 

-

361 try: 

-

362 soup = BeautifulSoup(urlopen(url)) 

-

363 msg = "{}. {}. {}".format( 

-

364 soup.h1.text, 

-

365 soup.h4.text, 

-

366 soup.h4.findNextSibling("p").text 

-

367 ) 

-

368 

-

369 except Exception: 

-

370 msg = getString("smhi", "failed") 

-

371 

-

372 return msg 

-

373 

-

374 

-

375def marvinStrip(row): 

-

376 """ 

-

377 Get a comic strip. 

-

378 """ 

-

379 msg = None 

-

380 if any(r in row for r in ["strip", "comic", "nöje", "paus"]): 

-

381 msg = commitStrip(randomize=any(r in row for r in ["rand", "random", "slump", "lucky"])) 

-

382 return msg 

-

383 

-

384 

-

385def commitStrip(randomize=False): 

-

386 """ 

-

387 Latest or random comic strip from CommitStrip. 

-

388 """ 

-

389 msg = getString("commitstrip", "message") 

-

390 

-

391 if randomize: 

-

392 first = getString("commitstrip", "first") 

-

393 last = getString("commitstrip", "last") 

-

394 rand = random.randint(first, last) 

-

395 url = getString("commitstrip", "urlPage") + str(rand) 

-

396 else: 

-

397 url = getString("commitstrip", "url") 

-

398 

-

399 return msg.format(url=url) 

-

400 

-

401 

-

402def marvinTimeToBBQ(row): 

-

403 """ 

-

404 Calcuate the time to next barbecue and print a appropriate msg 

-

405 """ 

-

406 msg = None 

-

407 if any(r in row for r in ["grilla", "grill", "grillcon", "bbq"]): 

-

408 url = getString("barbecue", "url") 

-

409 nextDate = nextBBQ() 

-

410 today = datetime.date.today() 

-

411 daysRemaining = (nextDate - today).days 

-

412 

-

413 if daysRemaining == 0: 

-

414 msg = getString("barbecue", "today") 

-

415 elif daysRemaining == 1: 

-

416 msg = getString("barbecue", "tomorrow") 

-

417 elif 1 < daysRemaining < 14: 

-

418 msg = getString("barbecue", "week") % nextDate 

-

419 elif 14 < daysRemaining < 200: 

-

420 msg = getString("barbecue", "base") % nextDate 

-

421 else: 

-

422 msg = getString("barbecue", "eternity") % nextDate 

-

423 

-

424 msg = url + ". " + msg 

-

425 return msg 

-

426 

-

427def nextBBQ(): 

-

428 """ 

-

429 Calculate the next grillcon date after today 

-

430 """ 

-

431 

-

432 MAY = 5 

-

433 SEPTEMBER = 9 

-

434 

-

435 after = datetime.date.today() 

-

436 spring = thirdFridayIn(after.year, MAY) 

-

437 if after <= spring: 

-

438 return spring 

-

439 

-

440 autumn = thirdFridayIn(after.year, SEPTEMBER) 

-

441 if after <= autumn: 

-

442 return autumn 

-

443 

-

444 return thirdFridayIn(after.year + 1, MAY) 

-

445 

-

446 

-

447def thirdFridayIn(y, m): 

-

448 """ 

-

449 Get the third Friday in a given month and year 

-

450 """ 

-

451 THIRD = 2 

-

452 FRIDAY = -1 

-

453 

-

454 # Start the weeks on saturday to prevent fridays from previous month 

-

455 cal = calendar.Calendar(firstweekday=calendar.SATURDAY) 

-

456 

-

457 # Return the friday in the third week 

-

458 return cal.monthdatescalendar(y, m)[THIRD][FRIDAY] 

-

459 

-

460 

-

461def marvinBirthday(row): 

-

462 """ 

-

463 Check birthday info 

-

464 """ 

-

465 msg = None 

-

466 if any(r in row for r in ["birthday", "födelsedag"]): 

-

467 try: 

-

468 url = getString("birthday", "url") 

-

469 soup = BeautifulSoup(urlopen(url), "html.parser") 

-

470 my_list = list() 

-

471 

-

472 for ana in soup.findAll('a'): 

-

473 if ana.parent.name == 'strong': 

-

474 my_list.append(ana.getText()) 

-

475 

-

476 my_list.pop() 

-

477 my_strings = ', '.join(my_list) 

-

478 if not my_strings: 

-

479 msg = getString("birthday", "nobody") 

-

480 else: 

-

481 msg = getString("birthday", "somebody").format(my_strings) 

-

482 

-

483 except Exception: 

-

484 msg = getString("birthday", "error") 

-

485 

-

486 return msg 

-

487 

-

488def marvinNameday(row): 

-

489 """ 

-

490 Check current nameday 

-

491 """ 

-

492 msg = None 

-

493 if any(r in row for r in ["nameday", "namnsdag"]): 

-

494 try: 

-

495 now = datetime.datetime.now() 

-

496 raw_url = "http://api.dryg.net/dagar/v2.1/{year}/{month}/{day}" 

-

497 url = raw_url.format(year=now.year, month=now.month, day=now.day) 

-

498 r = requests.get(url, timeout=5) 

-

499 nameday_data = r.json() 

-

500 names = nameday_data["dagar"][0]["namnsdag"] 

-

501 if names: 

-

502 msg = getString("nameday", "somebody").format(",".join(names)) 

-

503 else: 

-

504 msg = getString("nameday", "nobody") 

-

505 except Exception: 

-

506 msg = getString("nameday", "error") 

-

507 return msg 

-

508 

-

509def marvinUptime(row): 

-

510 """ 

-

511 Display info about uptime tournament 

-

512 """ 

-

513 msg = None 

-

514 if "uptime" in row: 

-

515 msg = getString("uptime", "info") 

-

516 return msg 

-

517 

-

518def marvinStream(row): 

-

519 """ 

-

520 Display info about stream 

-

521 """ 

-

522 msg = None 

-

523 if any(r in row for r in ["stream", "streama", "ström", "strömma"]): 

-

524 msg = getString("stream", "info") 

-

525 return msg 

-

526 

-

527def marvinPrinciple(row): 

-

528 """ 

-

529 Display one selected software principle, or provide one as random 

-

530 """ 

-

531 msg = None 

-

532 if any(r in row for r in ["principle", "princip", "principer"]): 

-

533 principles = getString("principle") 

-

534 principleKeys = list(principles.keys()) 

-

535 matchedKeys = [k for k in row if k in principleKeys] 

-

536 if matchedKeys: 

-

537 msg = principles[matchedKeys.pop()] 

-

538 else: 

-

539 msg = principles[random.choice(principleKeys)] 

-

540 return msg 

-

541 

-

542def getJoke(): 

-

543 """ 

-

544 Retrieves joke from api.chucknorris.io/jokes/random?category=dev 

-

545 """ 

-

546 try: 

-

547 url = getString("joke", "url") 

-

548 r = requests.get(url, timeout=5) 

-

549 joke_data = r.json() 

-

550 return joke_data["value"] 

-

551 except Exception: 

-

552 return getString("joke", "error") 

-

553 

-

554def marvinJoke(row): 

-

555 """ 

-

556 Display a random Chuck Norris joke 

-

557 """ 

-

558 msg = None 

-

559 if any(r in row for r in ["joke", "skämt", "chuck norris", "chuck", "norris"]): 

-

560 msg = getJoke() 

-

561 return msg 

-

562 

-

563def getCommit(): 

-

564 """ 

-

565 Retrieves random commit message from whatthecommit.com/index.html 

-

566 """ 

-

567 try: 

-

568 url = getString("commit", "url") 

-

569 r = requests.get(url, timeout=5) 

-

570 res = r.text.strip() 

-

571 return res 

-

572 except Exception: 

-

573 return getString("commit", "error") 

-

574 

-

575def marvinCommit(row): 

-

576 """ 

-

577 Display a random commit message 

-

578 """ 

-

579 msg = None 

-

580 if any(r in row for r in ["commit", "-m"]): 

-

581 commitMsg = getCommit() 

-

582 msg = "Använd detta meddelandet: '{}'".format(commitMsg) 

-

583 return msg 

-
- - - diff --git a/docs/coverage/marvin_general_actions_py.html b/docs/coverage/marvin_general_actions_py.html deleted file mode 100644 index 5108519..0000000 --- a/docs/coverage/marvin_general_actions_py.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - Coverage for marvin_general_actions.py: 59% - - - - - -
-
-

- Coverage for marvin_general_actions.py: - 59% -

- -

- 34 statements   - - - -

-

- « prev     - ^ index     - » next -       - coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -

- -
-
-
-

1#! /usr/bin/env python3 

-

2# -*- coding: utf-8 -*- 

-

3 

-

4""" 

-

5Make general actions for Marvin, one function for each action. 

-

6""" 

-

7import datetime 

-

8import json 

-

9import random 

-

10 

-

11# Load all strings from file 

-

12with open("marvin_strings.json", encoding="utf-8") as f: 

-

13 STRINGS = json.load(f) 

-

14 

-

15# Configuration loaded 

-

16CONFIG = None 

-

17 

-

18lastDateGreeted = None 

-

19 

-

20def setConfig(config): 

-

21 """ 

-

22 Keep reference to the loaded configuration. 

-

23 """ 

-

24 global CONFIG 

-

25 CONFIG = config 

-

26 

-

27 

-

28def getString(key, key1=None): 

-

29 """ 

-

30 Get a string from the string database. 

-

31 """ 

-

32 data = STRINGS[key] 

-

33 if isinstance(data, list): 

-

34 res = data[random.randint(0, len(data) - 1)] 

-

35 elif isinstance(data, dict): 

-

36 if key1 is None: 

-

37 res = data 

-

38 else: 

-

39 res = data[key1] 

-

40 if isinstance(res, list): 

-

41 res = res[random.randint(0, len(res) - 1)] 

-

42 elif isinstance(data, str): 

-

43 res = data 

-

44 

-

45 return res 

-

46 

-

47 

-

48def getAllGeneralActions(): 

-

49 """ 

-

50 Return all general actions as an array. 

-

51 """ 

-

52 return [ 

-

53 marvinMorning 

-

54 ] 

-

55 

-

56 

-

57def marvinMorning(row): 

-

58 """ 

-

59 Marvin says Good morning after someone else says it 

-

60 """ 

-

61 msg = None 

-

62 phrases = [ 

-

63 "morgon", 

-

64 "godmorgon", 

-

65 "god morgon", 

-

66 "morrn", 

-

67 "morn" 

-

68 ] 

-

69 

-

70 morning_phrases = [ 

-

71 "Godmorgon! :-)", 

-

72 "Morgon allesammans", 

-

73 "Morgon gott folk", 

-

74 "Guten morgen", 

-

75 "Morgon" 

-

76 ] 

-

77 

-

78 global lastDateGreeted 

-

79 

-

80 for phrase in phrases: 

-

81 if phrase in row: 

-

82 if lastDateGreeted != datetime.date.today(): 

-

83 lastDateGreeted = datetime.date.today() 

-

84 msg = random.choice(morning_phrases) 

-

85 return msg 

-
- - - diff --git a/docs/coverage/status.json b/docs/coverage/status.json deleted file mode 100644 index a69ef1f..0000000 --- a/docs/coverage/status.json +++ /dev/null @@ -1 +0,0 @@ -{"note":"This file is an internal implementation detail to speed up HTML report generation. Its format can change at any time. You might be looking for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json","format":5,"version":"7.6.1","globals":"1e62a01dda053cff30dd2530301c9113","files":{"bot_py":{"hash":"de1793d856528b4b69010c66bb5c84f5","index":{"url":"bot_py.html","file":"bot.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":23,"n_excluded":0,"n_missing":10,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"discord_bot_py":{"hash":"0280e7c30f5611eaa0d40ca5d04aa07e","index":{"url":"discord_bot_py.html","file":"discord_bot.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":27,"n_excluded":0,"n_missing":15,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"irc_bot_py":{"hash":"e186f890efd59a347ab94b5d0ae6ad2a","index":{"url":"irc_bot_py.html","file":"irc_bot.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":134,"n_excluded":0,"n_missing":107,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"main_py":{"hash":"2f670876222fa515a487f42eb4c15975","index":{"url":"main_py.html","file":"main.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":72,"n_excluded":0,"n_missing":16,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"marvin_actions_py":{"hash":"65ee00e03781271d0ff7ffab82c01ef7","index":{"url":"marvin_actions_py.html","file":"marvin_actions.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":303,"n_excluded":0,"n_missing":64,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"marvin_general_actions_py":{"hash":"954d2659a99ed486330dd4da038fead4","index":{"url":"marvin_general_actions_py.html","file":"marvin_general_actions.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":34,"n_excluded":0,"n_missing":14,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}}}} \ No newline at end of file diff --git a/docs/coverage/style_cb_8e611ae1.css b/docs/coverage/style_cb_8e611ae1.css deleted file mode 100644 index 3cdaf05..0000000 --- a/docs/coverage/style_cb_8e611ae1.css +++ /dev/null @@ -1,337 +0,0 @@ -@charset "UTF-8"; -/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ -/* Don't edit this .css file. Edit the .scss file instead! */ -html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } - -body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; } - -@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } } - -@media (prefers-color-scheme: dark) { body { color: #eee; } } - -html > body { font-size: 16px; } - -a:active, a:focus { outline: 2px dashed #007acc; } - -p { font-size: .875em; line-height: 1.4em; } - -table { border-collapse: collapse; } - -td { vertical-align: top; } - -table tr.hidden { display: none !important; } - -p#no_rows { display: none; font-size: 1.15em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } - -a.nav { text-decoration: none; color: inherit; } - -a.nav:hover { text-decoration: underline; color: inherit; } - -.hidden { display: none; } - -header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; } - -@media (prefers-color-scheme: dark) { header { background: black; } } - -@media (prefers-color-scheme: dark) { header { border-color: #333; } } - -header .content { padding: 1rem 3.5rem; } - -header h2 { margin-top: .5em; font-size: 1em; } - -header h2 a.button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } - -@media (prefers-color-scheme: dark) { header h2 a.button { background: #333; } } - -@media (prefers-color-scheme: dark) { header h2 a.button { border-color: #444; } } - -header h2 a.button.current { border: 2px solid; background: #fff; border-color: #999; cursor: default; } - -@media (prefers-color-scheme: dark) { header h2 a.button.current { background: #1e1e1e; } } - -@media (prefers-color-scheme: dark) { header h2 a.button.current { border-color: #777; } } - -header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; } - -@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } } - -header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; } - -header.sticky .text { display: none; } - -header.sticky h1, header.sticky h2 { font-size: 1em; margin-top: 0; display: inline-block; } - -header.sticky .content { padding: 0.5rem 3.5rem; } - -header.sticky .content p { font-size: 1em; } - -header.sticky ~ #source { padding-top: 6.5em; } - -main { position: relative; z-index: 1; } - -footer { margin: 1rem 3.5rem; } - -footer .content { padding: 0; color: #666; font-style: italic; } - -@media (prefers-color-scheme: dark) { footer .content { color: #aaa; } } - -#index { margin: 1rem 0 0 3.5rem; } - -h1 { font-size: 1.25em; display: inline-block; } - -#filter_container { float: right; margin: 0 2em 0 0; line-height: 1.66em; } - -#filter_container #filter { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } - -@media (prefers-color-scheme: dark) { #filter_container #filter { border-color: #444; } } - -@media (prefers-color-scheme: dark) { #filter_container #filter { background: #1e1e1e; } } - -@media (prefers-color-scheme: dark) { #filter_container #filter { color: #eee; } } - -#filter_container #filter:focus { border-color: #007acc; } - -#filter_container :disabled ~ label { color: #ccc; } - -@media (prefers-color-scheme: dark) { #filter_container :disabled ~ label { color: #444; } } - -#filter_container label { font-size: .875em; color: #666; } - -@media (prefers-color-scheme: dark) { #filter_container label { color: #aaa; } } - -header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } - -@media (prefers-color-scheme: dark) { header button { background: #333; } } - -@media (prefers-color-scheme: dark) { header button { border-color: #444; } } - -header button:active, header button:focus { outline: 2px dashed #007acc; } - -header button.run { background: #eeffee; } - -@media (prefers-color-scheme: dark) { header button.run { background: #373d29; } } - -header button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; } - -@media (prefers-color-scheme: dark) { header button.run.show_run { background: #373d29; } } - -header button.mis { background: #ffeeee; } - -@media (prefers-color-scheme: dark) { header button.mis { background: #4b1818; } } - -header button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; } - -@media (prefers-color-scheme: dark) { header button.mis.show_mis { background: #4b1818; } } - -header button.exc { background: #f7f7f7; } - -@media (prefers-color-scheme: dark) { header button.exc { background: #333; } } - -header button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; } - -@media (prefers-color-scheme: dark) { header button.exc.show_exc { background: #333; } } - -header button.par { background: #ffffd5; } - -@media (prefers-color-scheme: dark) { header button.par { background: #650; } } - -header button.par.show_par { background: #ffa; border: 2px solid #bbbb00; margin: 0 .1em; } - -@media (prefers-color-scheme: dark) { header button.par.show_par { background: #650; } } - -#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; } - -#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; } - -#help_panel_wrapper { float: right; position: relative; } - -#keyboard_icon { margin: 5px; } - -#help_panel_state { display: none; } - -#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; } - -#help_panel .keyhelp p { margin-top: .75em; } - -#help_panel .legend { font-style: italic; margin-bottom: 1em; } - -.indexfile #help_panel { width: 25em; } - -.pyfile #help_panel { width: 18em; } - -#help_panel_state:checked ~ #help_panel { display: block; } - -kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; } - -#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; } - -#source p { position: relative; white-space: pre; } - -#source p * { box-sizing: border-box; } - -#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; user-select: none; } - -@media (prefers-color-scheme: dark) { #source p .n { color: #777; } } - -#source p .n.highlight { background: #ffdd00; } - -#source p .n a { scroll-margin-top: 6em; text-decoration: none; color: #999; } - -@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } } - -#source p .n a:hover { text-decoration: underline; color: #999; } - -@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } } - -#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; } - -@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } } - -#source p .t:hover { background: #f2f2f2; } - -@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } } - -#source p .t:hover ~ .r .annotate.long { display: block; } - -#source p .t .com { color: #008000; font-style: italic; line-height: 1px; } - -@media (prefers-color-scheme: dark) { #source p .t .com { color: #6a9955; } } - -#source p .t .key { font-weight: bold; line-height: 1px; } - -#source p .t .str { color: #0451a5; } - -@media (prefers-color-scheme: dark) { #source p .t .str { color: #9cdcfe; } } - -#source p.mis .t { border-left: 0.2em solid #ff0000; } - -#source p.mis.show_mis .t { background: #fdd; } - -@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } } - -#source p.mis.show_mis .t:hover { background: #f2d2d2; } - -@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } } - -#source p.run .t { border-left: 0.2em solid #00dd00; } - -#source p.run.show_run .t { background: #dfd; } - -@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } } - -#source p.run.show_run .t:hover { background: #d2f2d2; } - -@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } } - -#source p.exc .t { border-left: 0.2em solid #808080; } - -#source p.exc.show_exc .t { background: #eee; } - -@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } } - -#source p.exc.show_exc .t:hover { background: #e2e2e2; } - -@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } } - -#source p.par .t { border-left: 0.2em solid #bbbb00; } - -#source p.par.show_par .t { background: #ffa; } - -@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } } - -#source p.par.show_par .t:hover { background: #f2f2a2; } - -@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } } - -#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } - -#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; } - -@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } } - -#source p .annotate.short:hover ~ .long { display: block; } - -#source p .annotate.long { width: 30em; right: 2.5em; } - -#source p input { display: none; } - -#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; } - -#source p input ~ .r label.ctx::before { content: "▶ "; } - -#source p input ~ .r label.ctx:hover { background: #e8f4ff; color: #666; } - -@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } } - -@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } } - -#source p input:checked ~ .r label.ctx { background: #d0e8ff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; } - -@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } } - -@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } } - -#source p input:checked ~ .r label.ctx::before { content: "▼ "; } - -#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; } - -#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; } - -@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } } - -#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #d0e8ff; border-radius: .25em; margin-right: 1.75em; text-align: right; } - -@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } } - -#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; } - -#index table.index { margin-left: -.5em; } - -#index td, #index th { text-align: right; padding: .25em .5em; border-bottom: 1px solid #eee; } - -@media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } } - -#index td.name, #index th.name { text-align: left; width: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; min-width: 15em; } - -#index th { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-style: italic; color: #333; cursor: pointer; } - -@media (prefers-color-scheme: dark) { #index th { color: #ddd; } } - -#index th:hover { background: #eee; } - -@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } } - -#index th .arrows { color: #666; font-size: 85%; font-family: sans-serif; font-style: normal; pointer-events: none; } - -#index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; } - -@media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } } - -#index th[aria-sort="ascending"] .arrows::after { content: " ▲"; } - -#index th[aria-sort="descending"] .arrows::after { content: " ▼"; } - -#index td.name { font-size: 1.15em; } - -#index td.name a { text-decoration: none; color: inherit; } - -#index td.name .no-noun { font-style: italic; } - -#index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; } - -#index tr.region:hover { background: #eee; } - -@media (prefers-color-scheme: dark) { #index tr.region:hover { background: #333; } } - -#index tr.region:hover td.name { text-decoration: underline; color: inherit; } - -#scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; } - -@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } } - -@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } } - -#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; } - -@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } }