Skip to content

Commit

Permalink
VSE implementation
Browse files Browse the repository at this point in the history
Self contained extensions!!!
  • Loading branch information
Starlii10 authored Sep 24, 2024
1 parent bf82da2 commit c200181
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 46 deletions.
38 changes: 34 additions & 4 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,36 @@
tree = bot.tree

# Events
@bot.event
async def setup_hook():
"""
Function called early in Vivia's initialization process.
"""

# skip if Vivia is already running
if viviatools.running:
viviatools.log("Vivia is already running. Skipping early initialization process", logging.DEBUG)
return

viviatools.log("Searching for VSC extensions...")

# Load VSCs
for root, dirs, files in os.walk("commands"):
for file in files:
if file.endswith(".vse"):
try:
viviatools.extractVSE(os.path.join(root, file))
except Exception as e:
viviatools.log(f"Failed to extract VSE extension {file}", logging.ERROR)
viviatools.log(f"{str(type(e))}: {e}", logging.ERROR)
viviatools.log("VSE extension will not be loaded - functionality may be limited.", logging.ERROR)
if config['Advanced']['Debug'] != "True":
os.remove("data/temp/extracted/")
else:
viviatools.log(f"VSE extension {file} extracted", logging.DEBUG)

viviatools.log("VSE extensions extracted.")

@bot.event
async def on_ready():
"""
Expand All @@ -87,7 +117,7 @@ async def on_ready():
await viviatools.setCustomPresence(random.choice(statuses["statuses"]), bot)
return

viviatools.log("Vivia is powering up...")
viviatools.log("Connected to websocket - powering on!")

# Load extensions
viviatools.log("Loading extensions!")
Expand Down Expand Up @@ -148,8 +178,8 @@ async def on_ready():
failed += [f"{file[:-3]}"]
continue
except Exception as e:
viviatools.log(f"Failed to load custom extension {file[:-3]}")
viviatools.log(f"{str(type(e))}: {e}")
viviatools.log(f"Failed to load custom extension {file[:-3]}", logging.ERROR)
viviatools.log(f"{str(type(e))}: {e}", logging.ERROR)
viviatools.log("Functionality may be limited. Ensure the extension contains no errors.", logging.ERROR)
failed += [f"{file[:-3]}"]
continue
Expand Down Expand Up @@ -491,4 +521,4 @@ async def add_custom_quote(interaction: discord.Interaction, message: discord.Me
viviatools.log(f"{str(type(e))}: {str(e)}", severity=logging.FATAL)
viviatools.log("Don't worry, she will automatically restart in 5 seconds.", logging.FATAL)
viviatools.log("I would appreciate if you would report this on GitHub, please.", logging.FATAL)
time.sleep(5)
time.sleep(5)
4 changes: 2 additions & 2 deletions commands/template.py.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ from discord import app_commands
# ex: from extras.viviatools import personalityMessage

async def setup(bot: commands.Bot): # Add your commands here
await bot.add_cog("cog_name")
await bot.add_command(commandFunction) # type: ignore
bot.add_cog("cog_name")
bot.add_command(commandFunction) # type: ignore

"""
Commands must have the following structure:
Expand Down
33 changes: 13 additions & 20 deletions commands/viviabase/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,21 @@

from discord.ext import commands
from discord import app_commands
from extras.viviatools import helpMsg, channelmakerHelpMsg, setupHelpMsg, personalityMessage
from extras.viviatools import helpMsg,personalityMessage, loaded_extensions, failed_extensions

async def setup(bot: commands.Bot): # for extension loading
bot.add_command(help)

@commands.hybrid_command(
name="help",
@commands.hybrid_command()
@app_commands.describe(
extension="The name of the extension you want help with. "
)
@app_commands.choices(message=[
app_commands.Choice(name="general", value="general"),
app_commands.Choice(name="channelmaker", value="channelmaker"),
app_commands.Choice(name="setup", value="setup"),
])
@app_commands.describe(message="The message to send to the user.")
async def help(ctx: commands.Context, message: str="general"):
match message:
case "general":
await ctx.author.send(helpMsg)
case "channelmaker":
await ctx.author.send(channelmakerHelpMsg)
case "setup":
await ctx.author.send(setupHelpMsg)
case _:
await ctx.author.send(helpMsg)
await ctx.send(personalityMessage("helpsent").replace("{user}", ctx.author.mention), ephemeral=True)
async def help(ctx: commands.Context, extension: str):
"""
Help command.
"""
# we need to account for custom extensions too
if extension in loaded_extensions or extension in failed_extensions:
await ctx.send(helpMsg(extension))
else:
await ctx.send(personalityMessage("extensionnotloaded"))
2 changes: 1 addition & 1 deletion data/help/channelmaker.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ A channelmaker JSON configuration looks like this:
You can also define the type of channels you want to create with the `type` argument.
- "text" makes text channels.
- "voice" makes voice channels.
- "forum" makes forum channels, but only in servers where it's supported.
- "forum" makes forum channels, but only in servers where it's supported (non-community servers usually can't make forum channels).
File renamed without changes.
11 changes: 0 additions & 11 deletions data/help/setup.txt

This file was deleted.

10 changes: 10 additions & 0 deletions data/personalityMessages/extensionnotloaded.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"messages": [
"I'm sorry, but that extension isn't loaded.",
"I'm sorry, that extension isn't loaded.",
"Doesn't look like that extension is loaded.",
"My extension list doesn't contain that one...",
"Extension not loaded.",
"It doesn't appear that I have that extension loaded."
]
}
2 changes: 1 addition & 1 deletion data/personalityMessages/historyclear.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"Cleared your chat history.",
"History wiped from memory banks.",
"Formatted chat history.",
"`rm -rf /home/vivia/data/chathistory`"
"`rm -rf /home/vivia/data/chathistory` returned 0."
]
}
81 changes: 74 additions & 7 deletions extras/viviatools.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
import logging
import os
import random
import shutil
import sys
import colorlog
import discord
import zipfile

from discord.ext import commands

Expand Down Expand Up @@ -72,20 +74,72 @@

logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.INFO)
if config["Advanced"]["Debug"] == "True":
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)

# File logging
handler = logging.FileHandler(f'data/logs/{datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")}.log')
handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s\t %(message)s'))
logger.addHandler(handler)

# Help messages
helpMsg = open("data/help/general.txt", "r").read()
channelmakerHelpMsg = open("data/help/channelmaker.txt", "r").read()
setupHelpMsg = open("data/help/setup.txt", "r").read()

# -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
# Functions
def extractVSE(file: str):
"""
Extracts a self-contained extension (VSE) file.
## Args:
- file (str): The path to the VSE file to extract.
## Returns:
- None.
## Notes:
- VSEs are zipped files that contain multiple files, specifically a Python script and data files.
"""

# find the name of the VSE file
filename = os.path.basename(file).removesuffix(".vse")

# Create the data/temp/extracted folder if it doesn't exist
os.makedirs("data/temp/extracted", exist_ok=True)

# Create the data/temp/extracted/{vse file name} folder
os.makedirs(f"data/temp/extracted/{filename}", exist_ok=True)

# Unzip the VSE
zipfile.ZipFile(file).extractall(f"data/temp/extracted/{filename}")

# python file
for f in os.listdir(f"data/temp/extracted/{filename}"):
if f.endswith(".py"):
os.rename(f"data/temp/extracted/{filename}/{f}", f"commands/{filename}.py")
break

# help text
for f in os.listdir(f"data/temp/extracted/{filename}"):
if f.endswith(".txt") and "help" in f:
os.rename(f"data/temp/extracted/{filename}/{f}", f"data/help/{f}")

# personality messages
for f in os.listdir(f"data/temp/extracted/{filename}/personalityMessages"):
if f.endswith(".json"):
os.rename(f"data/temp/extracted/{filename}/personalityMessages/{f}", f"data/personalityMessages/{f}")

# requirements.txt
for f in os.listdir(f"data/temp/extracted/{filename}"):
if f.endswith(".txt") and "requirements" in f:
os.rename(f"data/temp/extracted/{filename}/{f}", f"requirements.txt")
break

# TODO: Find and copy any other files (if any)

# Remove the temporary folder
shutil.rmtree(f"data/temp/extracted/{filename}")


def has_bot_permissions(user: discord.Member, server: discord.Guild):
"""
Checks if the specified user has bot permissions.
Expand Down Expand Up @@ -209,4 +263,17 @@ async def setCustomPresence(message: str, bot: commands.Bot):
## Notes:
- This will always set the bot's status to online.
"""
await bot.change_presence(status=discord.Status.online, activity=discord.CustomActivity(name=message))
await bot.change_presence(status=discord.Status.online, activity=discord.CustomActivity(name=message))

def helpMsg(extension: str):
"""
Gets the help message for the specified extension.
## Args:
- extension (str): The name of the extension to get the help message for.
## Returns:
- str: The help message for the specified extension.
"""
with open(f"data/extensions/{extension}/help.txt", "r") as f:
return f.read()

0 comments on commit c200181

Please sign in to comment.