diff --git a/bot.py b/bot.py index 4ed03fb..00ed04f 100644 --- a/bot.py +++ b/bot.py @@ -1,11 +1,13 @@ import os -import discord import logging -import rollbot.log_config from dotenv import load_dotenv +import rollbot.config.log_config + from rollbot.Bot import Bot +from rollbot.CommandManager import CommandManager +from rollbot.config.BotConfig import BotConfig logger = logging.getLogger('bot') @@ -14,5 +16,6 @@ PREFIX = os.getenv('PREFIX') BUILD = os.getenv('BUILD') -bot = Bot(commandPrefix=PREFIX, build=BUILD) +botConfig = BotConfig(PREFIX, BUILD) +bot = Bot(botConfig, CommandManager(botConfig)) bot.run(TOKEN) diff --git a/rollbot/Bot.py b/rollbot/Bot.py index 49d2a66..b8870d7 100644 --- a/rollbot/Bot.py +++ b/rollbot/Bot.py @@ -1,22 +1,24 @@ import logging -from typing import Dict, List - import discord - -from rollbot import roller -from rollbot.model.DiceRoll import DiceRoll +from rollbot.CommandManager import CommandManager, ReplyResult +from rollbot.config.BotConfig import BotConfig class Bot(discord.Client): + """ + The base of the Bot. Only concerns itself with discord.Client events and delegates handling + """ logger = logging.getLogger(__name__) prefix: str build: str - previousRoll: Dict[discord.User, DiceRoll] = {} - def __init__(self, commandPrefix='!', build='dev'): + manager: CommandManager + + def __init__(self, config: BotConfig, commandManager: CommandManager): super(Bot, self).__init__() - self.prefix = commandPrefix - self.build = build + self.prefix = config.prefix + self.build = config.build + self.manager = commandManager self.logger.info(f"Will be reacting to '{self.prefix}'") async def on_ready(self): @@ -27,41 +29,8 @@ async def on_ready(self): async def on_message(self, message: discord.Message): if message.author == self.user: return - if str(message.content).startswith(self.prefix + 'roll'): # todo add command manager, enumerate - self.logger.info(f"Command {message.content} from {message.author} at {message.guild}.{message.channel}") - args = str(message.content).split(' ') - self.logger.info(f"Command args: {args}") - args.pop(0) - if not args: - await message.channel.send(f"{message.author.mention}, mkm") - elif len(args) > 1: - await message.channel.send(f"{message.author.mention}, 1 korraga atm") - else: - if len(args[0]) > 100: - await message.channel.send(f"{message.author.mention}, that command is too long") - return - - result = roller.roll(args[0]) - self.logger.debug(f"{result}") - - if not result.valid: - await message.channel.send(f"{message.author.mention}, invalid roll [{result.command}]") - return - - self.previousRoll[message.author] = result - await message.channel.send(self.buildResultMessage(result, message)) - if str(message.content).startswith(self.prefix + 'reroll'): # todo add command manager, enumerate - result = roller.reroll(self.previousRoll[message.author]) - await message.channel.send(self.buildResultMessage(result, message)) - if str(message.content).startswith(self.prefix + 'help'): # todo add command manager, enumerate - await message.channel.send(f"`{self.prefix}roll 2d4+1` - roll 2 d4 dice and add 1\n" - f"`{self.prefix}reroll` - reroll your last roll\n" - f"`{self.prefix}about` - general info") - if str(message.content).startswith(self.prefix + 'about'): # todo add command manager, enumerate - await message.channel.send(f"Build: **{self.build}**\n" - f"Source: https://github.com/hannilo/rollbot-py") - def buildResultMessage(self, roll: DiceRoll, message: discord.Message): - return f"{message.author.mention}\n" \ - f"result: {roll}\n" \ - f"sum ({roll.sum()} {'+' if roll.modifier >= 0 else '-'} {abs(roll.modifier)}) : **{roll.sum() + roll.modifier}**" + if str(message.content).startswith(self.prefix): + result = self.manager.handle(message) + if isinstance(result, ReplyResult): + await message.channel.send(result.reply) diff --git a/rollbot/CommandManager.py b/rollbot/CommandManager.py index c9bf388..3155ac5 100644 --- a/rollbot/CommandManager.py +++ b/rollbot/CommandManager.py @@ -1,5 +1,106 @@ +import logging +from dataclasses import dataclass +from enum import Enum +from typing import Dict + +import discord + +from rollbot import roller +from rollbot.config.BotConfig import BotConfig +from rollbot.model.DiceRoll import DiceRoll + + +class Command(Enum): + ABOUT = 'about' + HELP = 'help' + REROLL = 'reroll' + ROLL = 'roll' + + +@dataclass +class CommandResult: + command: str + successful: bool + + +@dataclass +class VoidResult(CommandResult): + pass + + +@dataclass +class ReplyResult(CommandResult): + reply: str + + class CommandManager: + logger = logging.getLogger(__name__) + + prefix: str + config: BotConfig + + previousRoll: Dict[discord.User, DiceRoll] = {} + + def __init__(self, botConfig: BotConfig): + self.prefix = botConfig.prefix + self.config = botConfig + + def handle(self, message: discord.Message) -> CommandResult: + messageContent = str(message.content) + + self.logger.info(f"Command {message.content} from {message.author} at {message.guild}.{message.channel}") + args = str(message.content).split(' ') + + userCommand = args.pop(0) + botCommand = self.parseBotCommand(userCommand) + if not botCommand: + return VoidResult(messageContent, False) + + self.logger.debug(f"Got {userCommand} [{botCommand}]") + if args: + self.logger.debug(f"Command args: {args}") + + if botCommand == Command.ROLL: + if not args: + return ReplyResult(messageContent, False, f"{message.author.mention}, mkm") + elif len(args) > 1: + return ReplyResult(messageContent, False, f"{message.author.mention}, 1 korraga atm") + elif len(args[0]) > 100: + return ReplyResult(messageContent, False, f"{message.author.mention}, that command is too long") + + result = roller.roll(args[0]) + self.logger.debug(f"{result}") + + if not result.valid: + return ReplyResult(messageContent, False, f"{message.author.mention}, invalid roll [{result.command}]") + + self.previousRoll[message.author] = result + return ReplyResult(messageContent, True, _buildResultMessage(result, message)) + + if botCommand == Command.REROLL: + previous = self.previousRoll.get(message.author) + if previous is None: + self.logger.info(f"No previous roll for {message.author}") + return VoidResult(userCommand, False) + result = roller.reroll(previous) + return ReplyResult(messageContent, True, _buildResultMessage(result, message)) + + if botCommand == Command.HELP: + return ReplyResult(messageContent, True, f"`{self.prefix}roll 2d4+1` - roll 2 d4 dice and add 1\n" + f"`{self.prefix}reroll` - reroll your last roll\n" + f"`{self.prefix}about` - general info") + + if botCommand == Command.ABOUT: + return ReplyResult(messageContent, True, f"Build: **{self.config.build}**\n" + f"Source: https://github.com/hannilo/rollbot-py") + + def parseBotCommand(self, userCommand: str) -> Command: + for cmd in Command: + if userCommand == f"{self.prefix}{cmd.value}": + return cmd - def about(self): - return "wasd" +def _buildResultMessage(roll: DiceRoll, message: discord.Message): + return f"{message.author.mention}\n" \ + f"result: {roll}\n" \ + f"sum ({roll.sum()} {'+' if roll.modifier >= 0 else '-'} {abs(roll.modifier)}) : **{roll.sum() + roll.modifier}**" diff --git a/rollbot/config/BotConfig.py b/rollbot/config/BotConfig.py new file mode 100644 index 0000000..bf3045c --- /dev/null +++ b/rollbot/config/BotConfig.py @@ -0,0 +1,7 @@ +from dataclasses import dataclass + + +@dataclass +class BotConfig: + prefix: str = '!' + build: str = 'dev' diff --git a/rollbot/config/__init__.py b/rollbot/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rollbot/log_config.py b/rollbot/config/log_config.py similarity index 74% rename from rollbot/log_config.py rename to rollbot/config/log_config.py index 5d87196..4e3eb1e 100644 --- a/rollbot/log_config.py +++ b/rollbot/config/log_config.py @@ -8,6 +8,7 @@ logging_config = dict( version=1, + disable_existing_loggers=True, formatters={ 'f': {'format': '%(levelname)-8s %(asctime)s %(name)-18s %(message)s'} @@ -16,13 +17,13 @@ 'console': { 'class': 'logging.StreamHandler', 'formatter': 'f', - 'level': LOGLEVEL + 'level': LOGLEVEL, }, 'file': { 'class': 'logging.handlers.RotatingFileHandler', 'formatter': 'f', 'filename': 'bot.log', - 'level': LOGLEVEL + 'level': LOGLEVEL, } }, root={ @@ -33,5 +34,9 @@ dictConfig(logging_config) +logging.getLogger('discord').setLevel(logging.WARNING) +logging.getLogger('websockets').setLevel(logging.ERROR) +logging.getLogger('asyncio').setLevel(logging.ERROR) + logger = logging.getLogger() logger.info(f"Logging level: {LOGLEVEL}") diff --git a/rollbot/roller.py b/rollbot/roller.py index dd8dc72..dad1949 100644 --- a/rollbot/roller.py +++ b/rollbot/roller.py @@ -1,13 +1,14 @@ import logging import re import random +from typing import List from rollbot.model.DiceRoll import DiceRoll logger = logging.getLogger(__name__) -def roll(rollCommand: str): +def roll(rollCommand: str) -> DiceRoll: if not re.fullmatch('\\d+d\\d+([+-]\\d+)?', rollCommand): logger.error(f"Failed to parse {rollCommand}") return DiceRoll(rollCommand, valid=False) @@ -29,7 +30,7 @@ def roll(rollCommand: str): modifier=modifier) -def reroll(roll: DiceRoll): +def reroll(roll: DiceRoll) -> DiceRoll: return DiceRoll(command=roll.command, diceCount=roll.diceCount, diceFaces=roll.diceFaces, @@ -37,7 +38,7 @@ def reroll(roll: DiceRoll): modifier=roll.modifier) -def getResults(diceCount: int, diceFaces: int): +def getResults(diceCount: int, diceFaces: int) -> List[int]: results = [] for i in range(0, diceCount): results.append(random.randint(1, diceFaces)) # todo inject random function