feat: sync with main

pull/85/head
Zack 2 years ago
parent 29b70f28e0
commit 1e7bb46d51

@ -1,4 +0,0 @@
DISCORD_BOT_TOKEN=
MENTION_CHANNEL_ID=
AUTH_COOKIE=
AUTH_COOKIE_SRCHHPGUSR=

@ -1,6 +0,0 @@
FROM python:3.9.16
WORKDIR /bot
COPY requirements.txt /bot/
RUN pip install -r requirements.txt
COPY . /bot
CMD python bot.py

@ -1,120 +0,0 @@
import discord
import os
import src.log
import sys
import pkg_resources
import json
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())
# init loggger
logger = src.log.setup_logger(__name__)
def restart_bot():
# Replace current process with new instance of bot.py
os.execl(sys.executable, sys.executable, "bot.py")
def check_verion() -> None:
# Read the requirements.txt file and add each line to a list
with open("requirements.txt") as f:
required = f.read().splitlines()
# For each library listed in requirements.txt, check if the corresponding version is installed
for package in required:
# Use the pkg_resources library to get information about the installed version of the library
package_name, package_verion = package.split("==")
installed = pkg_resources.get_distribution(package_name)
# Extract the library name and version number
name, version = installed.project_name, installed.version
# Compare the version number to see if it matches the one in requirements.txt
if package != f"{name}=={version}":
logger.error(
f"{name} version {version} is installed but does not match the requirements"
)
sys.exit()
@bot.event
async def on_ready():
bot_status = discord.Status.online
bot_activity = discord.Activity(type=discord.ActivityType.playing, name="bing.com")
await bot.change_presence(status=bot_status, activity=bot_activity)
for Filename in os.listdir("./cogs"):
if Filename.endswith(".py"):
await bot.load_extension(f"cogs.{Filename[:-3]}")
logger.info(f"{bot.user} is now running!")
print("Bot is Up and Ready!")
try:
synced = await bot.tree.sync()
print(f"Synced {len(synced)} commands")
except Exception as e:
print(e)
# Load command
@commands.is_owner()
@bot.command()
async def load(ctx, extension):
await bot.load_extension(f"cogs.{extension}")
await ctx.author.send(f"> **Loaded {extension} done.**")
# Unload command
@commands.is_owner()
@bot.command()
async def unload(ctx, extension):
await bot.unload_extension(f"cogs.{extension}")
await ctx.author.send(f"> **Un-Loaded {extension} done.**")
# Empty discord_bot.log file
@commands.is_owner()
@bot.command()
async def clean(ctx):
open("discord_bot.log", "w").close()
await ctx.author.send(f"> **Successfully emptied the file!**")
# Get discord_bot.log file
@commands.is_owner()
@bot.command()
async def getLog(ctx):
try:
with open("discord_bot.log", "rb") as f:
file = discord.File(f)
await ctx.author.send(file=file)
await ctx.author.send("> **Send successfully!**")
except:
await ctx.author.send("> **Send failed!**")
# Upload new Bing cookies and restart the bot
@commands.is_owner()
@bot.command()
async def upload(ctx):
if ctx.message.attachments:
for attachment in ctx.message.attachments:
if str(attachment)[-4:] == ".txt":
content = await attachment.read()
with open("cookies.json", "w", encoding="utf-8") as f:
json.dump(json.loads(content), f, indent=2)
if not isinstance(ctx.channel, discord.abc.PrivateChannel):
await ctx.message.delete()
await ctx.author.send(f"> **Upload new cookies successfully!**")
logger.warning("\x1b[31mCookies has been setup successfully\x1b[0m")
restart_bot()
else:
await ctx.author.send("> **Didn't get any txt file.**")
else:
await ctx.author.send("> **Didn't get any file.**")
if __name__ == "__main__":
check_verion()
bot.run(os.getenv("DISCORD_BOT_TOKEN"))

@ -1,148 +0,0 @@
import os
import discord
import json
from typing import Optional
from EdgeGPT.ImageGen import ImageGenAsync, ImageGen
from EdgeGPT.EdgeGPT import Chatbot
from discord import app_commands
from core.classes import Cog_Extension
from src import log
from src.imageCreate import create_image, get_using_create, set_using_create
from src.response import send_message, get_using_send, set_using_send
from dotenv import load_dotenv
load_dotenv()
logger = log.setup_logger(__name__)
users_chatbot = {}
users_image_generator = {}
user_conversation_style = {}
async def init_chatbot(user_id):
with open("./cookies.json", encoding="utf-8") as file:
cookie_json = json.load(file)
for cookie in cookie_json:
if cookie.get("name") == "_U":
auth_cookie = cookie.get("value")
break
auth_cookie = os.environ.get("AUTH_COOKIE")
auth_cookie_SRCHHPGUSR = os.environ.get("AUTH_COOKIE_SRCHHPGUSR")
# auth_cookie_SRCHHPGUSR = os.environ.get("AUTH_COOKIE_SRCHHPGUSR")
users_chatbot[user_id] = UserChatbot(cookies=cookie_json)
users_image_generator[user_id] = ImageGenAsync(auth_cookie, quiet=True)
user_conversation_style[user_id] = "balanced"
class UserChatbot:
def __init__(self, cookies):
self.chatbot = Chatbot(cookies=cookies)
async def send_message(self, interaction, message, conversation_style):
await send_message(self.chatbot, interaction, message, conversation_style)
async def create_image(self, interaction, prompt: str, image_generator):
await create_image(interaction, prompt, image_generator)
async def reset(self):
await self.chatbot.reset()
class EdgeGPT(Cog_Extension):
# Chat with Bing
@app_commands.command(name="bing", description="Have a chat with Bing")
async def bing(self, interaction: discord.Interaction, *, message: str):
try:
using = await get_using_send(interaction.user.id)
except:
await set_using_send(interaction.user.id, False)
using = await get_using_send(interaction.user.id)
if not using:
await interaction.response.defer(ephemeral=False, thinking=True)
username = str(interaction.user)
usermessage = message
channel = str(interaction.channel)
user_id = interaction.user.id
if user_id not in users_chatbot:
await init_chatbot(interaction.user.id)
conversation_style = user_conversation_style[user_id]
logger.info(f"\x1b[31m{username}\x1b[0m : '{usermessage}' ({channel}) [Style: {conversation_style}]")
await users_chatbot[user_id].send_message(interaction, usermessage, conversation_style)
else:
await interaction.response.defer(ephemeral=True, thinking=True)
await interaction.followup.send("> **Please wait for your last conversation to finish.**")
# Reset Bing conversation
@app_commands.command(name="reset", description="Reset Bing conversation")
async def reset(self, interaction: discord.Interaction):
await interaction.response.defer(ephemeral=True, thinking=True)
user_id = interaction.user.id
try:
await users_chatbot[user_id].reset()
await interaction.followup.send("> **Info: Reset finish.**")
logger.warning("\x1b[31mBing has been successfully reset\x1b[0m")
except:
await interaction.followup.send(f"> **You don't have any conversation yet.**")
logger.exception("Bing reset failed.")
# Switch conversation style
@app_commands.command(name="switch_style", description="Switch conversation style")
@app_commands.choices(style=[app_commands.Choice(name="Creative", value="creative"), app_commands.Choice(name="Balanced", value="balanced"), app_commands.Choice(name="Precise", value="precise")])
async def switch_style(self, interaction: discord.Interaction, style: app_commands.Choice[str]):
await interaction.response.defer(ephemeral=True, thinking=True)
user_id = interaction.user.id
if user_id not in users_chatbot:
await init_chatbot(user_id)
user_conversation_style[user_id] = style.value
await interaction.followup.send(f"> **Info: successfull switch conversation style to {style.value}.**")
logger.warning(f"\x1b[31mConversation style has been successfully switch to {style.value}\x1b[0m")
# Set and delete personal Bing Cookies
@app_commands.command(name="bing_cookies", description="Set or delete Bing Cookies")
@app_commands.choices(choice=[app_commands.Choice(name="set", value="set"), app_commands.Choice(name="delete", value="delete")])
async def cookies_setting(self, interaction: discord.Interaction, choice: app_commands.Choice[str], cookies_file: Optional[discord.Attachment]=None):
await interaction.response.defer(ephemeral=True, thinking=True)
user_id = interaction.user.id
if choice.value == "set":
try:
content = json.loads(await cookies_file.read())
for cookie in content:
if cookie.get("name") == "_U":
auth_cookie = cookie.get("value")
break
users_image_generator[user_id] = ImageGenAsync(auth_cookie, quiet=True)
users_chatbot[user_id] = UserChatbot(cookies=content)
user_conversation_style[user_id] = "balanced"
await interaction.followup.send("> **Upload successful!**")
logger.warning(f"\x1b[31m{interaction.user} set Bing Cookies successful\x1b[0m")
except:
await interaction.followup.send("> **Please upload your Bing Cookies.**")
else:
try:
del users_chatbot[user_id]
del users_image_generator[user_id]
del user_conversation_style[user_id]
await interaction.followup.send("> **Delete finish.**")
logger.warning(f"\x1b[31m{interaction.user} delete Cookies\x1b[0m")
except:
await interaction.followup.send("> **You don't have any Bing Cookies.**")
# Create images
@app_commands.command(name="create_image", description="generate image by Bing image creator")
async def create_image(self, interaction: discord.Interaction, *, prompt: str):
user_id = interaction.user.id
if interaction.user.id not in users_chatbot:
await init_chatbot(user_id)
try:
using = await get_using_create(user_id)
except:
await set_using_create(user_id, False)
using = await get_using_create(user_id)
if not using:
logger.info(f"\x1b[31m{interaction.user}\x1b[0m : '{prompt}' ({interaction.channel}) [BingImageCreator]")
await users_chatbot[user_id].create_image(interaction, prompt, users_image_generator[user_id] )
else:
await interaction.response.defer(ephemeral=True, thinking=True)
await interaction.followup.send("> **Please wait for your last image to create finish.**")
async def setup(bot):
await bot.add_cog(EdgeGPT(bot))

@ -1,321 +0,0 @@
import discord
import re
import os
import json
import asyncio
from EdgeGPT.EdgeGPT import Chatbot, ConversationStyle
from dotenv import load_dotenv
from discord.ext import commands
from core.classes import Cog_Extension
from functools import partial
from src import log
load_dotenv()
USE_SUGGEST_RESPONSES = True
try:
MENTION_CHANNEL_ID = int(os.getenv("MENTION_CHANNEL_ID"))
except:
MENTION_CHANNEL_ID = None
logger = log.setup_logger(__name__)
sem = asyncio.Semaphore(1)
conversation_style = "balanced"
with open("./cookies.json", encoding="utf-8") as file:
cookies = json.load(file)
chatbot = Chatbot(cookies=cookies)
# To add suggest responses
class MyView(discord.ui.View):
def __init__(self, chatbot: Chatbot, suggest_responses: list):
super().__init__(timeout=120)
# Add buttons
for label in suggest_responses:
button = discord.ui.Button(label=label)
# Button event
async def callback(
interaction: discord.Interaction, button: discord.ui.Button
):
await interaction.response.defer(ephemeral=False, thinking=True)
# When click the button, all buttons will disable.
for child in self.children:
child.disabled = True
await interaction.followup.edit_message(
message_id=interaction.message.id, view=self
)
username = str(interaction.user)
usermessage = button.label
channel = str(interaction.channel)
logger.info(
f"\x1b[31m{username}\x1b[0m : '{usermessage}' ({channel}) [Style: {conversation_style}] [button]"
)
task = asyncio.create_task(
send_message(chatbot, interaction, usermessage)
)
await asyncio.gather(task)
self.add_item(button)
self.children[-1].callback = partial(callback, button=button)
# Show Dropdown
class DropdownView(discord.ui.View):
def __init__(self):
super().__init__(timeout=180)
options = [
discord.SelectOption(
label="Creative",
description="Switch conversation style to Creative",
emoji="🎨",
),
discord.SelectOption(
label="Balanced",
description="Switch conversation style to Balanced",
emoji="⚖️",
),
discord.SelectOption(
label="Precise",
description="Switch conversation style to Precise",
emoji="🔎",
),
discord.SelectOption(
label="Reset", description="Reset conversation", emoji="🔄"
),
]
dropdown = discord.ui.Select(
placeholder="Choose setting", min_values=1, max_values=1, options=options
)
dropdown.callback = self.dropdown_callback
self.add_item(dropdown)
# Dropdown event
async def dropdown_callback(self, interaction: discord.Interaction):
await interaction.response.defer(ephemeral=False, thinking=True)
if interaction.data["values"][0] == "Creative":
await set_conversation_style("creative")
await interaction.followup.send(
f"> **Info: successfull switch conversation style to *{interaction.data['values'][0]}*.**"
)
logger.warning(
f"\x1b[31mConversation style has been successfully switch to {interaction.data['values'][0]}\x1b[0m"
)
elif interaction.data["values"][0] == "Balanced":
await set_conversation_style("balanced")
await interaction.followup.send(
f"> **Info: successfull switch conversation style to *{interaction.data['values'][0]}*.**"
)
logger.warning(
f"\x1b[31mConversation style has been successfully switch to {interaction.data['values'][0]}\x1b[0m"
)
elif interaction.data["values"][0] == "Precise":
await set_conversation_style("precise")
await interaction.followup.send(
f"> **Info: successfull switch conversation style to *{interaction.data['values'][0]}*.**"
)
logger.warning(
f"\x1b[31mConversation style has been successfully switch to {interaction.data['values'][0]}\x1b[0m"
)
else:
await chatbot.reset()
await interaction.followup.send(f"> **Info: Reset finish.**")
logger.warning("\x1b[31mBing has been successfully reset\x1b[0m")
# disable dropdown after select
for dropdown in self.children:
dropdown.disabled = True
await interaction.followup.edit_message(
message_id=interaction.message.id, view=self
)
# Set conversation style
async def set_conversation_style(style: str):
global conversation_style
conversation_style = style
async def set_chatbot(cookies):
global chatbot
chatbot = Chatbot(cookies=cookies)
async def send_message(chatbot: Chatbot, message, user_message: str):
async with sem:
if isinstance(message, discord.message.Message):
await message.channel.typing()
reply = ""
text = ""
link_embed = ""
images_embed = []
all_url = []
try:
# Change conversation style
if conversation_style == "creative":
reply = await chatbot.ask(
prompt=user_message,
conversation_style=ConversationStyle.creative,
simplify_response=True,
)
elif conversation_style == "precise":
reply = await chatbot.ask(
prompt=user_message,
conversation_style=ConversationStyle.precise,
simplify_response=True,
)
else:
reply = await chatbot.ask(
prompt=user_message,
conversation_style=ConversationStyle.balanced,
simplify_response=True,
)
# Get reply text
text = f"{reply['text']}"
text = re.sub(r"\[\^(\d+)\^\]", lambda match: "", text)
# Get the URL, if available
try:
if len(reply["sources"]) != 0:
for i, url in enumerate(reply["sources"], start=1):
if len(url["providerDisplayName"]) == 0:
all_url.append(f"{i}. {url['seeMoreUrl']}")
else:
all_url.append(
f"{i}. [{url['providerDisplayName']}]({url['seeMoreUrl']})"
)
link_text = "\n".join(all_url)
link_embed = discord.Embed(description=link_text)
except:
pass
# Set the final message
if isinstance(message, discord.interactions.Interaction):
user_message = user_message.replace("\n", "")
ask = f"> **{user_message}**\t(***style: {conversation_style}***)\n\n"
response = f"{ask}{text}"
else:
response = f"{text}\t(***style: {conversation_style}***)"
# Discord limit about 2000 characters for a message
while len(response) > 2000:
temp = response[:2000]
response = response[2000:]
if isinstance(message, discord.interactions.Interaction):
await message.followup.send(temp)
else:
await message.channel.send(temp)
# Get the image, if available
try:
if len(link_embed) == 0:
all_image = re.findall(
"https?://[\w\./]+", str(reply["sources_text"])
)
[
images_embed.append(
discord.Embed(url="https://www.bing.com/").set_image(
url=image_link
)
)
for image_link in all_image
]
except:
pass
if USE_SUGGEST_RESPONSES:
suggest_responses = reply["suggestions"]
if images_embed:
if isinstance(message, discord.interactions.Interaction):
await message.followup.send(
response,
view=MyView(chatbot, suggest_responses),
embeds=images_embed,
wait=True,
)
else:
await message.channel.send(
response,
view=MyView(chatbot, suggest_responses),
embeds=images_embed,
)
elif link_embed:
if isinstance(message, discord.interactions.Interaction):
await message.followup.send(
response,
view=MyView(chatbot, suggest_responses),
embed=link_embed,
wait=True,
)
else:
await message.channel.send(
response,
view=MyView(chatbot, suggest_responses),
embed=link_embed,
)
else:
if isinstance(message, discord.interactions.Interaction):
await message.followup.send(
response, view=MyView(chatbot, suggest_responses), wait=True
)
else:
await message.channel.send(
response, view=MyView(chatbot, suggest_responses)
)
else:
if images_embed:
if isinstance(message, discord.interactions.Interaction):
await message.followup.send(
response, embeds=images_embed, wait=True
)
else:
await message.channel.send(response, embeds=images_embed)
elif link_embed:
if isinstance(message, discord.interactions.Interaction):
await message.followup.send(
response, embed=link_embed, wait=True
)
else:
await message.channel.send(response, embed=link_embed)
else:
if isinstance(message, discord.interactions.Interaction):
await message.followup.send(response, wait=True)
else:
await message.channel.send(response)
except Exception as e:
if isinstance(message, discord.interactions.Interaction):
await message.followup.send(f">>> **Error: {e}**")
else:
await message.channel.send(f">>> **Error: {e}**")
logger.exception(f"Error while sending message: {e}")
class Event(Cog_Extension):
@commands.Cog.listener()
async def on_message(self, message: discord.Message):
if message.author == self.bot.user:
return
if self.bot.user in message.mentions:
if not MENTION_CHANNEL_ID or message.channel.id == MENTION_CHANNEL_ID:
content = re.sub(r"<@.*?>", "", message.content).strip()
if len(content) > 0:
username = str(message.author)
channel = str(message.channel)
logger.info(
f"\x1b[31m{username}\x1b[0m : '{content}' ({channel}) [Style: {conversation_style}]"
)
task = asyncio.create_task(send_message(chatbot, message, content))
await asyncio.gather(task)
else:
await message.channel.send(view=DropdownView())
elif MENTION_CHANNEL_ID is not None:
await message.channel.send(
f"> **Can only be mentioned at <#{self.bot.get_channel(MENTION_CHANNEL_ID).id}>**"
)
async def setup(bot):
await bot.add_cog(Event(bot))

@ -1,35 +0,0 @@
import discord
from core.classes import Cog_Extension
from discord import app_commands
class Help(Cog_Extension):
@app_commands.command(name="help", description="Show how to use")
async def help(self, interaction: discord.Interaction):
embed = discord.Embed(
title="Help",
)
embed.add_field(
name="/bing_cookies",
value="Set and delete your Bing Cookies.",
inline=False,
)
embed.add_field(name="/bing", value="Chat with Bing.", inline=False)
embed.add_field(
name="/reset", value="Reset your Bing conversation.", inline=False
)
embed.add_field(
name="/switch_style",
value="Switch your Bing conversation style.",
inline=False,
)
embed.add_field(
name="/create_image",
value="Generate image by Bing Image Creator.",
inline=False,
)
await interaction.response.send_message(embed=embed)
async def setup(bot):
await bot.add_cog(Help(bot))

@ -1,11 +0,0 @@
version: '3'
services:
spartan:
container_name: Spartan
build: .
environment:
- DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN}
volumes:
- ./cookies.json:/bot/cookies.json
- ./config.yml:/bot/config.yml

@ -1,6 +0,0 @@
[
{
"name": "cookie1",
"value": "1lEXeWRSIPUsQ0S3tdAc3v7BexGK2qBlzsXz8j52w_HNBoOsegjiwRySQHmfoWduHVUxSXo6cETPP2qNrYWAz6k7wn43WGO9i7ll9_Wl7M6HA2c9twbKByfAtAB5fr26wPawQ6y1GCdakD_Kr4xdD20fvkytnmOmZu7Ktnb9mUVE605AAbJcIA9SOlRN5410ZPOnZA1cIzr4WtAFWNfQKPG6Sxk_zO5zvXQfYTyMNmOI"
}
]

@ -1,6 +0,0 @@
from discord.ext import commands
class Cog_Extension(commands.Cog):
def __init__(self, bot):
self.bot = bot

@ -1,4 +0,0 @@
discord.py==2.3.2
python-dotenv==1.0.0
PyYAML==6.0.1
bing-chat==1.9.3

@ -1,40 +0,0 @@
import discord
import asyncio
from src import log
logger = log.setup_logger(__name__)
using_func = {}
async def get_using_create(user_id):
return using_func[user_id]
async def set_using_create(user_id, status: bool):
using_func[user_id] = status
async def create_image(interaction: discord.Interaction, prompt: str, image_generator):
await interaction.response.defer(ephemeral=False, thinking=True)
using_func[interaction.user.id] = True
try:
embeds = []
prompts = f"> **{prompt}** - <@{str(interaction.user.id)}> (***BingImageCreator***)\n\n"
# Fetches image links
images = await image_generator.get_images(prompt)
# Add embed to list of embeds
[
embeds.append(
discord.Embed(url="https://www.bing.com/").set_image(url=image_link)
)
for image_link in images
]
await interaction.followup.send(prompts, embeds=embeds, wait=True)
except asyncio.TimeoutError:
await interaction.followup.send("> **Error: Request timed out.**")
logger.exception("Error while create image: Request timed out.")
except Exception as e:
await interaction.followup.send(f"> **Error: {e}**")
logger.exception(f"Error while create image: {e}")
finally:
using_func[interaction.user.id] = False

@ -1,66 +0,0 @@
import os
import logging
import logging.handlers
class CustomFormatter(logging.Formatter):
LEVEL_COLORS = [
(logging.DEBUG, "\x1b[40;1m"),
(logging.INFO, "\x1b[34;1m"),
(logging.WARNING, "\x1b[33;1m"),
(logging.ERROR, "\x1b[31m"),
(logging.CRITICAL, "\x1b[41m"),
]
FORMATS = {
level: logging.Formatter(
f"\x1b[30;1m%(asctime)s\x1b[0m {color}%(levelname)-8s\x1b[0m \x1b[35m%(name)s\x1b[0m -> %(message)s",
"%Y-%m-%d %H:%M:%S",
)
for level, color in LEVEL_COLORS
}
def format(self, record):
formatter = self.FORMATS.get(record.levelno)
if formatter is None:
formatter = self.FORMATS[logging.DEBUG]
# Override the traceback to always print in red
if record.exc_info:
text = formatter.formatException(record.exc_info)
record.exc_text = f"\x1b[31m{text}\x1b[0m"
output = formatter.format(record)
# Remove the cache layer
record.exc_text = None
return output
def setup_logger(module_name: str) -> logging.Logger:
# create logger
library, _, _ = module_name.partition(".py")
logger = logging.getLogger(library)
logger.setLevel(logging.INFO)
if not logger.handlers:
# create console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(CustomFormatter())
# specify that the log file path is the same as `main.py` file path
grandparent_dir = os.path.abspath(__file__ + "/../../")
log_name = "discord_bot.log"
log_path = os.path.join(grandparent_dir, log_name)
# create local log handler
log_handler = logging.handlers.RotatingFileHandler(
filename=log_path,
encoding="utf-8",
maxBytes=32 * 1024 * 1024, # 32 MiB
backupCount=2, # Rotate through 5 files
)
log_handler.setFormatter(CustomFormatter())
# Add handlers to logger
logger.addHandler(log_handler)
logger.addHandler(console_handler)
return logger

@ -1,194 +0,0 @@
import discord
import re
from EdgeGPT.EdgeGPT import Chatbot, ConversationStyle
from src import log
from functools import partial
USE_SUGGEST_RESPONSES = True
logger = log.setup_logger(__name__)
using_func = {}
# To add suggest responses
class MyView(discord.ui.View):
def __init__(
self,
interaction: discord.Interaction,
chatbot: Chatbot,
conversation_style: str,
suggest_responses: list,
):
super().__init__(timeout=120)
self.button_author = interaction.user.id
# Add buttons
for label in suggest_responses:
button = discord.ui.Button(label=label)
# Button event
async def callback(
interaction: discord.Interaction,
button_author: int,
button: discord.ui.Button,
):
if interaction.user.id != button_author:
await interaction.response.defer(ephemeral=True, thinking=True)
await interaction.followup.send(
"You don't have permission to press this button."
)
elif not using_func[interaction.user.id]:
await interaction.response.defer(ephemeral=False, thinking=True)
# When click the button, all buttons will disable.
for child in self.children:
child.disabled = True
await interaction.followup.edit_message(
message_id=interaction.message.id, view=self
)
username = str(interaction.user)
usermessage = button.label
channel = str(interaction.channel)
logger.info(
f"\x1b[31m{username}\x1b[0m : '{usermessage}' ({channel}) [Style: {conversation_style}] [button]"
)
await send_message(
chatbot, interaction, usermessage, conversation_style
)
else:
await interaction.response.defer(ephemeral=True, thinking=True)
await interaction.followup.send(
"Please wait for your last conversation to finish."
)
self.add_item(button)
self.children[-1].callback = partial(
callback, button_author=self.button_author, button=button
)
async def get_using_send(user_id):
return using_func[user_id]
async def set_using_send(user_id, status: bool):
using_func[user_id] = status
async def send_message(
chatbot: Chatbot,
interaction: discord.Interaction,
user_message: str,
conversation_style: str,
):
using_func[interaction.user.id] = True
reply = ""
text = ""
link_embed = ""
images_embed = []
all_url = []
try:
# Change conversation style
if conversation_style == "creative":
reply = await chatbot.ask(
prompt=user_message,
conversation_style=ConversationStyle.creative,
simplify_response=True,
)
elif conversation_style == "precise":
reply = await chatbot.ask(
prompt=user_message,
conversation_style=ConversationStyle.precise,
simplify_response=True,
)
else:
reply = await chatbot.ask(
prompt=user_message,
conversation_style=ConversationStyle.balanced,
simplify_response=True,
)
# Get reply text
text = f"{reply['text']}"
text = re.sub(r"\[\^(\d+)\^\]", lambda match: "", text)
# Get the URL, if available
try:
if len(reply["sources"]) != 0:
for i, url in enumerate(reply["sources"], start=1):
if len(url["providerDisplayName"]) == 0:
all_url.append(f"{i}. {url['seeMoreUrl']}")
else:
all_url.append(
f"{i}. [{url['providerDisplayName']}]({url['seeMoreUrl']})"
)
link_text = "\n".join(all_url)
link_embed = discord.Embed(description=link_text)
except:
pass
# Set the final message
user_message = user_message.replace("\n", "")
ask = f"> **{user_message}** - <@{str(interaction.user.id)}> (***style: {conversation_style}***)\n\n"
response = f"{ask}{text}"
# Discord limit about 2000 characters for a message
while len(response) > 2000:
temp = response[:2000]
response = response[2000:]
await interaction.followup.send(temp)
# Get the image, if available
try:
if len(link_embed) == 0:
all_image = re.findall("https?://[\w\./]+", str(reply["sources_text"]))
[
images_embed.append(
discord.Embed(url="https://www.bing.com/").set_image(
url=image_link
)
)
for image_link in all_image
]
except:
pass
# Add all suggest responses in list
if USE_SUGGEST_RESPONSES:
suggest_responses = reply["suggestions"]
if images_embed:
await interaction.followup.send(
response,
view=MyView(
interaction, chatbot, conversation_style, suggest_responses
),
embeds=images_embed,
wait=True,
)
elif link_embed:
await interaction.followup.send(
response,
view=MyView(
interaction, chatbot, conversation_style, suggest_responses
),
embed=link_embed,
wait=True,
)
else:
await interaction.followup.send(
response,
view=MyView(
interaction, chatbot, conversation_style, suggest_responses
),
wait=True,
)
else:
if images_embed:
await interaction.followup.send(
response, embeds=images_embed, wait=True
)
elif link_embed:
await interaction.followup.send(response, embed=link_embed, wait=True)
else:
await interaction.followup.send(response, wait=True)
except Exception as e:
await interaction.followup.send(f">>> **Error: {e}**")
logger.exception(f"Error while sending message: {e}")
finally:
using_func[interaction.user.id] = False

@ -1,197 +0,0 @@
import asyncio
import argparse
from collections import Counter
import json
import pathlib
import re
import discord
from discord.ext import commands
import gradio as gr
from gradio import utils
import requests
from typing import Dict, List
from utils import *
lock = asyncio.Lock()
bot = commands.Bot("", intents=discord.Intents(messages=True, guilds=True))
GUILD_SPACES_FILE = "guild_spaces.pkl"
if pathlib.Path(GUILD_SPACES_FILE).exists():
guild_spaces = read_pickle_file(GUILD_SPACES_FILE)
assert isinstance(guild_spaces, dict), f"{GUILD_SPACES_FILE} in invalid format."
guild_blocks = {}
delete_keys = []
for k, v in guild_spaces.items():
try:
guild_blocks[k] = gr.Interface.load(v, src="spaces")
except ValueError:
delete_keys.append(k)
for k in delete_keys:
del guild_spaces[k]
else:
guild_spaces: Dict[int, str] = {}
guild_blocks: Dict[int, gr.Blocks] = {}
HASHED_USERS_FILE = "users.pkl"
if pathlib.Path(HASHED_USERS_FILE).exists():
hashed_users = read_pickle_file(HASHED_USERS_FILE)
assert isinstance(hashed_users, list), f"{HASHED_USERS_FILE} in invalid format."
else:
hashed_users: List[str] = []
@bot.event
async def on_ready():
print(f"Logged in as {bot.user}")
print(f"Running in {len(bot.guilds)} servers...")
async def run_prediction(space: gr.Blocks, *inputs):
inputs = list(inputs)
fn_index = 0
processed_inputs = space.serialize_data(fn_index=fn_index, inputs=inputs)
batch = space.dependencies[fn_index]["batch"]
if batch:
processed_inputs = [[inp] for inp in processed_inputs]
outputs = await space.process_api(
fn_index=fn_index, inputs=processed_inputs, request=None, state={}
)
outputs = outputs["data"]
if batch:
outputs = [out[0] for out in outputs]
processed_outputs = space.deserialize_data(fn_index, outputs)
processed_outputs = utils.resolve_singleton(processed_outputs)
return processed_outputs
async def display_stats(message: discord.Message):
await message.channel.send(
f"Running in {len(bot.guilds)} servers\n"
f"Total # of users: {len(hashed_users)}\n"
f"------------------"
)
await message.channel.send(f"Most popular spaces:")
# display the top 10 most frequently occurring strings and their counts
spaces = guild_spaces.values()
counts = Counter(spaces)
for space, count in counts.most_common(10):
await message.channel.send(f"- {space}: {count}")
async def load_space(guild: discord.Guild, message: discord.Message, content: str):
iframe_url = (
requests.get(f"https://huggingface.co/api/spaces/{content}/host")
.json()
.get("host")
)
if iframe_url is None:
return await message.channel.send(
f"Space: {content} not found. If you'd like to make a prediction, enclose the inputs in quotation marks."
)
else:
await message.channel.send(
f"Loading Space: https://huggingface.co/spaces/{content}..."
)
interface = gr.Interface.load(content, src="spaces")
guild_spaces[guild.id] = content
guild_blocks[guild.id] = interface
asyncio.create_task(update_pickle_file(guild_spaces, GUILD_SPACES_FILE))
if len(content) > 32 - len(f"{bot.name} []"): # type: ignore
nickname = content[: 32 - len(f"{bot.name} []") - 3] + "..." # type: ignore
else:
nickname = content
nickname = f"{bot.name} [{nickname}]" # type: ignore
await guild.me.edit(nick=nickname)
await message.channel.send(
"Ready to make predictions! Type in your inputs and enclose them in quotation marks."
)
async def disconnect_space(bot: commands.Bot, guild: discord.Guild):
guild_spaces.pop(guild.id, None)
guild_blocks.pop(guild.id, None)
asyncio.create_task(update_pickle_file(guild_spaces, GUILD_SPACES_FILE))
await guild.me.edit(nick=bot.name) # type: ignore
async def make_prediction(guild: discord.Guild, message: discord.Message, content: str):
if guild.id in guild_spaces:
params = re.split(r' (?=")', content)
params = [p.strip("'\"") for p in params]
space = guild_blocks[guild.id]
predictions = await run_prediction(space, *params)
if isinstance(predictions, (tuple, list)):
for p in predictions:
await send_file_or_text(message.channel, p)
else:
await send_file_or_text(message.channel, predictions)
return
else:
await message.channel.send(
"No Space is currently running. Please type in the name of a Hugging Face Space name first, e.g. abidlabs/en2fr"
)
await guild.me.edit(nick=bot.name) # type: ignore
@bot.event
async def on_message(message: discord.Message):
if message.author == bot.user:
return
h = hash_user_id(message.author.id)
if h not in hashed_users:
hashed_users.append(h)
asyncio.create_task(update_pickle_file(hashed_users, HASHED_USERS_FILE))
else:
if message.content:
content = remove_tags(message.content)
guild = message.channel.guild
assert guild, "Message not sent in a guild."
if content.strip() == "exit":
await disconnect_space(bot, guild)
elif content.strip() == "stats":
await display_stats(message)
elif content.startswith('"') or content.startswith("'"):
await make_prediction(guild, message, content)
else:
await load_space(guild, message, content)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--token",
type=str,
help="API key for the Discord bot. You can set this to your Discord token if you'd like to make your own clone of the Gradio Bot.",
required=False,
default="",
)
args = parser.parse_args()
if args.token.strip():
discord_token = args.token
bot.env = "staging" # type: ignore
bot.name = "StagingBot" # type: ignore
else:
with open("secrets.json") as fp:
discord_token = json.load(fp)["discord_token"]
bot.env = "prod" # type: ignore
bot.name = "GradioBot" # type: ignore
bot.run(discord_token)

@ -1,41 +0,0 @@
from __future__ import annotations
import asyncio
import pickle
import hashlib
import pathlib
from typing import Dict, List
import discord
lock = asyncio.Lock()
async def update_pickle_file(data: Dict | List, file_path: str):
async with lock:
with open(file_path, "wb") as fp:
pickle.dump(data, fp)
def read_pickle_file(file_path: str):
with open(file_path, "rb") as fp:
return pickle.load(fp)
async def send_file_or_text(channel, file_or_text: str):
# if the file exists, send as a file
if pathlib.Path(str(file_or_text)).exists():
with open(file_or_text, "rb") as f:
return await channel.send(file=discord.File(f))
else:
return await channel.send(file_or_text)
def remove_tags(content: str) -> str:
content = content.replace("<@1040198143695933501>", "")
content = content.replace("<@1057338428938788884>", "")
return content.strip()
def hash_user_id(user_id: int) -> str:
return hashlib.sha256(str(user_id).encode("utf-8")).hexdigest()

@ -1,2 +0,0 @@
OPENAI_API_KEY="YOUR_API_KEY"
DALLE_COOKIE="YOUR_COOKIE"

@ -1,71 +0,0 @@
MythGen: A Dynamic New Art Form
Overview
![panel_2](https://github.com/elder-plinius/MythGen/assets/133052465/86bb5784-845b-4db8-a38f-217169ea5201)
MythGen is an Iterative Multimedia Generator that allows users to create their own comic stories based on textual prompts. The system integrates state-of-the-art language and image models to provide a seamless and creative experience.
Features
Initial Prompting: Kick-start your story with an initial text prompt.
Artistic Style Suffix: Maintain a consistent artistic style throughout your comic.
Image Generation: Generate captivating comic panels based on textual captions.
Caption Generation: Produce engaging captions for each comic panel.
Interactive Story Building: Select your favorite panels and captions to build your story iteratively.
Storyboard: View the sequence of your selected panels and their associated captions.
State Management: Keep track of the current state of your comic generation process.
User-Friendly Interface: Easy-to-use interface built on Gradio.
Prerequisites
OpenAI API Key
You will need an OpenAI API key to access GPT-3 for generating captions. Follow these steps to obtain one:
Visit OpenAI's Developer Dashboard.
Sign up for an API key and follow the verification process.
Once verified, you will be provided with an API key.
Bing Image Creator Cookie
You should obtain your cookie to run this program. Follow these steps to obtain your cookie:
Go to Bing Image Creator in your browser and log in to your account.
Press Ctrl+Shift+J to open developer tools.
Navigate to the Application section.
Click on the Cookies section.
Find the variable _U and copy its value.
How to Use
Initial Prompt: Start by inputting your initial comic concept.
Select a Panel: Choose your favorite panel and caption from the generated options.
Iterate: Use the "Next Part" button to generate the next part of your comic based on your latest selection.
View Storyboard: See your selected comic panels and captions in a storyboard for a comprehensive view of your comic.
Finalize: Continue this process until you've created your full comic story.
Installation
bash
pip install -r requirements.txt
Running MythGen
bash
python main.py
This will launch the Gradio interface where you can interact with MythGen.
Dependencies
Python 3.x
Gradio
OpenAI's GPT-3
DALL-E
Contributing
We welcome contributions! Please read the CONTRIBUTING.md for guidelines on how to contribute to this project.
License
This project is licensed under the MIT License. See LICENSE.md for details.

@ -1,6 +0,0 @@
[
{
"name": "cookie1",
"value": "1lEXeWRSIPUsQ0S3tdAc3v7BexGK2qBlzsXz8j52w_HNBoOsegjiwRySQHmfoWduHVUxSXo6cETPP2qNrYWAz6k7wn43WGO9i7ll9_Wl7M6HA2c9twbKByfAtAB5fr26wPawQ6y1GCdakD_Kr4xdD20fvkytnmOmZu7Ktnb9mUVE605AAbJcIA9SOlRN5410ZPOnZA1cIzr4WtAFWNfQKPG6Sxk_zO5zvXQfYTyMNmOI"
}
]

@ -1,8 +0,0 @@
dalle3==0.0.7
Flask==2.3.2
gradio==3.48.0
openai==0.28.1
Pillow==10.1.0
python-dotenv==1.0.0
Requests==2.31.0
swarms==1.8.2

@ -1,6 +0,0 @@
ELEVEN_LABS_API_KEY = "<your_api_key>" # https://elevenlabs.io/speech-synthesis
OPENAI_API_KEY = "<your_api_key>" # https://platform.openai.com/account/api-keys
DISCORD_TOKEN="discord_token"
API_BASE="api_base"
SYSTEM_MESSAGE=""
BOT_ID="your_bot_token"

@ -1,17 +0,0 @@
# Use an official Python runtime as a parent image
FROM python:3.10
# Set the working directory in the container to /app
WORKDIR /app
# Add the current directory contents into the container at /app
ADD . /app
# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Make port 80 available to the world outside this container
EXPOSE 80
# Run DiscordInterpreter.py when the container launches
CMD ["python", "main.py"]

@ -1,124 +0,0 @@
---
title: open-interpreter
app_file: jarvis.py
sdk: gradio
sdk_version: 3.33.1
---
# Open-Sourcerer: The Code Sorcerer's Apprentice
![Sourcerer](Open-Sourcerer.jpg)
Greetings, fellow developer! Welcome to the realm of the Open-Sourcerer, your trusty assistant in the magical world of open source projects. Open-Sourcerer is here to assist you in finding, integrating, and mastering the arcane arts of open source code.
## Introduction
Open-Sourcerer is your magical companion, capable of traversing the vast landscapes of the internet, particularly GitHub, to discover open source projects that align with your desires. It can also lend you a hand in weaving these projects into your own creations.
### How Does Open-Sourcerer Work?
Open-Sourcerer operates in two phases:
1. **Discovery**: It explores the realms of GitHub to unearth repositories that resonate with your quest.
2. **Integration and Assistance**: Once you've chosen your allies (repositories), Open-Sourcerer helps you integrate them into your own codebase. It can even conjure code snippets to assist you.
## Installation
Before embarking on this mystical journey, ensure you have the following:
- Python (version X.X.X)
- Git (version X.X.X)
- Your favorite code editor (e.g., Visual Studio Code)
Now, let's summon the Open-Sourcerer:
```shell
pip install open-sourcerer
```
## Configuration
Open-Sourcerer must be attuned to your intentions. Let's configure it:
```shell
open-sourcerer configure
```
Follow the instructions to set up your preferences, such as the programming languages and search keywords that align with your project.
## MVP (Minimum Viable Potion) Tasks
1. **Prepare the Cauldron**
- [ ] Create a dedicated workspace/repository for Open-Sourcerer.
2. **Web Scrying**
- [ ] Implement web scraping to search GitHub for relevant open source projects.
3. **Submodule Conjuring**
- [ ] Develop a submodule management system to add selected GitHub repositories as submodules to your workspace.
4. **Bloop Integration**
- [ ] Integrate Open-Sourcerer with the Bloop tool (https://github.com/BloopAI/bloop).
- [ ] Implement code generation and assistance features.
5. **Version Control & Long-Term Memory**
- [ ] Set up version control for the workspace and submodules.
- [ ] Create a vector database to store project information for long-term memory.
6. **Magical Interface (Optional)**
- [ ] Create a user-friendly interface for interacting with Open-Sourcerer.
7. **Testing & Documentation**
- [ ] Ensure the reliability of Open-Sourcerer through thorough testing.
- [ ] Document the magic spells for fellow developers.
## Stretch Goals (Beyond the Sorcerer's Hat)
1. **Advanced Recommendation Alchemy**
- [ ] Enhance the recommendation algorithm using machine learning or NLP.
2. **Explore Other Realms**
- [ ] Expand Open-Sourcerer's reach to platforms like GitLab, Bitbucket, and more.
3. **Code Quality Insights**
- [ ] Add code review and quality analysis features for recommended projects.
4. **Summon a Community**
- [ ] Create a community where developers can collaborate on recommended open source projects.
5. **Editor Enchantments**
- [ ] Develop plugins/extensions for popular code editors to provide real-time assistance.
6. **Language Understanding Scrolls**
- [ ] Improve Open-Sourcerer's natural language understanding capabilities.
7. **Continuous Learning**
- [ ] Implement a mechanism for Open-Sourcerer to learn and adapt from user interactions.
8. **Security Warding**
- [ ] Add security scanning to identify vulnerabilities in recommended projects.
9. **Mobile App (Optional)**
- [ ] Create a mobile app version of Open-Sourcerer for convenience on your travels.
10. **Licensing & Compliance**
- [ ] Ensure Open-Sourcerer checks the licensing of recommended projects for legal compliance.
11. **Performance Enhancements**
- [ ] Optimize Open-Sourcerer's performance for faster results.
## How to Contribute
As we embark on this magical quest, we invite other sorcerers to join us. Feel free to contribute to Open-Sourcerer's development and help us unlock even more mystical powers.
```shell
git clone https://github.com/your-fork/open-sourcerer.git
cd open-sourcerer
# Create a virtual environment and activate it
pip install -r requirements.txt
python setup.py install
```
May your code be bug-free and your projects prosperous! The Open-Sourcerer awaits your commands.
```
Feel free to adapt and expand this README with more details, graphics, and styling to make it engaging and in line with the sorcerer theme.

@ -1,9 +0,0 @@
version: '3'
services:
my-python-app:
container_name: Open-Soucerer
build: .
ports:
- "80:80"
env_file:
- ./.env

@ -1,77 +0,0 @@
import openai
import os
import dotenv
import logging
import gradio as gr
from BingImageCreator import ImageGen
from swarms.models.bing_chat import BingChat
# from swarms.models.bingchat import BingChat
dotenv.load_dotenv(".env")
# Initialize the EdgeGPTModel
model = BingChat()
response = model("Generate")
logging.basicConfig(level=logging.INFO)
accumulated_story = ""
latest_caption = ""
standard_suffix = ""
storyboard = []
caption = "Create comic about opensourcerer a robot wizard"
def generate_images_with_bingchat(caption):
img_path = model.create_img(caption)
img_urls = model.images(caption)
return img_urls
def generate_single_caption(text):
prompt = f"A comic about {text}."
response = model(text)
return response
def interpret_text_with_gpt(text, suffix):
return generate_single_caption(f"{text} {suffix}")
def create_standard_suffix(original_prompt):
return f"In the style of {original_prompt}"
def gradio_interface(text=None, next_button_clicked=False):
global accumulated_story, latest_caption, standard_suffix, storyboard
if not standard_suffix:
standard_suffix = create_standard_suffix(text)
if next_button_clicked:
new_caption = generate_single_caption(latest_caption + " " + standard_suffix)
new_urls = generate_images_with_bingchat(new_caption)
latest_caption = new_caption
storyboard.append((new_urls, new_caption))
elif text:
caption = generate_single_caption(text + " " + standard_suffix)
comic_panel_urls = generate_images_with_bingchat(caption)
latest_caption = caption
storyboard.append((comic_panel_urls, caption))
storyboard_html = ""
for urls, cap in storyboard:
for url in urls:
storyboard_html += f'<img src="{url}" alt="{cap}" width="300"/><br>{cap}<br>'
return storyboard_html
if __name__ == "__main__":
iface = gr.Interface(
fn=gradio_interface,
inputs=[
gr.inputs.Textbox(default="Type your story concept here", optional=True, label="Story Concept"),
gr.inputs.Checkbox(label="Generate Next Part")
],
outputs=[gr.outputs.HTML()],
live=False # Submit button will appear
)
iface.launch()

@ -1,6 +0,0 @@
openai-whisper
py-cord
discord
open-interpreter
elevenlabs
gradio

@ -1,97 +0,0 @@
import gradio_client as grc
import interpreter
import time
import gradio as gr
from pydub import AudioSegment
import io
from elevenlabs import generate, play, set_api_key
import dotenv
dotenv.load_dotenv(".env")
# interpreter.model = "TheBloke/Mistral-7B-OpenOrca-GGUF"
interpreter.auto_run = True
set_api_key("ELEVEN_LABS_API_KEY")
def get_audio_length(audio_bytes):
# Create a BytesIO object from the byte array
byte_io = io.BytesIO(audio_bytes)
# Load the audio data with PyDub
audio = AudioSegment.from_mp3(byte_io)
# Get the length of the audio in milliseconds
length_ms = len(audio)
# Optionally convert to seconds
length_s = length_ms / 1000.0
return length_s
def speak(text):
speaking = True
audio = generate(text=text, voice="Daniel")
play(audio, notebook=True)
audio_length = get_audio_length(audio)
time.sleep(audio_length)
# @title Text-only JARVIS
# @markdown Run this cell for a ChatGPT-like interface.
with gr.Blocks() as demo:
chatbot = gr.Chatbot()
msg = gr.Textbox()
def user(user_message, history):
return "", history + [[user_message, None]]
def bot(history):
user_message = history[-1][0]
history[-1][1] = ""
active_block_type = ""
for chunk in interpreter.chat(user_message, stream=True, display=False):
# Message
if "message" in chunk:
if active_block_type != "message":
active_block_type = "message"
history[-1][1] += chunk["message"]
yield history
# Code
if "language" in chunk:
language = chunk["language"]
if "code" in chunk:
if active_block_type != "code":
active_block_type = "code"
history[-1][1] += f"\n```{language}\n"
history[-1][1] += chunk["code"]
yield history
# Output
if "executing" in chunk:
history[-1][1] += "\n```\n\n```text\n"
yield history
if "output" in chunk:
if chunk["output"] != "KeyboardInterrupt":
history[-1][1] += chunk["output"] + "\n"
yield history
if "end_of_execution" in chunk:
history[-1][1] = history[-1][1].strip()
history[-1][1] += "\n```\n"
yield history
msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then(
bot, chatbot, chatbot
)
if __name__ == "__main__":
demo.queue()
demo.launch(debug=True)

@ -1,2 +0,0 @@
node_modules
.env

@ -1,3 +0,0 @@
DISCORD_TOKEN=
DISCORD_CLIENT_ID=
DISCORD_GUILD_ID=

@ -1,10 +0,0 @@
FROM node:19-slim
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . .
CMD [ "node", "index.js" ]

@ -1,42 +0,0 @@
# Server-Bot
[View on Docker Hub](https://hub.docker.com/r/allenrkeen/server-bot)
### Discord bot to remotely monitor and control a docker based server. Using the docker socket.
Setup is pretty straightforward.
1. Create a new application in the *[discord developer portal](https://discord.com/developers/applications)*
2. Go to the bot section and click *Add Bot*
3. Reset Token and keep the token somewhere secure (This will be referred to as "DISCORD_TOKEN" in .env and docker environment variables)
4. Get the "Application ID" from the General Information tab of your application (This will be referred to as "DISCORD_CLIENT_ID" in .env and docker environment variables)
5. *Optional:* If you have developer mode enabled in Discord, get your server's ID by right-clicking on the server name and clicking "Copy ID" (This will be referred to as "DISCORD_GUILD_ID" in .env and docker environment variables)
- If you skip this, it will still work, but commands will be published globally instead of to your server and can take up to an hour to be available in your server.
- Using the Server ID will be more secure, making the commands available only in the specified server.
6. Run the application in your preffered method.
- Run the docker container with the provided [docker-compose.yml](docker-compose.yml) or the docker run command below.
```bash
docker run -v /var/run/docker.sock:/var/run/docker.sock --name server-bot \
-e DISCORD_TOKEN=your_token_here \
-e DISCORD_CLIENT_ID=your_client_id_here \
-e DISCORD_GUILD_ID=your_guild_id_here \
allenrkeen/server-bot:latest
```
- Clone the repo, cd into the server-bot directory and run "npm install" to install dependencies, then "npm run start" to start the server
7. The program will build an invite link with the correct permissions and put it in the logs. Click the link and confirm the server to add the bot to.
Current commands:
- /allcontainers
- provides container name and status for all containers
- /restartcontainer
- provides an autocomplete list of running containers to select from, or just type in container name then restarts the container
- /stopcontainer
- provides an autocomplete list of running containers to select from, or just type in container name then stops the container
- /startcontainer
- provides an autocomplete list of stopped containers to select from, or just type in container name then starts the container
- /ping
- Replies with "Pong!" when the bot is listening
- /server
- Replies with Server Name and member count, good for testing.

@ -1,22 +0,0 @@
/*
* This file is used to delete all commands from the Discord API.
* Only use this if you want to delete all commands and understand the consequences.
*/
require('dotenv').config();
const token = process.env.DISCORD_TOKEN;
const clientID = process.env.DISCORD_CLIENT_ID;
const guildID = process.env.DISCORD_GUILD_ID;
const { REST, Routes } = require('discord.js');
const fs = require('node:fs');
const rest = new REST({ version: '10' }).setToken(token);
rest.put(Routes.applicationCommands(clientID), { body: [] })
.then(() => console.log('Successfully deleted application (/) commands.'))
.catch(console.error);
rest.put(Routes.applicationGuildCommands(clientID, guildID), { body: [] })
.then(() => console.log('Successfully deleted guild (/) commands.'))
.catch(console.error);

@ -1,53 +0,0 @@
/*
This script pushes all commands in the commands folder to be usable in discord.
*/
require('dotenv').config();
const token = process.env.DISCORD_TOKEN;
const clientID = process.env.DISCORD_CLIENT_ID;
const guildID = process.env.DISCORD_GUILD_ID;
const { REST, Routes } = require('discord.js');
const fs = require('node:fs');
const commands = [];
// Get all commands from the commands folder
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
console.log(commandFiles);
for (const file of commandFiles) {
const command = require(`../commands/${file}`);
commands.push(command.data.toJSON());
}
const rest = new REST({ version: '10' }).setToken(token);
// console.log(commands);
(async () => {
try {
const rest = new REST({ version: '10' }).setToken(token);
console.log('Started refreshing application (/) commands.');
//publish to guild if guildID is set, otherwise publish to global
if (guildID) {
const data = await rest.put(
Routes.applicationGuildCommands(clientID, guildID),
{ body: commands },
);
console.log('Successfully reloaded '+ data.length +' commands.');
} else {
const data = await rest.put(
Routes.applicationCommands(clientID),
{ body: commands },
);
console.log('Successfully reloaded '+ data.length +' commands.');
}
} catch (error) {
console.error(error);
}
})();

@ -1,39 +0,0 @@
/* A command that lists all containers with their status */
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
const Docker = require('node-docker-api').Docker;
module.exports = {
data: new SlashCommandBuilder()
.setName("allcontainers")
.setDescription("Lists all containers"),
async execute(interaction) {
outArray = [];
interaction.reply('Listing all containers...');
//create docker client
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
// get all containers
const containers = await docker.container.list({ all: true});
// create array of containers with name and status
outArray = containers.map(c => {
return {
name: c.data.Names[0].slice(1),
status: c.data.State
};
});
embedCount = Math.ceil(outArray.length / 25);
for (let i = 0; i < embedCount; i++) {
const embed = new EmbedBuilder()
.setTitle('Containers')
.addFields(outArray.slice(i * 25, (i + 1) * 25).map(e => {
return { name: e.name, value: e.status };
}))
.setColor(0x00AE86);
interaction.channel.send({ embeds: [embed] });
}
},
};

@ -1,69 +0,0 @@
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
const Docker = require('node-docker-api').Docker;
module.exports = {
data: new SlashCommandBuilder()
.setName("restartcontainer")
.setDescription("Restarts a Docker container")
.addStringOption(option =>
option.setName('container')
.setDescription('The container to restart')
.setRequired(true)
.setAutocomplete(true)),
async autocomplete(interaction) {
try {
// Create docker client
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
// Get list of running containers
const containers = await docker.container.list({ all: true, filters: { status: ['running'] } });
const runningContainers = containers.map(c => c.data.Names[0].slice(1));
// Filter list of containers by focused value
const focusedValue = interaction.options.getFocused(true);
const filteredContainers = runningContainers.filter(container => container.startsWith(focusedValue.value));
//slice if more than 25
let sliced;
if (filteredContainers.length > 25) {
sliced = filteredContainers.slice(0, 25);
} else {
sliced = filteredContainers;
}
// Respond with filtered list of containers
await interaction.respond(sliced.map(container => ({ name: container, value: container })));
} catch (error) {
// Handle error
console.error(error);
await interaction.reply('An error occurred while getting the list of running containers.');
}
},
async execute(interaction) {
try {
// create docker client
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
// Get container name from options
const container = interaction.options.getString('container');
// Restart container
await interaction.reply(`Restarting container "${container}"...`);
const containers = await docker.container.list({ all: true, filters: { name: [container] } });
if (containers.length === 0) {
await interaction.followUp(`Container "${container}" does not exist.`);
throw new Error(`Container "${container}" does not exist.`);
}
await containers[0].restart();
// Confirm that container was restarted
await interaction.followUp(`Container "${container}" was successfully restarted.`);
} catch (error) {
// Handle error
console.error(error);
await interaction.followUp(`An error occurred while trying to restart the container "${container}".`);
}
}
};

@ -1,92 +0,0 @@
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
const Docker = require('node-docker-api').Docker;
module.exports = {
data: new SlashCommandBuilder()
.setName("startcontainer")
.setDescription("Starts a Docker container")
.addStringOption(option =>
option.setName('container')
.setDescription('The container to start')
.setRequired(true)
.setAutocomplete(true)),
async autocomplete(interaction) {
try {
// Create docker client
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
// Get list of running containers
const containers = await docker.container.list({ all: true, filters: { status: ['exited'] } });
const runningContainers = containers.map(c => c.data.Names[0].slice(1));
// Filter list of containers by focused value
const focusedValue = interaction.options.getFocused(true);
const filteredContainers = runningContainers.filter(container => container.startsWith(focusedValue.value));
//slice if more than 25
let sliced;
if (filteredContainers.length > 25) {
sliced = filteredContainers.slice(0, 25);
} else {
sliced = filteredContainers;
}
// Respond with filtered list of containers
await interaction.respond(sliced.map(container => ({ name: container, value: container })));
} catch (error) {
// Handle error
console.error(error);
await interaction.reply('An error occurred while getting the list of running containers.');
}
},
async execute(interaction) {
try {
// Get container name from options
const containerName = interaction.options.getString('container');
// Start container in interactive mode
await interaction.reply(`Starting container "${containerName}" in interactive mode...`);
const container = docker.getContainer(containerName);
const info = await container.inspect();
if (!info) {
await interaction.followUp(`Container "${containerName}" does not exist.`);
throw new Error(`Container "${containerName}" does not exist.`);
}
await container.start({
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: true,
OpenStdin: true,
StdinOnce: false
});
// Attach to container's streams
const stream = await container.attach({
stream: true,
stdin: true,
stdout: true,
stderr: true
});
// Use socket.io for real-time communication with the container
io.on('connection', (socket) => {
socket.on('containerInput', (data) => {
stream.write(data + '\n'); // Send input to the container
});
stream.on('data', (data) => {
socket.emit('containerOutput', data.toString()); // Send container's output to the client
});
});
// Confirm that container was started
await interaction.followUp(`Container "${containerName}" was successfully started in interactive mode.`);
} catch (error) {
// Handle error
console.error(error);
await interaction.followUp(`An error occurred while trying to start the container "${containerName}" in interactive mode.`);
}
},
};

@ -1,68 +0,0 @@
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
const Docker = require('node-docker-api').Docker;
module.exports = {
data: new SlashCommandBuilder()
.setName("stopcontainer")
.setDescription("Stops a Docker container")
.addStringOption(option =>
option.setName('container')
.setDescription('The container to stop')
.setRequired(true)
.setAutocomplete(true)),
async autocomplete(interaction) {
try {
// Create docker client
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
// Get list of running containers
const containers = await docker.container.list({ all: true, filters: { status: ['running'] } });
const runningContainers = containers.map(c => c.data.Names[0].slice(1));
// Filter list of containers by focused value
const focusedValue = interaction.options.getFocused(true);
const filteredContainers = runningContainers.filter(container => container.startsWith(focusedValue.value));
//slice if more than 25
let sliced;
if (filteredContainers.length > 25) {
sliced = filteredContainers.slice(0, 25);
} else {
sliced = filteredContainers;
}
// Respond with filtered list of containers
await interaction.respond(sliced.map(container => ({ name: container, value: container })));
} catch (error) {
// Handle error
console.error(error);
await interaction.reply('An error occurred while getting the list of running containers.');
}
},
async execute(interaction) {
try {
// create docker client
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
// Get container name from options
const container = interaction.options.getString('container');
// Restart container
await interaction.reply(`Stopping container "${container}"...`);
const containers = await docker.container.list({ all: true, filters: { name: [container] } });
if (containers.length === 0) {
await interaction.followUp(`Container "${container}" does not exist.`);
throw new Error(`Container "${container}" does not exist.`);
}
await containers[0].stop();
// Confirm that container was restarted
await interaction.followUp(`Container "${container}" was successfully stopped.`);
} catch (error) {
// Handle error
console.error(error);
await interaction.followUp(`An error occurred while trying to stop the container "${container}".`);
}
}
};

@ -1,12 +0,0 @@
version: '3'
services:
server-bot:
container_name: Leonidas
build:
context: .
dockerfile: Dockerfile
volumes:
- /var/run/docker.sock:/var/run/docker.sock #required
env_file:
- ./.env # environment:

@ -1,89 +0,0 @@
require('dotenv').config();
const fs = require('node:fs');
const path = require('node:path');
const token = process.env.DISCORD_TOKEN;
const clientID = process.env.DISCORD_CLIENT_ID;
// Require the necessary discord.js classes
const { Client, Collection, Events, GatewayIntentBits } = require('discord.js');
// Create a new client instance
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
//run backend/deployCommands.js
const { exec } = require('child_process');
exec('node backend/deployCommands.js', (err, stdout, stderr) => {
if (err) {
//some err occurred
console.error(err);
} else {
// print complete output
console.log(stdout);
}
});
// When the client is ready, run this code
client.once(Events.ClientReady, c => {
console.log(`Ready! Logged in as ${c.user.tag}`);
});
// Log in to Discord with your client's token
client.login(token);
// Create a new collection for commands
client.commands = new Collection();
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
// Set a new item in the Collection with the key as the name of the command and the value as the exported module
if ('data' in command && 'execute' in command) {
client.commands.set(command.data.name, command);
} else {
console.log(`Command ${file} is missing 'data' or 'execute'`);
}
}
//build and display invite link
const inviteLink = 'https://discord.com/oauth2/authorize?client_id='+clientID+'&permissions=2147534912&scope=bot%20applications.commands';
console.log(`Invite link: ${inviteLink}`);
// execute on slash command
client.on(Events.InteractionCreate, async interaction => {
if (interaction.isChatInputCommand()) {
const command = client.commands.get(interaction.commandName);
if (!command) {
console.error('No command matching ${interaction.commandName} was found.');
return;
}
try {
await command.execute(interaction);
} catch (error) {
console.error(error);
// await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
}
} else if (interaction.isAutocomplete()) {
const command = client.commands.get(interaction.commandName);
if (!command) {
console.error('No command matching ${interaction.commandName} was found.');
return;
}
try {
await command.autocomplete(interaction);
} catch (error) {
console.error(error);
// await interaction.({ content: 'There was an error while executing this command!', ephemeral: true });
}
}
});

@ -1,723 +0,0 @@
{
"name": "server-bot",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "server-bot",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"discord.js": "^14.7.1",
"dockerode": "^3.3.4",
"dotenv": "^16.0.3",
"node-docker-api": "^1.1.22"
}
},
"node_modules/@balena/dockerignore": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz",
"integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q=="
},
"node_modules/@discordjs/builders": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.4.0.tgz",
"integrity": "sha512-nEeTCheTTDw5kO93faM1j8ZJPonAX86qpq/QVoznnSa8WWcCgJpjlu6GylfINTDW6o7zZY0my2SYdxx2mfNwGA==",
"dependencies": {
"@discordjs/util": "^0.1.0",
"@sapphire/shapeshift": "^3.7.1",
"discord-api-types": "^0.37.20",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.2",
"tslib": "^2.4.1"
},
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/@discordjs/collection": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.3.0.tgz",
"integrity": "sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg==",
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/@discordjs/rest": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.5.0.tgz",
"integrity": "sha512-lXgNFqHnbmzp5u81W0+frdXN6Etf4EUi8FAPcWpSykKd8hmlWh1xy6BmE0bsJypU1pxohaA8lQCgp70NUI3uzA==",
"dependencies": {
"@discordjs/collection": "^1.3.0",
"@discordjs/util": "^0.1.0",
"@sapphire/async-queue": "^1.5.0",
"@sapphire/snowflake": "^3.2.2",
"discord-api-types": "^0.37.23",
"file-type": "^18.0.0",
"tslib": "^2.4.1",
"undici": "^5.13.0"
},
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/@discordjs/util": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.1.0.tgz",
"integrity": "sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ==",
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/@sapphire/async-queue": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz",
"integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@sapphire/shapeshift": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.8.1.tgz",
"integrity": "sha512-xG1oXXBhCjPKbxrRTlox9ddaZTvVpOhYLmKmApD/vIWOV1xEYXnpoFs68zHIZBGbqztq6FrUPNPerIrO1Hqeaw==",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"lodash": "^4.17.21"
},
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@sapphire/snowflake": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.4.0.tgz",
"integrity": "sha512-zZxymtVO6zeXVMPds+6d7gv/OfnCc25M1Z+7ZLB0oPmeMTPeRWVPQSS16oDJy5ZsyCOLj7M6mbZml5gWXcVRNw==",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
},
"node_modules/@types/node": {
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
},
"node_modules/@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"dependencies": {
"tweetnacl": "^0.14.3"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/buildcheck": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.3.tgz",
"integrity": "sha512-pziaA+p/wdVImfcbsZLNF32EiWyujlQLwolMqUQE8xpKNOH7KmZQaY8sXN7DGOEzPAElo9QTaeNRfGnf3iOJbA==",
"optional": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"streamsearch": "^1.1.0"
},
"engines": {
"node": ">=10.16.0"
}
},
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/cpu-features": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.4.tgz",
"integrity": "sha512-fKiZ/zp1mUwQbnzb9IghXtHtDoTMtNeb8oYGx6kX2SYfhnG0HNdBEBIzB9b5KlXu5DQPhfy3mInbBxFcgwAr3A==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"buildcheck": "0.0.3",
"nan": "^2.15.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/discord-api-types": {
"version": "0.37.24",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.24.tgz",
"integrity": "sha512-1+Fb4huJCihdbkJLcq2p7nBmtlmAryNwjefT8wwJnL8c7bc7WA87Oaa5mbLe96QvZyfwnwRCDX40H0HhcVV50g=="
},
"node_modules/discord.js": {
"version": "14.7.1",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.7.1.tgz",
"integrity": "sha512-1FECvqJJjjeYcjSm0IGMnPxLqja/pmG1B0W2l3lUY2Gi4KXiyTeQmU1IxWcbXHn2k+ytP587mMWqva2IA87EbA==",
"dependencies": {
"@discordjs/builders": "^1.4.0",
"@discordjs/collection": "^1.3.0",
"@discordjs/rest": "^1.4.0",
"@discordjs/util": "^0.1.0",
"@sapphire/snowflake": "^3.2.2",
"@types/ws": "^8.5.3",
"discord-api-types": "^0.37.20",
"fast-deep-equal": "^3.1.3",
"lodash.snakecase": "^4.1.1",
"tslib": "^2.4.1",
"undici": "^5.13.0",
"ws": "^8.11.0"
},
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/docker-modem": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.6.tgz",
"integrity": "sha512-h0Ow21gclbYsZ3mkHDfsYNDqtRhXS8fXr51bU0qr1dxgTMJj0XufbzX+jhNOvA8KuEEzn6JbvLVhXyv+fny9Uw==",
"dependencies": {
"debug": "^4.1.1",
"readable-stream": "^3.5.0",
"split-ca": "^1.0.1",
"ssh2": "^1.11.0"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/dockerode": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.4.tgz",
"integrity": "sha512-3EUwuXnCU+RUlQEheDjmBE0B7q66PV9Rw5NiH1sXwINq0M9c5ERP9fxgkw36ZHOtzf4AGEEYySnkx/sACC9EgQ==",
"dependencies": {
"@balena/dockerignore": "^1.0.2",
"docker-modem": "^3.0.0",
"tar-fs": "~2.0.1"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/dotenv": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
"engines": {
"node": ">=12"
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/file-type": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-18.0.0.tgz",
"integrity": "sha512-jjMwFpnW8PKofLE/4ohlhqwDk5k0NC6iy0UHAJFKoY1fQeGMN0GDdLgHQrvCbSpMwbqzoCZhRI5dETCZna5qVA==",
"dependencies": {
"readable-web-to-node-stream": "^3.0.2",
"strtok3": "^7.0.0",
"token-types": "^5.0.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
},
"node_modules/jsonparse": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz",
"integrity": "sha512-fw7Q/8gFR8iSekUi9I+HqWIap6mywuoe7hQIg3buTVjuZgALKj4HAmm0X6f+TaL4c9NJbvyFQdaI2ppr5p6dnQ==",
"engines": [
"node >= 0.2.0"
]
},
"node_modules/JSONStream": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz",
"integrity": "sha512-8XbSFFd43EG+1thjLNFIzCBlwXti0yKa7L+ak/f0T/pkC+31b7G41DXL/JzYpAoYWZ2eCPiu4IIqzijM8N0a/w==",
"dependencies": {
"jsonparse": "0.0.5",
"through": ">=2.2.7 <3"
},
"bin": {
"JSONStream": "index.js"
},
"engines": {
"node": "*"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.snakecase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
},
"node_modules/memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
"integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==",
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/nan": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
"integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==",
"optional": true
},
"node_modules/node-docker-api": {
"version": "1.1.22",
"resolved": "https://registry.npmjs.org/node-docker-api/-/node-docker-api-1.1.22.tgz",
"integrity": "sha512-8xfOiuLDJQw+l58i66lUNQhRhS5fAExqQbLolmyqMucrsDON7k7eLMIHphcBwwB7utwCHCQkcp73gSAmzSiAiw==",
"dependencies": {
"docker-modem": "^0.3.1",
"memorystream": "^0.3.1"
}
},
"node_modules/node-docker-api/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/node-docker-api/node_modules/docker-modem": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-0.3.7.tgz",
"integrity": "sha512-4Xn4ZVtc/2DEFtxY04lOVeF7yvxwXGVo0sN8FKRBnLhBcwQ78Hb56j+Z5yAXXUhoweVhzGeBeGWahS+af0/mcg==",
"dependencies": {
"debug": "^2.6.0",
"JSONStream": "0.10.0",
"readable-stream": "~1.0.26-4",
"split-ca": "^1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/node-docker-api/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/node-docker-api/node_modules/readable-stream": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"node_modules/node-docker-api/node_modules/string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/peek-readable": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz",
"integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==",
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/readable-web-to-node-stream": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
"integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
"dependencies": {
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/split-ca": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz",
"integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ=="
},
"node_modules/ssh2": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.11.0.tgz",
"integrity": "sha512-nfg0wZWGSsfUe/IBJkXVll3PEZ//YH2guww+mP88gTpuSU4FtZN7zu9JoeTGOyCNx2dTDtT9fOpWwlzyj4uOOw==",
"hasInstallScript": true,
"dependencies": {
"asn1": "^0.2.4",
"bcrypt-pbkdf": "^1.0.2"
},
"engines": {
"node": ">=10.16.0"
},
"optionalDependencies": {
"cpu-features": "~0.0.4",
"nan": "^2.16.0"
}
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/strtok3": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz",
"integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"peek-readable": "^5.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/tar-fs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
"integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.0.0"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
},
"node_modules/token-types": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz",
"integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/ts-mixer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.2.tgz",
"integrity": "sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A=="
},
"node_modules/tslib": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
},
"node_modules/undici": {
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.14.0.tgz",
"integrity": "sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ==",
"dependencies": {
"busboy": "^1.6.0"
},
"engines": {
"node": ">=12.18"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

@ -1,30 +0,0 @@
{
"name": "server-bot",
"version": "1.0.0",
"description": "Discord bot to remotely monitor and control a docker based server",
"main": "index.js",
"scripts": {
"start": "nodemon index.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/allenrkeen/server-bot.git"
},
"keywords": [
"discord",
"docker",
"linux",
"selfhost"
],
"author": "allenrkeen",
"license": "MIT",
"bugs": {
"url": "https://github.com/allenrkeen/server-bot/issues"
},
"homepage": "https://github.com/allenrkeen/server-bot#readme",
"dependencies": {
"discord.js": "^14.7.1",
"dotenv": "^16.0.3",
"node-docker-api": "^1.1.22"
}
}
Loading…
Cancel
Save