import discord from discord.ext import commands from discord import app_commands import re import random import os import aiohttp # In-memory conversation history for owo AI (keyed by channel id) _owo_conversations = {} def _owoify_text(text: str) -> str: """Improved owoification with more rules and randomness.""" # Basic substitutions text = re.sub(r"[rl]", "w", text) text = re.sub(r"[RL]", "W", text) text = re.sub(r"n([aeiou])", r"ny\1", text) text = re.sub(r"N([aeiou])", r"Ny\1", text) text = re.sub(r"N([AEIOU])", r"NY\1", text) text = re.sub(r"ove", "uv", text) text = re.sub(r"OVE", "UV", text) # Extra substitutions text = re.sub( r"\bth", lambda m: "d" if random.random() < 0.5 else "f", text, flags=re.IGNORECASE, ) text = re.sub(r"\bthe\b", "da", text, flags=re.IGNORECASE) text = re.sub(r"\bthat\b", "dat", text, flags=re.IGNORECASE) text = re.sub(r"\bthis\b", "dis", text, flags=re.IGNORECASE) text = re.sub(r"\bthose\b", "dose", text, flags=re.IGNORECASE) text = re.sub(r"\bthere\b", "dere", text, flags=re.IGNORECASE) text = re.sub( r"\bhere\b", "here", text, flags=re.IGNORECASE ) # Intentionally no change, for variety text = re.sub(r"\bwhat\b", "whut", text, flags=re.IGNORECASE) text = re.sub(r"\bwhen\b", "wen", text, flags=re.IGNORECASE) text = re.sub(r"\bwhere\b", "whewe", text, flags=re.IGNORECASE) text = re.sub(r"\bwhy\b", "wai", text, flags=re.IGNORECASE) text = re.sub(r"\bhow\b", "hau", text, flags=re.IGNORECASE) text = re.sub(r"\bno\b", "nu", text, flags=re.IGNORECASE) text = re.sub(r"\bhas\b", "haz", text, flags=re.IGNORECASE) text = re.sub(r"\bhave\b", "haz", text, flags=re.IGNORECASE) text = re.sub( r"\byou\b", lambda m: "u" if random.random() < 0.5 else "yu", text, flags=re.IGNORECASE, ) text = re.sub(r"\byour\b", "ur", text, flags=re.IGNORECASE) text = re.sub(r"tion\b", "shun", text, flags=re.IGNORECASE) text = re.sub(r"ing\b", "in", text, flags=re.IGNORECASE) # Playful punctuation text = re.sub( r"!", lambda m: random.choice(["!!1!", "! UwU", "! owo", "!! >w<", "! >//<", "!!?!"]), text, ) text = re.sub(r"\?", lambda m: random.choice(["?? OwO", "? uwu", "?"]), text) text = re.sub( r"\.", lambda m: random.choice(["~", ".", " ^w^", " o.o", " ._."]), text ) # Stutter (probabilistic, only for words with at least 2 letters) def stutter_word(match): word = match.group(0) if ( len(word) > 2 and random.random() < 0.33 and word[0].isalpha() ): # Increased probability return f"{word[0]}-{word}" return word text = re.sub(r"\b\w+\b", stutter_word, text) # Random interjection insertion (after commas or randomly) interjections = [ " owo", " uwu", " >w<", " ^w^", " OwO", " UwU", " >.<", " XD", " nyaa~", ":3", "(^///^)", "(ᵘʷᵘ)", "(・`ω´・)", ";;w;;", " teehee", " hehe", " x3", " rawr", "*nuzzles*", "*pounces*", ] parts = re.split(r"([,])", text) for i in range(len(parts)): if parts[i] == "," or ( random.random() < 0.15 and parts[i].strip() ): # Increased probability parts[i] += random.choice(interjections) text = "".join(parts) # Suffix text += random.choice(interjections) return text async def _owoify_text_ai(text: str) -> str: """Owoify text using AI via OpenRouter (google/gemini-2.0-flash-exp:free).""" return await _owoify_text_ai_with_messages( [{"role": "user", "content": text}], system_mode="transform" ) async def _owoify_text_ai_with_messages(messages, system_mode="transform"): """ Use OpenRouter AI to generate an owoified response. system_mode: "transform" for just transforming, "reply" for replying as an owo AI. """ api_key = os.getenv("AI_API_KEY") if not api_key: raise RuntimeError("AI_API_KEY environment variable not set.") url = "https://openrouter.ai/api/v1/chat/completions" headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} if system_mode == "transform": system_prompt = ( "You are a text transformer. Your ONLY job is to convert the user's input into an uwu/owo style of speech. " "Do NOT reply, greet, or comment. Do NOT add any extra words, context, or explanation. " "Return ONLY the transformed version of the input text, preserving punctuation and structure. " "Make the output playful, creative, and expressive, using a variety of cute interjections, stuttering, and playful punctuation. " "Never act as if you are replying to a message—just output the transformed text." ) else: system_prompt = ( "You are an uwu/owo style AI chatbot. Reply to the user in a playful, expressive, and cute uwu/owo style. " "Stay in character, use lots of interjections, stuttering, and playful punctuation. " "You are having a conversation, so respond naturally and keep the conversation going in uwu/owo style." ) payload = { "model": "deepseek/deepseek-chat-v3-0324:free", "messages": [{"role": "system", "content": system_prompt}] + messages, } async with aiohttp.ClientSession() as session: async with session.post(url, headers=headers, json=payload) as resp: if resp.content_type == "application/json": data = await resp.json() return data["choices"][0]["message"]["content"] else: text = await resp.text() raise RuntimeError( f"OpenRouter API returned non-JSON response (status {resp.status}): {text[:500]}" ) class OwoifyCog(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot @app_commands.command(name="owoify", description="Owoifies your message!") @app_commands.describe(message_to_owoify="The message to owoify") async def owoify_slash_command( self, interaction: discord.Interaction, message_to_owoify: str ): """Owoifies the provided message via a slash command.""" if not message_to_owoify.strip(): await interaction.response.send_message( "You nyeed to pwovide some text to owoify! >w<", ephemeral=True ) return owo_text = _owoify_text(message_to_owoify) await interaction.response.send_message(owo_text) @app_commands.command(name="owoify_ai", description="Owoify your message with AI!") @app_commands.describe(message_to_owoify="The message to owoify using AI") async def owoify_ai_slash_command( self, interaction: discord.Interaction, message_to_owoify: str ): """Owoifies the provided message via the OpenRouter AI.""" if not message_to_owoify.strip(): await interaction.response.send_message( "You nyeed to pwovide some text to owoify! >w<", ephemeral=True ) return try: owo_text = await _owoify_text_ai(message_to_owoify) await interaction.response.send_message(owo_text) except Exception as e: await interaction.response.send_message( f"AI owoification failed: {e} >w<", ephemeral=True ) # Context menu command must be defined at module level @app_commands.context_menu(name="Owoify Message") async def owoify_context_menu( interaction: discord.Interaction, message: discord.Message ): """Owoifies the content of the selected message and replies.""" if not message.content: await interaction.response.send_message( "The sewected message has no text content to owoify! >.<", ephemeral=True ) return original_content = message.content owo_text = _owoify_text(original_content) try: await message.reply(owo_text) await interaction.response.send_message( "Message owoified and wepwied! uwu", ephemeral=True ) except discord.Forbidden: await interaction.response.send_message( f"I couwdn't wepwy to the message (nyi Pwermissions? owo).\n" f"But hewe's the owoified text fow you: {owo_text}", ephemeral=True, ) except discord.HTTPException as e: await interaction.response.send_message( f"Oopsie! A tiny ewwow occuwwed: {e} >w<", ephemeral=True ) @app_commands.context_menu(name="Owoify Message (AI)") async def owoify_context_menu_ai( interaction: discord.Interaction, message: discord.Message ): """Owoifies the content of the selected message using AI and replies.""" if not message.content: await interaction.response.send_message( "The sewected message has no text content to owoify! >.<", ephemeral=True ) return original_content = message.content try: await interaction.response.defer(ephemeral=True) owo_text = await _owoify_text_ai(original_content) await message.reply(owo_text) await interaction.followup.send( "Message AI-owoified and wepwied! uwu", ephemeral=True ) except discord.Forbidden: await interaction.followup.send( f"I couwdn't wepwy to the message (nyi Pwermissions? owo).\n" f"But hewe's the AI owoified text fow you: {owo_text}", ephemeral=True, ) except Exception as e: await interaction.followup.send( f"AI owoification failed: {e} >w<", ephemeral=True ) @app_commands.context_menu(name="Owo AI Reply") async def owoify_context_menu_ai_reply( interaction: discord.Interaction, message: discord.Message ): """Replies to the selected message as an owo AI.""" if not message.content: await interaction.response.send_message( "The sewected message has no text content to reply to! >.<", ephemeral=True ) return await interaction.response.defer(ephemeral=True) convo_key = message.channel.id convo = _owo_conversations.get(convo_key, []) convo.append({"role": "user", "content": message.content}) try: ai_reply = await _owoify_text_ai_with_messages(convo, system_mode="reply") await message.reply(ai_reply) await interaction.followup.send("AI replied in owo style! uwu", ephemeral=True) convo.append({"role": "assistant", "content": ai_reply}) _owo_conversations[convo_key] = convo[-10:] # Keep last 10 messages except Exception as e: await interaction.followup.send(f"AI owo reply failed: {e} >w<", ephemeral=True) async def setup(bot: commands.Bot): cog = OwoifyCog(bot) await bot.add_cog(cog) bot.tree.add_command(owoify_context_menu) bot.tree.add_command(owoify_context_menu_ai) bot.tree.add_command(owoify_context_menu_ai_reply) print("OwoifyCog loaded! uwu")