- Coverage for bot.py: - 57% -
- -- 23 statements - - - -
-- « prev - ^ index - » next - - coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -
- -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 @@ - - -
- -- « 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 -*-
- -4"""
-5Module for the common base class for all Bots
-6"""
- -8import re
- -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 = []
- -17 def getConfig(self):
-18 """Return the current configuration"""
-19 return self.CONFIG
- -21 def setConfig(self, config):
-22 """Set the current configuration"""
-23 self.CONFIG = config
- -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)
- -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)
- -39 @staticmethod
-40 def tokenize(message):
-41 """Split a message into normalized tokens"""
-42 return re.sub("[,.?:]", " ", message).strip().lower().split()
-- coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -
-File | -class | -statements | -missing | -excluded | -coverage | -
---|---|---|---|---|---|
bot.py | -Bot | -14 | -10 | -0 | -29% | -
bot.py | -(no class) | -9 | -0 | -0 | -100% | -
discord_bot.py | -DiscordBot | -20 | -15 | -0 | -25% | -
discord_bot.py | -(no class) | -7 | -0 | -0 | -100% | -
irc_bot.py | -IrcBot | -111 | -107 | -0 | -4% | -
irc_bot.py | -(no class) | -23 | -0 | -0 | -100% | -
main.py | -(no class) | -72 | -16 | -0 | -78% | -
marvin_actions.py | -(no class) | -303 | -64 | -0 | -79% | -
marvin_general_actions.py | -(no class) | -34 | -14 | -0 | -59% | -
Total | -- | 593 | -226 | -0 | -62% | -
- No items found using the specified filter. -
-- « 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 -*-
- -4"""
-5Module for the Discord bot.
- -7Connecting, sending and receiving messages and doing custom actions.
-8"""
- -10import discord
- -12from bot import Bot
- -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)
- -25 def begin(self):
-26 """Start the bot"""
-27 self.run(self.CONFIG.get("token"))
- -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)
- -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)
-- coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -
-File | -function | -statements | -missing | -excluded | -coverage | -
---|---|---|---|---|---|
bot.py | -Bot.__init__ | -3 | -0 | -0 | -100% | -
bot.py | -Bot.getConfig | -1 | -1 | -0 | -0% | -
bot.py | -Bot.setConfig | -1 | -1 | -0 | -0% | -
bot.py | -Bot.registerActions | -4 | -4 | -0 | -0% | -
bot.py | -Bot.registerGeneralActions | -4 | -4 | -0 | -0% | -
bot.py | -Bot.tokenize | -1 | -0 | -0 | -100% | -
bot.py | -(no function) | -9 | -0 | -0 | -100% | -
discord_bot.py | -DiscordBot.__init__ | -5 | -0 | -0 | -100% | -
discord_bot.py | -DiscordBot.begin | -1 | -1 | -0 | -0% | -
discord_bot.py | -DiscordBot.checkMarvinActions | -10 | -10 | -0 | -0% | -
discord_bot.py | -DiscordBot.on_message | -4 | -4 | -0 | -0% | -
discord_bot.py | -(no function) | -7 | -0 | -0 | -100% | -
irc_bot.py | -IrcBot.__init__ | -4 | -0 | -0 | -100% | -
irc_bot.py | -IrcBot.connectToServer | -23 | -23 | -0 | -0% | -
irc_bot.py | -IrcBot.sendPrivMsg | -4 | -4 | -0 | -0% | -
irc_bot.py | -IrcBot.sendMsg | -2 | -2 | -0 | -0% | -
irc_bot.py | -IrcBot.decode_irc | -18 | -18 | -0 | -0% | -
irc_bot.py | -IrcBot.receive | -8 | -8 | -0 | -0% | -
irc_bot.py | -IrcBot.ircLogAppend | -5 | -5 | -0 | -0% | -
irc_bot.py | -IrcBot.ircLogWriteToFile | -2 | -2 | -0 | -0% | -
irc_bot.py | -IrcBot.readincoming | -12 | -12 | -0 | -0% | -
irc_bot.py | -IrcBot.mainLoop | -11 | -11 | -0 | -0% | -
irc_bot.py | -IrcBot.begin | -2 | -2 | -0 | -0% | -
irc_bot.py | -IrcBot.checkIrcActions | -4 | -4 | -0 | -0% | -
irc_bot.py | -IrcBot.checkMarvinActions | -16 | -16 | -0 | -0% | -
irc_bot.py | -(no function) | -23 | -0 | -0 | -100% | -
main.py | -printVersion | -2 | -0 | -0 | -100% | -
main.py | -mergeOptionsWithConfigFile | -9 | -1 | -0 | -89% | -
main.py | -parseOptions | -17 | -0 | -0 | -100% | -
main.py | -determineProtocol | -4 | -0 | -0 | -100% | -
main.py | -createBot | -5 | -0 | -0 | -100% | -
main.py | -main | -14 | -14 | -0 | -0% | -
main.py | -(no function) | -21 | -1 | -0 | -95% | -
marvin_actions.py | -getAllActions | -1 | -1 | -0 | -0% | -
marvin_actions.py | -setConfig | -1 | -1 | -0 | -0% | -
marvin_actions.py | -getString | -12 | -0 | -0 | -100% | -
marvin_actions.py | -marvinSmile | -5 | -0 | -0 | -100% | -
marvin_actions.py | -wordsAfterKeyWords | -7 | -0 | -0 | -100% | -
marvin_actions.py | -marvinGoogle | -8 | -0 | -0 | -100% | -
marvin_actions.py | -marvinExplainShell | -8 | -0 | -0 | -100% | -
marvin_actions.py | -marvinSource | -4 | -0 | -0 | -100% | -
marvin_actions.py | -marvinBudord | -13 | -2 | -0 | -85% | -
marvin_actions.py | -marvinQuote | -4 | -0 | -0 | -100% | -
marvin_actions.py | -videoOfToday | -7 | -1 | -0 | -86% | -
marvin_actions.py | -marvinVideoOfToday | -5 | -0 | -0 | -100% | -
marvin_actions.py | -marvinWhoIs | -4 | -0 | -0 | -100% | -
marvin_actions.py | -marvinHelp | -4 | -0 | -0 | -100% | -
marvin_actions.py | -marvinStats | -4 | -0 | -0 | -100% | -
marvin_actions.py | -marvinIrcLog | -4 | -0 | -0 | -100% | -
marvin_actions.py | -marvinSayHi | -7 | -0 | -0 | -100% | -
marvin_actions.py | -marvinLunch | -8 | -0 | -0 | -100% | -
marvin_actions.py | -marvinListen | -16 | -16 | -0 | -0% | -
marvin_actions.py | -marvinSun | -11 | -11 | -0 | -0% | -
marvin_actions.py | -marvinWeather | -9 | -9 | -0 | -0% | -
marvin_actions.py | -marvinStrip | -4 | -0 | -0 | -100% | -
marvin_actions.py | -commitStrip | -8 | -0 | -0 | -100% | -
marvin_actions.py | -marvinTimeToBBQ | -17 | -0 | -0 | -100% | -
marvin_actions.py | -nextBBQ | -10 | -0 | -0 | -100% | -
marvin_actions.py | -thirdFridayIn | -4 | -0 | -0 | -100% | -
marvin_actions.py | -marvinBirthday | -17 | -17 | -0 | -0% | -
marvin_actions.py | -marvinNameday | -15 | -2 | -0 | -87% | -
marvin_actions.py | -marvinUptime | -4 | -0 | -0 | -100% | -
marvin_actions.py | -marvinStream | -4 | -0 | -0 | -100% | -
marvin_actions.py | -marvinPrinciple | -9 | -0 | -0 | -100% | -
marvin_actions.py | -getJoke | -7 | -2 | -0 | -71% | -
marvin_actions.py | -marvinJoke | -4 | -0 | -0 | -100% | -
marvin_actions.py | -getCommit | -7 | -2 | -0 | -71% | -
marvin_actions.py | -marvinCommit | -5 | -0 | -0 | -100% | -
marvin_actions.py | -(no function) | -46 | -0 | -0 | -100% | -
marvin_general_actions.py | -setConfig | -1 | -1 | -0 | -0% | -
marvin_general_actions.py | -getString | -12 | -12 | -0 | -0% | -
marvin_general_actions.py | -getAllGeneralActions | -1 | -1 | -0 | -0% | -
marvin_general_actions.py | -marvinMorning | -9 | -0 | -0 | -100% | -
marvin_general_actions.py | -(no function) | -11 | -0 | -0 | -100% | -
Total | -- | 593 | -226 | -0 | -62% | -
- No items found using the specified filter. -
-- coverage.py v7.6.1, - created at 2024-10-04 22:49 +0200 -
-File | -statements | -missing | -excluded | -coverage | -
---|---|---|---|---|
bot.py | -23 | -10 | -0 | -57% | -
discord_bot.py | -27 | -15 | -0 | -44% | -
irc_bot.py | -134 | -107 | -0 | -20% | -
main.py | -72 | -16 | -0 | -78% | -
marvin_actions.py | -303 | -64 | -0 | -79% | -
marvin_general_actions.py | -34 | -14 | -0 | -59% | -
Total | -593 | -226 | -0 | -62% | -
- No items found using the specified filter. -
-- « 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 -*-
- -4"""
-5Module for the IRC bot.
- -7Connecting, sending and receiving messages and doing custom actions.
- -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
- -19import chardet
- -21from bot import Bot
- -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 }
- -41 # Socket for IRC server
-42 self.SOCKET = None
- -44 # Keep a log of the latest messages
-45 self.IRCLOG = None
- - -48 def connectToServer(self):
-49 """Connect to the IRC Server"""
- -51 # Create the socket & Connect to the server
-52 server = self.CONFIG["server"]
-53 port = self.CONFIG["port"]
- -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
- -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.")
- -71 # Present yourself
-72 realname = self.CONFIG["realname"]
-73 self.sendMsg('USER {NICK} 0 * :{REALNAME}\r\n'.format(NICK=nick, REALNAME=realname))
- -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.")
- -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.")
- -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)
- -94 msg = "PRIVMSG {CHANNEL} :{MSG}\r\n".format(CHANNEL=channel, MSG=message)
-95 self.sendMsg(msg)
- -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())
- -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"]
- -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
- -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')
- -128 return res
- -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)
- -140 return lines
- -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)
- -147 if not message:
-148 message = ' '.join(line[3:]).lstrip(':')
- -150 self.IRCLOG.append({
-151 'time': datetime.now().strftime("%H:%M").rjust(5),
-152 'user': user,
-153 'msg': message
-154 })
- -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)
- -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
- -169 listing = os.listdir(self.CONFIG["dirIncoming"])
- -171 for infile in listing:
-172 filename = os.path.join(self.CONFIG["dirIncoming"], infile)
- -174 with open(filename, "r", encoding="UTF-8") as f:
-175 for msg in f:
-176 self.sendPrivMsg(msg, self.CONFIG["channel"])
- -178 try:
-179 shutil.move(filename, self.CONFIG["dirDone"])
-180 except Exception:
-181 os.remove(filename)
- -183 def mainLoop(self):
-184 """For ever, listen and answer to incoming chats"""
-185 self.IRCLOG = deque([], self.CONFIG["irclogmax"])
- -187 while 1:
-188 # Write irclog
-189 self.ircLogWriteToFile()
- -191 # Check in any in the incoming directory
-192 self.readincoming()
- -194 for line in self.receive():
-195 print(line)
-196 words = line.strip().split()
- -198 if not words:
-199 continue
- -201 self.checkIrcActions(words)
-202 self.checkMarvinActions(words)
- -204 def begin(self):
-205 """Start the bot"""
-206 self.connectToServer()
-207 self.mainLoop()
- -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]))
- -217 if words[1] == 'INVITE':
-218 self.sendMsg('JOIN {CHANNEL}\r\n'.format(CHANNEL=words[3]))
- -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)
- -225 if words[1] == 'PRIVMSG':
-226 raw = ' '.join(words[3:])
-227 row = self.tokenize(raw)
- -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
-- « 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 -*-
- -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.
- -9You need to install additional modules.
- -11# Install needed modules in local directory
-12pip3 install --target modules/ feedparser beautifulsoup4 chardet
- -14Modules in modules/ will be loaded automatically. If you want to use a
-15different directory you can start the program like this instead:
- -17PYTHONPATH=modules python3 main.py
- -19# To get help
-20PYTHONPATH=modules python3 main.py --help
- -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
- -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.
- -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.
- -37# Read from incoming
-38Marvin reads messages from the incoming/ directory, if it exists, and writes
-39it out the the irc channel.
-40"""
- -42import argparse
-43import json
-44import os
-45import sys
- -47from discord_bot import DiscordBot
-48from irc_bot import IrcBot
- -50import marvin_actions
-51import marvin_general_actions
- -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)
- - - -64def printVersion():
-65 """
-66 Print version information and exit.
-67 """
-68 print(MSG_VERSION)
-69 sys.exit(0)
- - -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)
- -80 options.update(data)
-81 res = json.dumps(options, sort_keys=True, indent=4, separators=(',', ': '))
- -83 msg = "Read configuration from config file '{file}'. Current configuration is:\n{config}"
-84 print(msg.format(config=res, file=configFile))
- -86 else:
-87 print("Config file '{file}' is not readable, skipping.".format(file=configFile))
- -89 return options
- - -92def parseOptions(options):
-93 """
-94 Merge default options with incoming options and arguments and return them as a dictionary.
-95 """
- -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")
- -102 for key, value in options.items():
-103 parser.add_argument(f"--{key}", type=type(value))
- -105 args = vars(parser.parse_args())
-106 if args["version"]:
-107 printVersion()
-108 if args["config"]:
-109 mergeOptionsWithConfigFile(options, args["config"])
- -111 for parameter in options:
-112 if args[parameter]:
-113 options[parameter] = args[parameter]
- -115 res = json.dumps(options, sort_keys=True, indent=4, separators=(',', ': '))
-116 print("Configuration updated after cli options:\n{config}".format(config=res))
- -118 return options
- - -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
- - -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}")
- - -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()
- -156 sys.exit(0)
- - -159if __name__ == "__main__":
-160 main()
-- « 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 -*-
- -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
- -15from bs4 import BeautifulSoup
- - -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 ]
- - -51# Load all strings from file
-52with open("marvin_strings.json", encoding="utf-8") as f:
-53 STRINGS = json.load(f)
- -55# Configuration loaded
-56CONFIG = None
- -58def setConfig(config):
-59 """
-60 Keep reference to the loaded configuration.
-61 """
-62 global CONFIG
-63 CONFIG = config
- - -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
- -83 return res
- - -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
- - -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))
- -107 if not kwIndex:
-108 return None
- -110 return words[min(kwIndex)+1:]
- - -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
- -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)
- - -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)
- - -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")
- -151 return msg
- - -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")
- -171 return msg
- - -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")
- -182 return msg
- - -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))
- -193 if video:
-194 msg += " En passande video är " + video
-195 else:
-196 msg += " Jag har ännu ingen passande video för denna dagen."
- -198 return msg
- - -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()
- -210 return msg
- - -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")
- -221 return msg
- - -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")
- -232 return msg
- - -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")
- -243 return msg
- - -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")
- -254 return msg
- - -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)
- -272 return msg
- - -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 }
- -287 if any(r in row for r in ["lunch", "mat", "äta", "luncha"]):
-288 lunchStr = getString('lunch-message')
- -290 for keys, value in lunchOptions.items():
-291 if any(r in row for r in keys.split(" ")):
-292 return lunchStr.format(getString(value))
- -294 return lunchStr.format(getString('lunch-bth'))
- -296 return None
- - -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"]):
- -306 if not CONFIG["lastfm"]:
-307 return getString("listen", "disabled")
- -309 url = "http://ws.audioscrobbler.com/2.0/"
- -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 )
- -320 resp = requests.get(url=url, params=params, timeout=5)
-321 data = json.loads(resp.text)
- -323 artist = data["recenttracks"]["track"][0]["artist"]["#text"]
-324 title = data["recenttracks"]["track"][0]["name"]
-325 link = data["recenttracks"]["track"][0]["url"]
- -327 msg = getString("listen", "success").format(artist=artist, title=title, link=link)
- -329 except Exception:
-330 msg = getString("listen", "failed")
- -332 return msg
- - -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)
- -348 except Exception:
-349 msg = getString("sun-no")
- -351 return msg
- - -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 )
- -369 except Exception:
-370 msg = getString("smhi", "failed")
- -372 return msg
- - -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
- - -385def commitStrip(randomize=False):
-386 """
-387 Latest or random comic strip from CommitStrip.
-388 """
-389 msg = getString("commitstrip", "message")
- -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")
- -399 return msg.format(url=url)
- - -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
- -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
- -424 msg = url + ". " + msg
-425 return msg
- -427def nextBBQ():
-428 """
-429 Calculate the next grillcon date after today
-430 """
- -432 MAY = 5
-433 SEPTEMBER = 9
- -435 after = datetime.date.today()
-436 spring = thirdFridayIn(after.year, MAY)
-437 if after <= spring:
-438 return spring
- -440 autumn = thirdFridayIn(after.year, SEPTEMBER)
-441 if after <= autumn:
-442 return autumn
- -444 return thirdFridayIn(after.year + 1, MAY)
- - -447def thirdFridayIn(y, m):
-448 """
-449 Get the third Friday in a given month and year
-450 """
-451 THIRD = 2
-452 FRIDAY = -1
- -454 # Start the weeks on saturday to prevent fridays from previous month
-455 cal = calendar.Calendar(firstweekday=calendar.SATURDAY)
- -457 # Return the friday in the third week
-458 return cal.monthdatescalendar(y, m)[THIRD][FRIDAY]
- - -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()
- -472 for ana in soup.findAll('a'):
-473 if ana.parent.name == 'strong':
-474 my_list.append(ana.getText())
- -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)
- -483 except Exception:
-484 msg = getString("birthday", "error")
- -486 return msg
- -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
- -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
- -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
- -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
- -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")
- -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
- -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")
- -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
-- « 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 -*-
- -4"""
-5Make general actions for Marvin, one function for each action.
-6"""
-7import datetime
-8import json
-9import random
- -11# Load all strings from file
-12with open("marvin_strings.json", encoding="utf-8") as f:
-13 STRINGS = json.load(f)
- -15# Configuration loaded
-16CONFIG = None
- -18lastDateGreeted = None
- -20def setConfig(config):
-21 """
-22 Keep reference to the loaded configuration.
-23 """
-24 global CONFIG
-25 CONFIG = config
- - -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
- -45 return res
- - -48def getAllGeneralActions():
-49 """
-50 Return all general actions as an array.
-51 """
-52 return [
-53 marvinMorning
-54 ]
- - -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 ]
- -70 morning_phrases = [
-71 "Godmorgon! :-)",
-72 "Morgon allesammans",
-73 "Morgon gott folk",
-74 "Guten morgen",
-75 "Morgon"
-76 ]
- -78 global lastDateGreeted
- -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
-