From b69d8bd67ee2d986722919fc611ceca0267838b1 Mon Sep 17 00:00:00 2001 From: nsde Date: Tue, 25 Jul 2023 03:39:17 +0200 Subject: [PATCH] Added ToS verification, key system demo, embeds --- README.md | 2 + cord/__main__.py | 1 + cord/bot.py | 96 ++++++++++++++++++++++++++++++----------------- cord/chatbot.py | 54 ++++++++++++++++++++++++++ cord/embedder.py | 40 ++++++++++++++++++++ cord/keys.py | 4 ++ cord/smarthelp.py | 0 emojis.txt | 1 + screen.sh | 1 + 9 files changed, 165 insertions(+), 34 deletions(-) create mode 100644 cord/__main__.py create mode 100644 cord/chatbot.py create mode 100644 cord/embedder.py create mode 100644 cord/keys.py delete mode 100644 cord/smarthelp.py create mode 100644 emojis.txt create mode 100755 screen.sh diff --git a/README.md b/README.md index fcd191a..d1aea2a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # nova-discord 🤖 Discord bot for project Nova, built using [NextCord](https://github.com/nextcord/nextcord) for Python. +You can find more information about the bot and how it works on [https://nova-oss.com/novacord](https://nova-oss.com/novacord). + diff --git a/cord/__main__.py b/cord/__main__.py new file mode 100644 index 0000000..813adbc --- /dev/null +++ b/cord/__main__.py @@ -0,0 +1 @@ +import bot \ No newline at end of file diff --git a/cord/bot.py b/cord/bot.py index adf0032..cba3a33 100644 --- a/cord/bot.py +++ b/cord/bot.py @@ -1,8 +1,13 @@ # This example requires the 'members' and 'message_content' privileged intents to function. import os -import openai +import asyncio import nextcord +import requests + +import keys +import chatbot +import embedder from dotenv import load_dotenv from nextcord.ext import commands @@ -28,45 +33,68 @@ async def on_message(message): async def chat(interaction: nextcord.Interaction, prompt: str = SlashOption(description='AI Prompt', required=True) ): - partial_message = await interaction.send('‎') # empty message - message = await partial_message.fetch() + await chatbot.respond(interaction, prompt) - openai.api_base = os.getenv('OPENAI_BASE', 'https://api.openai.com/v1') - openai.api_key = os.getenv('OPENAI_KEY') +@bot.slash_command(description='Sets your DMs up, so you can write the bot.') +async def dm(interaction: nextcord.Interaction): + try: + await interaction.user.create_dm() + await embedder.info(interaction.user.dm_channel, 'Hello!') + except nextcord.Forbidden: + await embedder.error(interaction, text='Please open this server\'s options, go to `Privacy Settings` and enable `Direct Messages` and `Message Requests`.') + else: + await embedder.ok(interaction, 'Great, DMs are set up successfully!') - model = os.getenv('OPENAI_MODEL') +@bot.slash_command(description='Get your secret NovaAI API key.') +async def key(interaction: nextcord.Interaction): + try: + resp = requests.post( + url='https://nova-oss.com/api/tos-verification', + timeout=5, + headers={'Content-Type': 'application/json', 'Authorization': os.getenv('TOS_VERIFICATION_KEY')} + ).json() + except Exception as exc: + await embedder.error(interaction, """Sorry, the API server for the verification system is not functioning, +which means you can\'t create a new key right now. Please report this issue to the staff!""") + raise exc - async with interaction.channel.typing(): # show the "Typing..." - completion = openai.ChatCompletion.create( - model=model, - messages=[ - {'role': 'system', 'content': f"""You are a helpful Discord AI bot based on OpenAI\'s {model} model called "Nova". -You were developed by NovaAI (website: nova-oss.com) in July of 2023, but your knowledge is limited to mid-2021. -Respond using Markdown. Keep things simple and short and directly do what the user says without any fluff. -For programming code, always make use formatted code blocks like this: -```py -print("Hello") -``` -"""}, - {'role': 'user', 'content': prompt} - ], - temperature=0.6, - stream=True + tos_code = resp['code'] + tos_emoji = resp['emoji'] + + tos_message = await embedder.warn(interaction, f"""You have to read the privacy policy and terms of service first. +In the latter, there is a hidden emoji which you'll have to send (NOT react!) in here. + +https://nova-oss.com/legal/privacy +https://nova-oss.com/legal/terms?verify={tos_code} + +I know it's annoying, but it really helps combat spam bots and abuse. + +This message will be deleted and your code will run out **after about 10 minutes** +if you don't pass the verification, but **feel free to run this command again** at any time. +""", ephemeral=True) + + def check(message): return interaction.user.id == message.author.id and message.content == tos_emoji + + try: + answer = await bot.wait_for('message', timeout=666, check=check) + except asyncio.TimeoutError: + await tos_message.delete() + requests.delete( + url=f'https://nova-oss.com/api/tos-verification/{tos_code}', + timeout=5, + headers={'Content-Type': 'application/json', 'Authorization': os.getenv('TOS_VERIFICATION_KEY')} ) - text = '' + else: + await answer.delete() + api_key = await keys.create(interaction.user) + await embedder.ok(interaction, f"""This is your **secret** API key. Don't paste it on untrusted websites, apps or programs. +Store it securely using a `.env` file in the environment variables or use a secure password manager like *KeePass*, *ProtonPass* or *Bitwarden*. +We reserve the right to __disable your API key at any time__ if you violate our terms of service. +If you accept the terms of service and privacy policy, feel free to use the following API key: - for event in completion: # loop through word generation in real time - try: - new_text = event['choices'][0]['delta']['content'] # newly generated word - except KeyError: # end - break +## ||`{api_key}`|| - text += new_text - - if text: - await message.edit(content=text) - - await message.add_reaction('✅') +""", ephemeral=True) bot.run(os.getenv('DISCORD_TOKEN')) diff --git a/cord/chatbot.py b/cord/chatbot.py new file mode 100644 index 0000000..79e40d5 --- /dev/null +++ b/cord/chatbot.py @@ -0,0 +1,54 @@ +import os +import openai + +import embedder + +from dotenv import load_dotenv + +load_dotenv() + +async def respond(interaction, prompt): + partial_message = await interaction.send('‎') # empty message + message = await partial_message.fetch() + + openai.api_base = os.getenv('OPENAI_BASE', 'https://api.openai.com/v1') + openai.api_key = os.getenv('OPENAI_KEY') + + model = os.getenv('OPENAI_MODEL') + + async with interaction.channel.typing(): # show the "Typing..." + try: + completion = openai.ChatCompletion.create( + model=model, + messages=[ + {'role': 'system', 'content': f"""You are a helpful Discord AI bot based on OpenAI\'s {model} model called "Nova". + You were developed by NovaAI (website: nova-oss.com) in July of 2023, but your knowledge is limited to mid-2021. + Respond using Markdown. Keep things simple and short and directly do what the user says without any fluff. + For programming code, always make use formatted code blocks like this: + ```py + print("Hello") + ``` + """}, + {'role': 'user', 'content': prompt} + ], + temperature=0.6, + stream=True + ) + except Exception as exc: + await embedder.error(interaction, 'Could not generate an AI response.', ephemeral=True) + raise exc + + text = '' + + for event in completion: # loop through word generation in real time + try: + new_text = event['choices'][0]['delta']['content'] # newly generated word + except KeyError: # end + break + + text += new_text + + if text: + await message.edit(content=text) + + await message.add_reaction('✅') diff --git a/cord/embedder.py b/cord/embedder.py new file mode 100644 index 0000000..65d2488 --- /dev/null +++ b/cord/embedder.py @@ -0,0 +1,40 @@ +import nextcord +import traceback + +from typing import Union + +async def send( + ctx, + title: str, + text: str, + ephemeral: bool = False, + color: nextcord.Color = nextcord.Color.blue() +): + embed = nextcord.Embed( + title=title, + description=text, + color=color + ) + embed.set_footer(text='Powered by NovaAI', icon_url='https://i.ibb.co/LDyFcSh/fav-blurple.png') + embed.set_author(name='NovaCord', url='https://nova-oss.com/novacord') + + if isinstance(ctx, nextcord.Message): + response = await ctx.reply(embed=embed) + elif isinstance(ctx, Union[nextcord.Interaction, nextcord.InteractionResponse]): + response = await ctx.send(embed=embed, ephemeral=ephemeral) + else: + response = await ctx.send(embed=embed) + + return response + +async def ok(ctx, text: str, title: str='Success', *args, **kwargs): + return await send(ctx, title, text, color=nextcord.Color.green(), *args, **kwargs) + +async def info(ctx, text: str, title: str='Information', *args, **kwargs): + return await send(ctx, title, text, color=nextcord.Color.blue(), *args, **kwargs) + +async def warn(ctx, text: str, title: str='Warning', *args, **kwargs): + return await send(ctx, title, text, color=nextcord.Color.orange(), *args, **kwargs) + +async def error(ctx, text: str, title: str='Error - Command Failed', *args, **kwargs): + return await send(ctx, title, text, color=nextcord.Color.red(), *args, **kwargs) diff --git a/cord/keys.py b/cord/keys.py new file mode 100644 index 0000000..b621453 --- /dev/null +++ b/cord/keys.py @@ -0,0 +1,4 @@ +import secrets + +async def create(user): + return 'nv-' + secrets.token_urlsafe(32) diff --git a/cord/smarthelp.py b/cord/smarthelp.py deleted file mode 100644 index e69de29..0000000 diff --git a/emojis.txt b/emojis.txt new file mode 100644 index 0000000..b3f65a9 --- /dev/null +++ b/emojis.txt @@ -0,0 +1 @@ +🥇🥈🥉📒📕📗📘📙💩👻💀👽👾🤖🎃💛💚💙💜💔🐶🐱🐭🐹🐰🦊🐻🐼🐨🐯🦁🐮🐷🐽🐸🐵🙊🙉🙊🐒🐔🐧🐦🐤🐣🐥🦆🦅🦉🦇🐺🐗🐴🦄🐝🐛🦋🐌🐚🐞🐢🐍🦎🦂🦀🦑🐙🦐🐠🐟🐡🐬🦈🐳🐋🐊🐆🐅🐂🐄🦌🐪🐫🐘🦏🦍🐎🐖🐐🐏🐑🐕🐩🐈🐓🦃🐇🐁🐀🐾🐉🐲🌵🎄🌲🌳🌴🌱🌿🍀🎍🎋🍃🍂🍁🍄🌾💐🌷🌹🥀🌻🌼🌸🌺🌎🌍🌏🌕🌖🌗🌘🌑🌒🌓🌔🌚🌝🌞🌛🌜🌙💫⭐️🌟✨🔥💥⛅️🌈⛄️💨🌪🌊💧💦🍏🍎🍐🍊🍋🍌🍉🍇🍓🍈🍒🍑🍍🥝🥑🍅🍆🥒🥕🌽🥔🍠🌰🥜🍯🥐🍞🥖🧀🥚🍳🥓🥞🍤🍗🍖🍕🌭🍔🍟🥙🌮🌯🥗🥘🍝🍜🍲🍥🍣🍱🍛🍚🍙🍘🍢🍡🍧🍨🍦🍰🎂🍮🍭🍬🍫🍿🍩🍪🥛🍼🍵🍶🍺🍻🥂🍷🥃🍸🍹🍾🥄🍴⚽️🏀🏈⚾️🎾🏐🏉🎱🏓🏸🥅🏒🏑🏏⛳️🏹🎣🥊🥋 \ No newline at end of file diff --git a/screen.sh b/screen.sh new file mode 100755 index 0000000..399c272 --- /dev/null +++ b/screen.sh @@ -0,0 +1 @@ +screen -S nova-cord python cord \ No newline at end of file