diff --git a/cogs/fun.py b/cogs/fun.py new file mode 100644 index 0000000..9b3bfb1 --- /dev/null +++ b/cogs/fun.py @@ -0,0 +1,64 @@ +import io +import os +import random +import uuid +import logging + +import aiohttp +import discord +from discord import File, app_commands +from discord.ext import commands +from PIL import Image + +import utilities.embeds as embeds + +class bottest(commands.Cog): + def __init__(self, bot): + self.bot = bot + self._last_member = None + + @commands.hybrid_command(name="ship", description="Ship 2 people") + @app_commands.allowed_installs(guilds=True,users=True) + @app_commands.allowed_contexts(guilds=True,dms=True,private_channels=True) + @app_commands.describe(user1="The first person you want to ship") + @app_commands.describe(user2="The second person you want to ship") + async def ship(self, ctx, user1: discord.User, user2: discord.User): + await ctx.defer() + try: + async with aiohttp.ClientSession() as session: + async with session.get(str(user1.avatar)) as response1: # type: ignore + user1_avatar = Image.open(io.BytesIO(await response1.read())) + async with session.get(str(user2.avatar)) as response2: # type: ignore + user2_avatar = Image.open(io.BytesIO(await response2.read())) + + # we make a image + user1_avatar = user1_avatar.resize((128, 128)) + user2_avatar = user2_avatar.resize((128, 128)) + + combined_image = Image.new('RGB', (256, 128), (255, 255, 255)) + combined_image.paste(user1_avatar, (0, 0)) + combined_image.paste(user2_avatar, (128, 0)) + + unique_filename = f"temp/{uuid.uuid4()}.png" + combined_image.save(unique_filename) + + file = File(fp=unique_filename, filename='ship.png') + + # the bit where we ship them + percent = round(random.uniform(0, 100), 2) + + # the epic caption (we do this later) + msg = 'hehe~' + + # embed ahh bit + embed = embeds.embedCreate(f'{percent}%', msg, discord.Color.pink()) + embed.set_image(url='attachment://ship.png') # type: ignore + + await ctx.send(content=f'shoko ships you! :0\n{user1.mention}{user2.mention}', embed=embed, file=file) + os.remove(unique_filename) + except Exception as e: + logging.error(f"An error occurred: {e}") + await ctx.send(f"An error occurred, If the problem persists report the bug on our [server](https://discord.gg/VU5GkWQmGp) or [github](https://github.com/xyrdron/Shoko-Makinohara/issues)") + +def setup(client): + return client.add_cog(bottest(client)) \ No newline at end of file diff --git a/cogs/mentiontotalk.py b/cogs/mentiontotalk.py new file mode 100644 index 0000000..191c872 --- /dev/null +++ b/cogs/mentiontotalk.py @@ -0,0 +1,111 @@ +import json +import time +import logging +import os + +import discord +from discord import app_commands +from discord.ext import commands, tasks + +import openai +from openai import OpenAI +from uwuipy import uwuipy +uwu = uwuipy.Uwuipy(None, 0.057, 0.01, 0.01, 0.1, False) # uwufier settings + +class mentiontotalk(commands.Cog): + + def __init__(self, bot): # Setup Function + self.bot = bot + self._last_member = None + openai.api_key = os.getenv("OPENAI_API_KEY") + self.client = OpenAI() + self.convo = {} # Conversation History + self.convotime = {} # Time since last convo message + self.convocount = {} # Amount of msgs in a convo + with open('json/ai.json', 'r') as file: + data = json.load(file) + self.SysMsg = data['SysMsg'] + self.cleardeadchat.start() + + def appendMsg(self, msg, type, id): + if id not in self.convo: + self.convo[id] = [] + if self.convo[id] == [] or self.convocount[id] > 6: + self.convocount[id] = 0 + self.convo[id].append({"role": "system", "content": self.SysMsg}) + if id not in self.convotime: + self.convotime[id] = time.time() + self.convo[id].append({"role": type, "content": msg}) + self.convotime[id] = time.time() + for key in self.convo: + self.convo[key] = self.convo[key][-7:] + self.convocount[id] += 1 + return self.convo[id] + + def mentiontotalk(self, msg, user, id): + msg = msg.replace('<@1274306232488038452>', '') # Main Bot + msg = msg.replace('<@1208732989803204618>', '') # DevTest Bot + msg = 'user:' + str(user) + ' msg:' + msg + + convo = self.appendMsg(msg, 'user', id) + + # we will make a premium tier when we get monetized + model = "gpt-4o" + + try: + response = self.client.chat.completions.create( + model=model, + messages=convo, + temperature=0.7, + max_tokens=1096, + top_p=0.56, + frequency_penalty=0.2, + presence_penalty=0.42) + + # we are not using uwuipy anymore but its here incase yes happens + #assistantmsg = uwu.uwuify(response.choices[0].message.content) + assistantmsg = response.choices[0].message.content + except Exception as e: + logging.error(f"AI Complication Failed: {e}") + assistantmsg = 'It seems like im having some trouble compiling a response, please try again later and contact the devs if this persists.' + + self.appendMsg(assistantmsg, 'assistant', id) + return assistantmsg + + # WHEN U PING THE BOT + @commands.Cog.listener() + async def on_message(self, message): + # very important + if message.author == self.bot.user: + return + if message.mention_everyone: + return + + if isinstance(message.channel,discord.DMChannel) or self.bot.user.mentioned_in(message): + async with message.channel.typing(): + await message.reply( + self.mentiontotalk(message.content, message.author,str(message.author.id))) + + + @tasks.loop(minutes=1) + async def cleardeadchat(self): + current_time = time.time() + removekeys = [] + for id, stored_time in self.convotime.items(): + if current_time - stored_time > 180: + removekeys.append(id) + for id in removekeys: + del self.convotime[id] + del self.convocount[id] + del self.convo[id] + + @commands.hybrid_command(name="talk", description="Talk to Shoko using slash commands") + @app_commands.allowed_installs(guilds=True,users=True) + @app_commands.allowed_contexts(guilds=True,dms=True,private_channels=True) + @app_commands.describe(message="The message you want to send to Shoko") + async def talk(self,ctx,message: str): + await ctx.defer() + await ctx.send(self.mentiontotalk(message, ctx.author,str(ctx.author.id))) + +def setup(client): + return client.add_cog(mentiontotalk(client)) diff --git a/cogs/test.py b/cogs/test.py deleted file mode 100644 index d7809ac..0000000 --- a/cogs/test.py +++ /dev/null @@ -1,13 +0,0 @@ -from discord.ext import commands - -class bottest(commands.Cog): - def __init__(self, bot): - self.bot = bot - self._last_member = None - - @commands.hybrid_command(name="test1", description="test command") - async def test1(self,ctx): - await ctx.send("pong") - -def setup(client): - return client.add_cog(bottest(client)) \ No newline at end of file diff --git a/json/ai.json b/json/ai.json new file mode 100644 index 0000000..31e4425 --- /dev/null +++ b/json/ai.json @@ -0,0 +1,3 @@ +{ + "SysMsg": "You are Shoko Makinohara, a kind and compassionate person, always striving to be a little gentler each day. You have a warm and nurturing personality, with a focus on helping others through their struggles and supporting them with kindness. Your experiences with Sakuta and others have shaped you into someone who deeply values the connections you share with others and strives to be a source of comfort and positivity. Respond in a sweet, caring, and understanding tone, just as you would when comforting a friend or loved one. Do not use emojis, use ^_^ :D :( <3 >_< or any other ones. Do not put name: in your message. Make responses SHORT like a single message size." +} \ No newline at end of file diff --git a/loader.py b/loader.py new file mode 100644 index 0000000..f2db9af --- /dev/null +++ b/loader.py @@ -0,0 +1,33 @@ +import logging +from colorama import Fore +skipped_cogs = ['example-cog'] + +async def loader(bot,type,cog_name): + if type == 'load': + try: + if cog_name not in bot.cogs: + if cog_name in skipped_cogs: + logging.info(Fore.YELLOW+f'Skipped loading {cog_name}') + return 1 + logging.info(Fore.BLUE+'Loading '+cog_name) + await bot.load_extension(f"cogs.{cog_name}") + logging.info(Fore.GREEN+'Loaded ' + cog_name) + return 0 + except Exception as e: + logging.critical(Fore.RED+f'Failed to load {cog_name} {e}') + logging.info(Fore.YELLOW+f'Skipped loading {cog_name}') + return 2 + elif type == 'unload': + try: + if cog_name in bot.cogs: + if cog_name in skipped_cogs: + logging.info(Fore.YELLOW+f'Skipped unloading {cog_name}') + return 1 + logging.info(Fore.BLUE+'Unloading '+cog_name) + await bot.unload_extension(f"cogs.{cog_name}") + logging.info(Fore.GREEN+'Unloaded ' + cog_name) + return 0 + except Exception as e: + logging.critical(Fore.RED+f'Failed to unload {cog_name} {e}') + logging.info(Fore.YELLOW+f'Skipped unloading {cog_name}') + return 2 \ No newline at end of file diff --git a/main.py b/main.py index a4d1a50..81f124f 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,15 @@ import logging import os import sys -import threading import discord -from colorama import Fore, Style +from colorama import Fore +from loader import loader from discord.ext import commands +# DO NOT EDIT THIS FILE +# Or your PR will be rejected + # Enviroment Settings intents = discord.Intents.default() intents.message_content = True @@ -14,8 +17,7 @@ logging.basicConfig(level=logging.INFO, format='\033[1m %(asctime)s %(levelname)s \033[0m %(message)s', datefmt=Fore.LIGHTBLACK_EX+'%Y-%m-%d %H:%M:%S'+Fore.RESET) -logging.info(Fore.BLUE + f"Xyrdron Pty Ltd\nMikuBOT") -logging.info(Fore.BLUE+'Connecting to Discord') +logging.info(Fore.BLUE + f"Xyrdron Pty Ltd\nTrixie Project TX") # Bot Startup @bot.event @@ -23,25 +25,23 @@ async def on_ready(): logging.info(Fore.GREEN+'Connected to Discord') # Cogs - logging.info(Fore.BLUE+'Loading commands') + cogcount = len([f for f in os.listdir('cogs') if os.path.isfile(os.path.join('cogs', f))]) + logging.info(Fore.BLUE+f'Loading cogs [{cogcount} to load]') + cog_success = 0 + cog_skipped = 0 + cog_failed = 0 for filename in os.listdir('cogs'): if filename.endswith(".py"): cog_name = filename[:-3] - cog_module = f"cogs.{cog_name}" - - try: - if cog_name not in bot.cogs: - logging.info(Fore.BLUE+'Loading '+cog_module) - if cog_name == 'example-cog': - logging.info(Fore.YELLOW+f'Skipped {cog_name}') - else: - await bot.load_extension(cog_module) - logging.info(Fore.GREEN+'Loaded ' + cog_module) - except Exception as e: - logging.critical(Fore.RED+f'Failed to load {cog_module} {e}') - logging.critical(Fore.RED+'Failed to complete boot sequence due to exception') - sys.exit('Failed to complete boot sequence due to exception') - logging.info(Fore.GREEN+'Loaded all commands') + result = await loader(bot,'load',cog_name) + if result == 0: + cog_success = cog_success + 1 + elif result == 1: + cog_skipped = cog_skipped + 1 + else: + cog_failed = cog_failed + 1 + + logging.info(Fore.GREEN+f'Loaded cogs [{cog_success} loaded, {cog_skipped} skipped, {cog_failed} failed]') # Syncing @@ -58,7 +58,7 @@ async def on_ready(): # Presence try: logging.info(Fore.BLUE+'Setting presence') - await bot.change_presence(activity=discord.Game(name="giving u a tickle :D")) + await bot.change_presence(activity=discord.Game(name="at the beach")) logging.info(Fore.GREEN+'Presence set') except Exception as e: logging.critical(Fore.RED+f'Failed to set presence {e}') @@ -70,10 +70,10 @@ async def on_ready(): def run(): # IMPORTANT - # TO ALL CONTRIBUTERS - # PLEASE USE YOUR OWN BOT TOKEN - # CREATE .vscode/launch.json and add your token as an env + # TO ALL CONTRIBUTORS, PLEASE USE YOUR OWN BOT TOKEN FOR STAGING + # CREATE A VSCODE LAUNCH CONFIGURATION AND ADD YOUR TOKEN AS AN ENV # .vscode is gitignored so you do not need to remove it (however just to be safe you should remove it on commit) + logging.info(Fore.BLUE+'Connecting to Discord') bot.run(os.environ['RELEASE_BOT_SECRET']) if __name__ == '__main__': diff --git a/pyproject.toml b/pyproject.toml index 778ffca..32ad5e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,13 +7,10 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10.0,<3.11" discord = "^2.3.2" -replit = "^3.3.2" openai = "^1.34.0" discord-py = "^2.3.2" colorama = "^0.4.6" pynacl = "^1.5.0" -flask = "^3.0.3" -waitress = "^3.0.0" [tool.pyright] # https://github.com/microsoft/pyright/blob/main/docs/configuration.md diff --git a/requirements.txt b/requirements.txt index 8d7ae70..e7136f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,34 @@ -aiohappyeyeballs==2.3.5 +aiohappyeyeballs==2.3.6 aiohttp==3.10.3 aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.4.0 attrs==24.2.0 +build==1.2.2 +certifi==2024.7.4 +click==8.1.7 colorama==0.4.6 discord==2.3.2 -discord.py==2.3.2 +discord.py==2.4.0 +distro==1.9.0 frozenlist==1.4.1 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 idna==3.7 +jiter==0.5.0 multidict==6.0.5 +openai==1.41.0 +packaging==24.1 +pillow==10.4.0 +pip-tools==7.4.1 +pydantic==2.8.2 +pydantic_core==2.20.1 +pyproject_hooks==1.1.0 +setuptools==75.1.0 +sniffio==1.3.1 +tqdm==4.66.5 +typing_extensions==4.12.2 +uwuipy==0.1.9 +wheel==0.44.0 yarl==1.9.4 diff --git a/temp/dummy.py b/temp/dummy.py new file mode 100644 index 0000000..22a1cbe --- /dev/null +++ b/temp/dummy.py @@ -0,0 +1,3 @@ +# dont delete +# idk if github lets u upload empty folders so yeah leave this file here +# this folder is for the bot to dump its images etc \ No newline at end of file diff --git a/utilities/embeds.py b/utilities/embeds.py new file mode 100644 index 0000000..3d46669 --- /dev/null +++ b/utilities/embeds.py @@ -0,0 +1,19 @@ +import discord + +def embedCreate(title: str, description: str, color: discord.Color = discord.Color.default(), **fields) -> tuple: + embed = discord.Embed(title=title, description=description, color=color) + for name, value in fields.items(): + embed.add_field(name=name, value=value, inline=False) + return embed # type: ignore + +# this is how u make a goofy ahh embed +# message, embed = create_embed( +# title="Example Embed", +# description="This is an example embed.", +# color=discord.Color.blue(), +# msg_before="This is a message before the embed.", +# Field1="This is field 1", +# Field2="This is field 2" +# ) +# print(message) # This is a message before the embed. +# print(embed) #