From ccfa61fdf18def678d01f962d92f438f1b78899c Mon Sep 17 00:00:00 2001 From: Zack Date: Mon, 23 Oct 2023 20:27:49 -0500 Subject: [PATCH] Add BingBot Former-commit-id: 581e9b9b697e712c9be5352149dbd3a4753289a0 --- apps/BingBot/.env.example | 4 + apps/BingBot/Dockerfile | 6 + apps/BingBot/bing_bot.py | 115 +++++++++ apps/BingBot/cogs/edgegpt.py | 148 ++++++++++++ apps/BingBot/cogs/event.py | 223 ++++++++++++++++++ apps/BingBot/cogs/help.py | 17 ++ apps/BingBot/compose.yaml | 10 + apps/BingBot/cookies.json | 6 + apps/BingBot/core/classes.py | 5 + apps/BingBot/requirements.txt | 4 + apps/BingBot/src/imageCreate.py | 31 +++ apps/BingBot/src/log.py | 67 ++++++ apps/BingBot/src/response.py | 117 ++++++++++ apps/discord.py | 233 +++++++++---------- playground/models/bingchat.py => bingchat.py | 8 +- playground/apps/bing_discord.py | 7 +- swarms/models/bing_chat.py | 2 +- 17 files changed, 875 insertions(+), 128 deletions(-) create mode 100644 apps/BingBot/.env.example create mode 100644 apps/BingBot/Dockerfile create mode 100644 apps/BingBot/bing_bot.py create mode 100644 apps/BingBot/cogs/edgegpt.py create mode 100644 apps/BingBot/cogs/event.py create mode 100644 apps/BingBot/cogs/help.py create mode 100644 apps/BingBot/compose.yaml create mode 100644 apps/BingBot/cookies.json create mode 100644 apps/BingBot/core/classes.py create mode 100644 apps/BingBot/requirements.txt create mode 100644 apps/BingBot/src/imageCreate.py create mode 100644 apps/BingBot/src/log.py create mode 100644 apps/BingBot/src/response.py rename playground/models/bingchat.py => bingchat.py (64%) diff --git a/apps/BingBot/.env.example b/apps/BingBot/.env.example new file mode 100644 index 00000000..341406f7 --- /dev/null +++ b/apps/BingBot/.env.example @@ -0,0 +1,4 @@ +DISCORD_BOT_TOKEN= +MENTION_CHANNEL_ID= +AUTH_COOKIE= +AUTH_COOKIE_SRCHHPGUSR= diff --git a/apps/BingBot/Dockerfile b/apps/BingBot/Dockerfile new file mode 100644 index 00000000..e276b5c8 --- /dev/null +++ b/apps/BingBot/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.9.16 +WORKDIR /bot +COPY requirements.txt /bot/ +RUN pip install -r requirements.txt +COPY . /bot +CMD python bot.py diff --git a/apps/BingBot/bing_bot.py b/apps/BingBot/bing_bot.py new file mode 100644 index 00000000..4a562411 --- /dev/null +++ b/apps/BingBot/bing_bot.py @@ -0,0 +1,115 @@ +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")) + + + + + + diff --git a/apps/BingBot/cogs/edgegpt.py b/apps/BingBot/cogs/edgegpt.py new file mode 100644 index 00000000..683780db --- /dev/null +++ b/apps/BingBot/cogs/edgegpt.py @@ -0,0 +1,148 @@ +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)) diff --git a/apps/BingBot/cogs/event.py b/apps/BingBot/cogs/event.py new file mode 100644 index 00000000..f42b6e5b --- /dev/null +++ b/apps/BingBot/cogs/event.py @@ -0,0 +1,223 @@ +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)) diff --git a/apps/BingBot/cogs/help.py b/apps/BingBot/cogs/help.py new file mode 100644 index 00000000..16ecff78 --- /dev/null +++ b/apps/BingBot/cogs/help.py @@ -0,0 +1,17 @@ +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", description="[see more](https://github.com/FuseFairy/DiscordBot-EdgeGPT/blob/main/README.md)\n\n**COMMANDS -**") + 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)) \ No newline at end of file diff --git a/apps/BingBot/compose.yaml b/apps/BingBot/compose.yaml new file mode 100644 index 00000000..f574f912 --- /dev/null +++ b/apps/BingBot/compose.yaml @@ -0,0 +1,10 @@ +version: '3' + +services: + discord_edgegpt: + build: . + environment: + - DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN} + volumes: + - ./cookies.json:/bot/cookies.json + - ./config.yml:/bot/config.yml diff --git a/apps/BingBot/cookies.json b/apps/BingBot/cookies.json new file mode 100644 index 00000000..4d0748fc --- /dev/null +++ b/apps/BingBot/cookies.json @@ -0,0 +1,6 @@ +[ + { + "name": "cookie1", + "value": "1lEXeWRSIPUsQ0S3tdAc3v7BexGK2qBlzsXz8j52w_HNBoOsegjiwRySQHmfoWduHVUxSXo6cETPP2qNrYWAz6k7wn43WGO9i7ll9_Wl7M6HA2c9twbKByfAtAB5fr26wPawQ6y1GCdakD_Kr4xdD20fvkytnmOmZu7Ktnb9mUVE605AAbJcIA9SOlRN5410ZPOnZA1cIzr4WtAFWNfQKPG6Sxk_zO5zvXQfYTyMNmOI" + } +] diff --git a/apps/BingBot/core/classes.py b/apps/BingBot/core/classes.py new file mode 100644 index 00000000..8dfdb114 --- /dev/null +++ b/apps/BingBot/core/classes.py @@ -0,0 +1,5 @@ +from discord.ext import commands + +class Cog_Extension(commands.Cog): + def __init__(self, bot): + self.bot = bot diff --git a/apps/BingBot/requirements.txt b/apps/BingBot/requirements.txt new file mode 100644 index 00000000..73773a31 --- /dev/null +++ b/apps/BingBot/requirements.txt @@ -0,0 +1,4 @@ +discord.py==2.3.2 +python-dotenv==0.21.1 +PyYAML==6.0 +EdgeGPT==0.13.2 diff --git a/apps/BingBot/src/imageCreate.py b/apps/BingBot/src/imageCreate.py new file mode 100644 index 00000000..b88d1d4b --- /dev/null +++ b/apps/BingBot/src/imageCreate.py @@ -0,0 +1,31 @@ +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 \ No newline at end of file diff --git a/apps/BingBot/src/log.py b/apps/BingBot/src/log.py new file mode 100644 index 00000000..fba4e94d --- /dev/null +++ b/apps/BingBot/src/log.py @@ -0,0 +1,67 @@ +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 diff --git a/apps/BingBot/src/response.py b/apps/BingBot/src/response.py new file mode 100644 index 00000000..371622b8 --- /dev/null +++ b/apps/BingBot/src/response.py @@ -0,0 +1,117 @@ +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 diff --git a/apps/discord.py b/apps/discord.py index aa07f4e5..eebc48c8 100644 --- a/apps/discord.py +++ b/apps/discord.py @@ -1,17 +1,117 @@ -import os -import asyncio -import dalle3 import discord -import responses -from invoke import Executor -from dotenv import load_dotenv from discord.ext import commands +import asyncio +import os +from dotenv import load_dotenv +from invoke import Executor + +class BotCommands(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.command() + async def greet(self, ctx): + """greets the user.""" + await ctx.send(f"hello, {ctx.author.name}!") + + @commands.command() + async def help_me(self, ctx): + """provides a list of commands and their descriptions.""" + help_text = """ + - `!greet`: greets you. + - `!run [description]`: generates a video based on the given description. + - `!help_me`: provides this list of commands and their descriptions. + """ + await ctx.send(help_text) + + @commands.command() + async def join(self, ctx): + """joins the voice channel that the user is in.""" + if ctx.author.voice: + channel = ctx.author.voice.channel + await channel.connect() + else: + await ctx.send("you are not in a voice channel!") + + @commands.command() + async def leave(self, ctx): + """leaves the voice channel that the self.bot is in.""" + if ctx.voice_client: + await ctx.voice_client.disconnect() + else: + await ctx.send("i am not in a voice channel!") + + @commands.command() + async def listen(self, ctx): + """starts listening to voice in the voice channel that the bot is in.""" + if ctx.voice_client: + # create a wavesink to record the audio + sink = discord.sinks.wavesink('audio.wav') + # start recording + ctx.voice_client.start_recording(sink) + await ctx.send("started listening and recording.") + else: + await ctx.send("i am not in a voice channel!") + + @commands.command() + async def generate_image(self, ctx, *, prompt: str = None, imggen: str = None): + """generates images based on the provided prompt""" + await ctx.send(f"generating images for prompt: `{prompt}`...") + loop = asyncio.get_event_loop() + + # initialize a future object for the dalle instance + future = loop.run_in_executor(Executor, imggen, prompt) + + try: + # wait for the dalle request to complete, with a timeout of 60 seconds + await asyncio.wait_for(future, timeout=300) + print("done generating images!") + + # list all files in the save_directory + all_files = [os.path.join(root, file) for root, _, files in os.walk(os.environ("SAVE_DIRECTORY")) for file in files] + + # sort files by their creation time (latest first) + sorted_files = sorted(all_files, key=os.path.getctime, reverse=True) + + # get the 4 most recent files + latest_files = sorted_files[:4] + print(f"sending {len(latest_files)} images to discord...") + + # send all the latest images in a single message + # storage_service = os.environ("STORAGE_SERVICE") # "https://storage.googleapis.com/your-bucket-name/ + # await ctx.send(files=[storage_service.upload(filepath) for filepath in latest_files]) + + except asyncio.timeouterror: + await ctx.send("the request took too long! it might have been censored or you're out of boosts. please try entering the prompt again.") + except Exception as e: + await ctx.send(f"an error occurred: {e}") + + @commands.command() + async def send_text(self, ctx, *, text: str, use_agent: bool = True): + """sends the provided text to the worker and returns the response""" + if use_agent: + response = self.bot.agent.run(text) + else: + response = self.bot.llm(text) + await ctx.send(response) + + @commands.Cog.listener() + async def on_ready(self): + print(f"we have logged in as {self.bot.user}") + + @commands.Cog.listener() + async def on_command_error(self, ctx, error): + """handles errors that occur while executing commands.""" + if isinstance(error, commands.CommandNotFound): + await ctx.send("that command does not exist!") + else: + await ctx.send(f"an error occurred: {error}") class Bot: - def __init__(self, agent, llm, command_prefix="!"): + def __init__(self, llm, command_prefix="!"): load_dotenv() - intents = discord.intents.default() + intents = discord.Intents.default() intents.messages = True intents.guilds = True intents.voice_states = True @@ -19,119 +119,12 @@ class Bot: # setup self.llm = llm - self.agent = agent - self. bot = commands.bot(command_prefix="!", intents=intents) + self.bot = commands.Bot(command_prefix="!", intents=intents) self.discord_token = os.getenv("DISCORD_TOKEN") self.storage_service = os.getenv("STORAGE_SERVICE") + # Load the BotCommands cog + self.bot.add_cog(BotCommands(self.bot)) - @self.bot.event - async def on_ready(): - print(f"we have logged in as {self.bot.user}") - - - @self.bot.command() - async def greet(ctx): - """greets the user.""" - await ctx.send(f"hello, {ctx.author.name}!") - - - @self.bot.command() - async def help_me(ctx): - """provides a list of commands and their descriptions.""" - help_text = """ - - `!greet`: greets you. - - `!run [description]`: generates a video based on the given description. - - `!help_me`: provides this list of commands and their descriptions. - """ - await ctx.send(help_text) - - @self.bot.event - async def on_command_error(ctx, error): - """handles errors that occur while executing commands.""" - if isinstance(error, commands.commandnotfound): - await ctx.send("that command does not exist!") - else: - await ctx.send(f"an error occurred: {error}") - - @self.bot.command() - async def join(ctx): - """joins the voice channel that the user is in.""" - if ctx.author.voice: - channel = ctx.author.voice.channel - await channel.connect() - else: - await ctx.send("you are not in a voice channel!") - - @self.bot.command() - async def leave(ctx): - """leaves the voice channel that the self.bot is in.""" - if ctx.voice_client: - await ctx.voice_client.disconnect() - else: - await ctx.send("i am not in a voice channel!") - - # voice_transcription.py - @self.bot.command() - async def listen(ctx): - """starts listening to voice in the voice channel that the bot is in.""" - if ctx.voice_client: - # create a wavesink to record the audio - sink = discord.sinks.wavesink('audio.wav') - # start recording - ctx.voice_client.start_recording(sink) - await ctx.send("started listening and recording.") - else: - await ctx.send("i am not in a voice channel!") - - # image_generator.py - @self.bot.command() - async def generate_image(ctx, *, prompt: str = None, imggen: str = None): - """generates images based on the provided prompt""" - await ctx.send(f"generating images for prompt: `{prompt}`...") - loop = asyncio.get_event_loop() - - # initialize a future object for the dalle instance - future = loop.run_in_executor(Executor, imggen, prompt) - - try: - # wait for the dalle request to complete, with a timeout of 60 seconds - await asyncio.wait_for(future, timeout=300) - print("done generating images!") - - # list all files in the save_directory - all_files = [os.path.join(root, file) for root, _, files in os.walk(os.environ("SAVE_DIRECTORY")) for file in files] - - # sort files by their creation time (latest first) - sorted_files = sorted(all_files, key=os.path.getctime, reverse=True) - - # get the 4 most recent files - latest_files = sorted_files[:4] - print(f"sending {len(latest_files)} images to discord...") - - # send all the latest images in a single message - # storage_service = os.environ("STORAGE_SERVICE") # "https://storage.googleapis.com/your-bucket-name/ - # await ctx.send(files=[storage_service.upload(filepath) for filepath in latest_files]) - - except asyncio.timeouterror: - await ctx.send("the request took too long! it might have been censored or you're out of boosts. please try entering the prompt again.") - except Exception as e: - await ctx.send(f"an error occurred: {e}") - - @self.bot.command() - async def send_text(ctx, *, text: str, use_agent: bool = True): - """sends the provided text to the worker and returns the response""" - if use_agent: - response = self.agent.run(text) - else: - response = self.llm.__call__(text) - await ctx.send(response) - - def add_command(self, name, func): - @self.bot.command() - async def command(ctx, *args): - reponse = func(*args) - await ctx.send(responses) - -def run(self) : - self.bot.run("DISCORD_TOKEN") + def run(self): + self.bot.run(self.discord_token) diff --git a/playground/models/bingchat.py b/bingchat.py similarity index 64% rename from playground/models/bingchat.py rename to bingchat.py index 6e44cbb5..f4b91cd7 100644 --- a/playground/models/bingchat.py +++ b/bingchat.py @@ -10,10 +10,10 @@ cookie = os.environ.get("BING_COOKIE") auth = os.environ.get("AUTH_COOKIE") # Use the worker to process a task -task = "hi" -# img_task = "Sunset over mountains" +# task = "hi" +img_task = "Sunset over mountains" -response = edgegpt(task) -# response = edgegpt.create_img(auth_cookie=cookie,auth_cookie_SRCHHPGUSR=auth,prompt=img_task) +# response = edgegpt(task) +response = edgegpt.create_img(auth_cookie=cookie,auth_cookie_SRCHHPGUSR=auth,prompt=img_task) print(response) diff --git a/playground/apps/bing_discord.py b/playground/apps/bing_discord.py index fc179d1c..d35253ff 100644 --- a/playground/apps/bing_discord.py +++ b/playground/apps/bing_discord.py @@ -3,12 +3,13 @@ from swarms.models.bing_chat import BingChat from apps.discord import Bot from dotenv import load_dotenv +load_dotenv() # Initialize the EdgeGPTModel cookie = os.environ.get("BING_COOKIE") auth = os.environ.get("AUTH_COOKIE") -bing = BingChat(cookies_path="./cookies.txt", bing_cookie=cookie, auth_cookie=auth) +bing = BingChat(cookies_path="./cookies.json") -bot = Bot(llm=bing, cookie=cookie, auth=auth) -bot.generate_image(imggen=bing.create_img()) +bot = Bot(llm=bing) +bot.generate_image(imggen=bing.create_img(auth_cookie=cookie, auth_cookie_SRCHHPGUSR=auth)) bot.send_text(use_agent=False) diff --git a/swarms/models/bing_chat.py b/swarms/models/bing_chat.py index cb90f97e..3ded87cd 100644 --- a/swarms/models/bing_chat.py +++ b/swarms/models/bing_chat.py @@ -58,7 +58,7 @@ class BingChat: images = image_generator.get_images(prompt) image_generator.save_images(images, output_dir=output_dir) - return Path(output_dir) / images[0]["path"] + return Path(output_dir) / images[0] @staticmethod def set_cookie_dir_path(path: str):