diff --git a/README.md b/README.md index 9406164..ebc005f 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,15 @@ Oauth = `https://twitchapps.com/tmi/` V5 Client ID = `https://twitchtokengenerator.com/` ### For Database Credentials +Nickname = `Anything You Want` + Engine = `"mysql+mysqlconnector://root:password@localhost:3306/DatabaseName"` +### For Discord Credentials +Nickname = `Anything You Want` + +Token = `https://discord.com/developers/` + ## Credential Usage: Place json credentials in the `/credentials/` folder. diff --git a/commands/implemented/command_roll_discord.py b/commands/implemented/command_roll_discord.py new file mode 100644 index 0000000..8c86d07 --- /dev/null +++ b/commands/implemented/command_roll_discord.py @@ -0,0 +1,75 @@ +from abc import ABCMeta + +from discord import message + +from commands.command_base import AbstractCommand + +import random + +import discord +import discord.message +import discord.channel + +class CommandRoll(AbstractCommand, metaclass=ABCMeta): + """ + this is the roll command. + """ + command = "!roll" + + def __init__(self): + super().__init__(CommandRoll.command, n_args=1, command_type=AbstractCommand.CommandType.DISCORD) + + async def do_command(self, bot, discord_message: discord.Message): + print("!roll Detected") + #twitch_message.chat.send("test acknowledged") + + diceRoll: str = "" + await bot.send_message(discord_message, "Rolling Dice...") + #await discord_message.channel.send("Rolling Dice...") + print("Rolling Dice...") + + temp_preParsedMessage = discord_message.content.split("+") + + tempParsedMessage = temp_preParsedMessage[0].split(" ") + temp_dice_stmt: str = tempParsedMessage[1] + parsedMessage = temp_dice_stmt.lower().split("d") + + loopBool: bool = False + if parsedMessage[0] != "": + loopBool = True + if loopBool == True: + if int(parsedMessage[0]) == 1: + loopBool = False + + # If roll is in xdx+x format + if loopBool == True: + rolls: list = [] + for x in range(int(parsedMessage[0])): + rolls.append(random.randint(1, int(parsedMessage[1]))) + + rollTotal = 0 + for roll in rolls: + rollTotal = rollTotal + roll + diceRoll = diceRoll + str(roll) + ", " + diceRoll = diceRoll[:-2] # This removes the last two characters in the string + + if len(temp_preParsedMessage) == 2: + diceRoll = diceRoll + " + " + temp_preParsedMessage[1] + " = " + str( + rollTotal + int(temp_preParsedMessage[1])) + else: + diceRoll = diceRoll + " = " + str(rollTotal) + # If roll is in dx+x format + if loopBool == False: + roll: int = random.randint(1, int(parsedMessage[1])) + + if len(temp_preParsedMessage) == 2: + diceRoll = str(roll) + " + " + temp_preParsedMessage[1] + " = " + str( + roll + int(temp_preParsedMessage[1])) + else: + diceRoll = str(roll) + + + diceRoll = discord_message.author.mention + " rolled: " + diceRoll + print(diceRoll) + await bot.send_message(discord_message, diceRoll) + #await discord_message.channel.send(diceRoll) diff --git a/commands/implemented/command_tts.py b/commands/implemented/command_tts.py index 305e1cf..133d851 100644 --- a/commands/implemented/command_tts.py +++ b/commands/implemented/command_tts.py @@ -18,5 +18,5 @@ class CommandTTS(AbstractCommand, metaclass=ABCMeta): bot.tts_enabled = True elif args[1] == "stop": if twitch_message.sender.lower() == twitch_message.channel: - bot.send_message("tts activated on #%s" % twitch_message.channel) + bot.send_message("tts deactivated on #%s" % twitch_message.channel) bot.tts_enabled = False diff --git a/commands/implemented/command_tts_discord.py b/commands/implemented/command_tts_discord.py new file mode 100644 index 0000000..36e0064 --- /dev/null +++ b/commands/implemented/command_tts_discord.py @@ -0,0 +1,30 @@ +from abc import ABCMeta + +from commands.command_base import AbstractCommand + +import discord +import discord.message + +class CommandTTS(AbstractCommand, metaclass=ABCMeta): + command = "!tts" + + def __init__(self): + super().__init__(CommandTTS.command, n_args=1, command_type=AbstractCommand.CommandType.DISCORD) + + async def do_command(self, bot, discord_message: discord.message): + args = self.get_args(discord_message.content) + if args[1] == "start": + print(discord_message.author.top_role) + print("start detected") + if str(discord_message.author.top_role) == "Admin": + print("Admin Check") + response = str("tts activated on %s" % discord_message.guild.name) + await bot.send_message(discord_message, response) + bot.tts_enabled = True + elif args[1] == "stop": + print("stop detected") + if str(discord_message.author.top_role) == "Admin": + print("Admin Check") + response = str("tts deactivated on %s" % discord_message.guild.name) + await bot.send_message(discord_message, response) + bot.tts_enabled = False diff --git a/commands/loader.py b/commands/loader.py index cab69b1..e885988 100644 --- a/commands/loader.py +++ b/commands/loader.py @@ -12,6 +12,9 @@ def load_commands() -> Dict[str, AbstractCommand]: commands = compile_and_load() return commands +def load_commands_new(commandType: AbstractCommand.CommandType) -> Dict[str, AbstractCommand]: + commands = compile_and_load_new(commandType) + return commands def compile_and_load_file(path: str) -> (str, AbstractCommand): module_name = os.path.split(path)[1].replace(".py", "") @@ -41,6 +44,18 @@ def compile_and_load() -> Dict[str, AbstractCommand]: break return dic +def compile_and_load_new(commandType: AbstractCommand.CommandType) -> Dict[str, AbstractCommand]: + dic = {} + implementations = get_implementations_dir() + for dirName, subdirList, fileList in os.walk(implementations): + for file in fileList: + name = os.path.join(dirName, file) + print("compiling %s" % name) + name, command = compile_and_load_file(name) + if command is not None and command.command_type is commandType: + dic[name] = command + break + return dic def get_base_dir() -> str: cwd = os.getcwd() @@ -52,7 +67,7 @@ def get_base_dir() -> str: return check_dir(os.path.join(cwd, "commands")) else: print("could not find working directory for Praxis_Bot/commands") - raise + raise Exception def get_implementations_dir() -> str: diff --git a/config.py b/config.py index e4899e4..1d1bb5a 100644 --- a/config.py +++ b/config.py @@ -74,7 +74,7 @@ class PollyVoices(Enum): Zhiyu = "Zhiyu" -botList = ("Nightbot", "StreamElements", "Moobot", "praxis_bot") +botList = ("Nightbot", "StreamElements", "Moobot", "Praxis Bot", "praxis_bot", "MEE6 +", "Nerdy", "Rythm", "Groovy") slurList = ("fag", "faggot", "niga", "nigga", "nigger", "retard", "tard", "rtard", "coon") diff --git a/credential_templates/credential_template_discord.json b/credential_templates/credential_template_discord.json index 68038f9..7a721bd 100644 --- a/credential_templates/credential_template_discord.json +++ b/credential_templates/credential_template_discord.json @@ -1,6 +1,4 @@ { - "username": "discord", - "helix": "secret discord", - "oauth": "token discord", - "v5_client": "I have no idea here" + "nickname": "AnythingYouWant", + "token": "" } \ No newline at end of file diff --git a/credentials.py b/credentials.py index 6001812..0f03a98 100644 --- a/credentials.py +++ b/credentials.py @@ -146,8 +146,8 @@ class Credentials_Module(): foundSomething = False tempCert: Discord_Credential = None for cert in self.Discord_Credentials_List: - if cert.username == searchParam: - print("Discord Credential Found: {" + cert.username + "}") + if cert.nickname == searchParam: + print("Discord Credential Found: {" + cert.nickname + "}") tempCert = cert foundSomething = True if foundSomething: diff --git a/db.py b/db.py index 7df35e8..2888f72 100644 --- a/db.py +++ b/db.py @@ -13,7 +13,7 @@ class db_module(): self.currentWorkingDB: str self.engine = None - def setup_engine(self, credential: credentials.DB_Credential): + def setup_engine(self, credential: credentials.DB_Credential = None): createEngine = True if credential is None: if self.dbCredential is None: diff --git a/discord_script.py b/discord_script.py new file mode 100644 index 0000000..64d0bf2 --- /dev/null +++ b/discord_script.py @@ -0,0 +1,152 @@ +import random +import re + +from discord import message +from discord.client import Client +import asyncio + +import config as config +import db +import tts + +import commands.loader as command_loader +from commands.command_base import AbstractCommand + +import credentials + +import discord +import discord.message +import discord.channel + +from cooldowns import Cooldown_Module + +class Discord_Module(discord.Client): + def __init__(self): + super().__init__() + self.loop = asyncio.get_event_loop() + self.dbCredential: credentials.DB_Credential + self.discordCredential: credentials.Discord_Credential + + self.cooldownModule:Cooldown_Module = Cooldown_Module() + self.cooldownModule.setupCooldown("discordRateLimit", 10, 1) + + self.commands = command_loader.load_commands_new(AbstractCommand.CommandType.DISCORD) + + self.tts_enabled: bool = False + + async def startup(self): + await self.start(self.discordCredential.token) + + def main(self): + print("starting loop") + self.loop.create_task(self.startup()) + self.loop.run_forever() + + async def on_ready(self): + print('Logged on as', self.user) + + async def on_message(self, message: discord.Message): + print("{" + message.guild.name + "}[ " + str(message.channel) + " ](" + message.author.display_name + ")> ") + print(message.content) + #Message ID + #print(str(message.id)) + #Channel ID + #print(str(message.channel.id)) + if message.content == "//test": + await message.channel.send('test response') + + if not await self.isSenderBot(message): + if self.cooldownModule.isCooldownActive("discordRateLimit") == False: + await self.eval_commands(message) + await self.tts_message(message) + + + async def eval_commands(self, message: discord.Message): + # containsURL: bool = self.contains_url(message) + try: + #first_space_idx = message.text.index(' ') + + # This fixes a error where if you send a command without arguments it fails because + # it cant find the substring. + if message.content.find(" ") != -1: + first_space_idx = message.content.index(' ') + else: + first_space_idx = -1 + + command_text = ' ' + if first_space_idx > -1: + command_text = message.content[0:first_space_idx] + else: + command_text = message.content + + command = self.commands[command_text] + if command is not None and command.command_type is AbstractCommand.CommandType.DISCORD: + await command.do_command(self, message) + except Exception as e: + # Undo the following for debug stuff + #print(e) + pass # we don't care + + async def send_message(self, message, response): + if self.cooldownModule.isCooldownActive("discordRateLimit") == False: + await message.channel.send(response) + self.cooldownModule.actionTrigger("discordRateLimit") + + async def tts_message(self, message: discord.Message): + if not await self.contains_slur(message): + if self.tts_enabled: + if not message.content.startswith('!'): + text_to_say: str = "%s says, %s" % (message.author.display_name, message.content) + channel_text = "%s user msg" % message.channel + + tts.tts(text_to_say) + + # Checks for basic slurs. + async def contains_slur(self, message: discord.Message): + containsSlur: bool = False + + if await self.slur_check(message.content) or await self.slur_check(message.author.display_name): + containsSlur = True + + return containsSlur + + async def slur_check(self, text): + containsSlur: bool = False + parsedMessage = text.split(" ") + for word in parsedMessage: + for slur in config.slurList: + if word.lower() == slur: + containsSlur = True + break # we want to immediately escape if we found a slur + if containsSlur: + break + + if containsSlur: + print("<{ slur detected! }>") + #print("<{ slur detected! }> " + " [#" + message.channel + "](" + message.author.display_name + ") used a slur in chat") + return containsSlur + + # Checks if Sender is bot. + async def isSenderBot(self, message: discord.Message): + isBot = False + for bot in config.botList: + if message.author.display_name.lower() == bot.lower(): + isBot = True + print("<{ bot detected! }> ") + return isBot + + + + +if __name__ == "__main__": + testModule = Discord_Module() + + credentials_manager = credentials.Credentials_Module() + credentials_manager.load_credentials() + testModule.dbCredential = credentials_manager.find_DB_Credential("praxis_bot") + testModule.discordCredential = credentials_manager.find_Discord_Credential("praxis_bot") + testModule.main() + + testModule.main() + + \ No newline at end of file diff --git a/main.py b/main.py index c30d5be..4939d57 100644 --- a/main.py +++ b/main.py @@ -5,31 +5,72 @@ import sys import time import twitch_script +import discord_script import utilities_script as utility import credentials +import threading + twitch_chat: twitch_script.Twitch_Module credentials_manager: credentials.Credentials_Module +discord_connection: discord_script.Discord_Module def main(): - global twitch_chat - global credentials_manager - credentials_manager = credentials.Credentials_Module() - credentials_manager.load_credentials() + pass - dbCert: credentials.DB_Credential = credentials_manager.find_Credential(credentials.DB_Credential, "praxis_bot") - twitchCert: credentials.Twitch_Credential = credentials_manager.find_Twitch_Credential("praxis_bot") - - twitch_chat = twitch_script.Twitch_Module() +def new_main(inputArg): + args = utility.get_args(inputArg) + +def twitch_module_init(dbCert, twitchCert): twitch_chat.db_manager.setup_engine(dbCert) twitch_chat.twitchCredential = twitchCert twitch_chat.join_channel(None, "thecuriousnerd") - # twitch_chat.send_message("activated") + +def discord_module_init(dbCert, discordCert): + discord_connection.dbCredential = dbCert + discord_connection.discordCredential = discordCert + + discord_connection.main() + + + +def thread_main(): + global twitch_chat + global discord_connection + global credentials_manager + + twitch_chat = twitch_script.Twitch_Module() + discord_connection = discord_script.Discord_Module() + credentials_manager = credentials.Credentials_Module() + + credentials_manager.load_credentials() + dbCert: credentials.DB_Credential = credentials_manager.find_Credential(credentials.DB_Credential, "praxis_bot") + twitchCert: credentials.Twitch_Credential = credentials_manager.find_Twitch_Credential("praxis_bot") + discordCert: credentials.Discord_Credential = credentials_manager.find_Discord_Credential("praxis_bot") + + threads = [] + + twitch = threading.Thread(target=twitch_module_init, args=(dbCert, twitchCert)) + threads.append(twitch) + twitch.start() + + discord = threading.Thread(target=discord_module_init, args=(dbCert, discordCert)) + threads.append(discord) + discord.start() + + print("---Post Thread Creation Test---") + for t in threads: + t.join() + + print("---Point of no return---") + + + if __name__ == "__main__": - main() + thread_main() diff --git a/requirements.txt b/requirements.txt index d052841..b90342e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ SQLAlchemy pandas numpy gTTS -playsound \ No newline at end of file +playsound +discord.py \ No newline at end of file diff --git a/twitch_script.py b/twitch_script.py index bf4ef2c..ef530ae 100644 --- a/twitch_script.py +++ b/twitch_script.py @@ -23,7 +23,7 @@ class Twitch_Module(): self.db_manager: db.db_module = db.db_module() self.chat: twitch.Chat - self.commands = command_loader.load_commands() + self.commands = command_loader.load_commands_new(AbstractCommand.CommandType.TWITCH) self.tts_enabled: bool = False self.tts_whitelist_enabled: bool = False self.links_allowed: bool = True @@ -34,8 +34,8 @@ class Twitch_Module(): # Default Twitch Chat limit is 20 per 30 seconds # If Mod or Op, Twitch Chat limit is 100 per 30 seconds - self.twitchChat_cooldown:Cooldown_Module = Cooldown_Module() - self.twitchChat_cooldown.setupCooldown("twitchChat", 20, 32) + self.cooldownModule:Cooldown_Module = Cooldown_Module() + self.cooldownModule.setupCooldown("twitchChat", 20, 32) def join_channel(self, credential: credentials.Twitch_Credential, channel_name:str): channel_name = "#" + channel_name @@ -60,9 +60,9 @@ class Twitch_Module(): self.chat.irc.socket.close() def send_message(self, message): - if self.twitchChat_cooldown.isCooldownActive("twitchChat") == False: + if self.cooldownModule.isCooldownActive("twitchChat") == False: self.chat.send(message) - self.twitchChat_cooldown.actionTrigger("twitchChat") + self.cooldownModule.actionTrigger("twitchChat") def send_whisper(self, user, message): pass @@ -73,13 +73,12 @@ class Twitch_Module(): if message.channel == "thecuriousnerd": if not self.isSenderBot(message): - if self.twitchChat_cooldown.isCooldownActive("twitchChat") == False: + if self.cooldownModule.isCooldownActive("twitchChat") == False: self.eval_commands(message) - #elif message.channel == message.sender: - #self.eval_commands(message) self.tts_message(message) def eval_commands(self, message: twitch.chat.Message): + print("evaling command") # containsURL: bool = self.contains_url(message) try: #first_space_idx = message.text.index(' ') @@ -99,9 +98,12 @@ class Twitch_Module(): command = self.commands[command_text] if command is not None and command.command_type is AbstractCommand.CommandType.TWITCH: + print("evaling command") command.do_command(self, message) except Exception as e: - print(e) + # Undo the following for debug stuff + #print(e) + print("failed command") pass # we don't care @@ -112,11 +114,7 @@ class Twitch_Module(): text_to_say: str = "%s says, %s" % (message.sender, message.text) channel_text = "%s user msg" % message.channel - if message.sender.lower() == message.channel: - tts.tts(text_to_say) - else: - # tts.tts(message.sender + " says, " + message.text) - tts.tts(text_to_say, channel_text) + tts.tts(text_to_say) def contains_url(self, message: twitch.chat.Message): containsURL = re.search(self._urlMatcher, message.text.lower()) is not None diff --git a/utilities_script.py b/utilities_script.py index dc1f6b6..38a1292 100644 --- a/utilities_script.py +++ b/utilities_script.py @@ -1,3 +1,6 @@ import os clearScreen = lambda: os.system('cls' if os.name == 'nt' else 'clear') + +def get_args(text: str) -> list: + return text.split(" ") \ No newline at end of file