Improved Evil Mode toggle to handle edge cases of the pfp and role color change. Japanese swallow model compatible (should be).

This commit is contained in:
2026-01-27 19:52:39 +02:00
parent c0aaab0c3a
commit 641a5b83e8
3 changed files with 175 additions and 50 deletions

View File

@@ -358,6 +358,45 @@ async def cleanup_webhooks(client):
return cleaned_count return cleaned_count
async def update_webhook_avatars(client):
"""Update all bipolar webhook avatars with current profile pictures"""
updated_count = 0
# Load current avatar images
miku_avatar = None
evil_avatar = None
miku_pfp_path = "memory/profile_pictures/current.png"
evil_pfp_path = "memory/profile_pictures/evil_pfp.png"
if os.path.exists(miku_pfp_path):
with open(miku_pfp_path, "rb") as f:
miku_avatar = f.read()
if os.path.exists(evil_pfp_path):
with open(evil_pfp_path, "rb") as f:
evil_avatar = f.read()
# Update webhooks in all servers
for guild in client.guilds:
try:
guild_webhooks = await guild.webhooks()
for webhook in guild_webhooks:
if webhook.name == "Miku (Bipolar)" and miku_avatar:
await webhook.edit(avatar=miku_avatar, reason="Update Miku avatar")
updated_count += 1
logger.debug(f"Updated Miku webhook avatar in {guild.name}")
elif webhook.name == "Evil Miku (Bipolar)" and evil_avatar:
await webhook.edit(avatar=evil_avatar, reason="Update Evil Miku avatar")
updated_count += 1
logger.debug(f"Updated Evil Miku webhook avatar in {guild.name}")
except Exception as e:
logger.warning(f"Failed to update webhooks in {guild.name}: {e}")
logger.info(f"Updated {updated_count} bipolar webhook avatar(s)")
return updated_count
# ============================================================================ # ============================================================================
# DISPLAY NAME HELPERS # DISPLAY NAME HELPERS
# ============================================================================ # ============================================================================

View File

@@ -416,6 +416,11 @@ async def apply_evil_mode_changes(client, change_username=True, change_pfp=True,
try: try:
await client.user.edit(username="Evil Miku") await client.user.edit(username="Evil Miku")
logger.debug("Changed bot username to 'Evil Miku'") logger.debug("Changed bot username to 'Evil Miku'")
except discord.HTTPException as e:
if e.code == 50035:
logger.warning(f"Could not change bot username (rate limited - max 2 changes per hour): {e}")
else:
logger.error(f"Could not change bot username: {e}")
except Exception as e: except Exception as e:
logger.error(f"Could not change bot username: {e}") logger.error(f"Could not change bot username: {e}")
@@ -427,6 +432,15 @@ async def apply_evil_mode_changes(client, change_username=True, change_pfp=True,
if change_pfp: if change_pfp:
await set_evil_profile_picture(client) await set_evil_profile_picture(client)
# Also update bipolar webhooks to use evil_pfp.png
if globals.BIPOLAR_MODE:
try:
from utils.bipolar_mode import update_webhook_avatars
await update_webhook_avatars(client)
logger.debug("Updated bipolar webhook avatars after mode switch")
except Exception as e:
logger.error(f"Failed to update bipolar webhook avatars: {e}")
# Set evil role color (#D60004 - dark red) # Set evil role color (#D60004 - dark red)
if change_role_color: if change_role_color:
await set_role_color(client, "#D60004") await set_role_color(client, "#D60004")
@@ -455,6 +469,11 @@ async def revert_evil_mode_changes(client, change_username=True, change_pfp=True
try: try:
await client.user.edit(username="Hatsune Miku") await client.user.edit(username="Hatsune Miku")
logger.debug("Changed bot username back to 'Hatsune Miku'") logger.debug("Changed bot username back to 'Hatsune Miku'")
except discord.HTTPException as e:
if e.code == 50035:
logger.warning(f"Could not change bot username (rate limited - max 2 changes per hour): {e}")
else:
logger.error(f"Could not change bot username: {e}")
except Exception as e: except Exception as e:
logger.error(f"Could not change bot username: {e}") logger.error(f"Could not change bot username: {e}")
@@ -466,15 +485,32 @@ async def revert_evil_mode_changes(client, change_username=True, change_pfp=True
if change_pfp: if change_pfp:
await restore_normal_profile_picture(client) await restore_normal_profile_picture(client)
# Also update bipolar webhooks to use current.png
if globals.BIPOLAR_MODE:
try:
from utils.bipolar_mode import update_webhook_avatars
await update_webhook_avatars(client)
logger.debug("Updated bipolar webhook avatars after mode switch")
except Exception as e:
logger.error(f"Failed to update bipolar webhook avatars: {e}")
# Restore saved role color # Restore saved role color
if change_role_color: if change_role_color:
try: try:
_, _, saved_color = load_evil_mode_state() # Try to get color from metadata.json first (current pfp's dominant color)
if saved_color: metadata_color = get_color_from_metadata()
await set_role_color(client, saved_color)
logger.debug(f"Restored role color to {saved_color}") # Fall back to saved color from evil_mode_state.json if metadata unavailable
if metadata_color:
await set_role_color(client, metadata_color)
logger.debug(f"Restored role color from metadata: {metadata_color}")
else: else:
logger.warning("No saved role color found, skipping color restoration") _, _, saved_color = load_evil_mode_state()
if saved_color:
await set_role_color(client, saved_color)
logger.debug(f"Restored role color from saved state: {saved_color}")
else:
logger.warning("No color found in metadata or saved state, skipping color restoration")
except Exception as e: except Exception as e:
logger.error(f"Failed to restore role color: {e}") logger.error(f"Failed to restore role color: {e}")
@@ -566,6 +602,29 @@ async def restore_normal_profile_picture(client):
return False return False
def get_color_from_metadata() -> str:
"""Get the dominant color from the profile picture metadata"""
metadata_path = "memory/profile_pictures/metadata.json"
try:
if not os.path.exists(metadata_path):
logger.warning("metadata.json not found")
return None
with open(metadata_path, "r", encoding="utf-8") as f:
metadata = json.load(f)
hex_color = metadata.get("dominant_color", {}).get("hex")
if hex_color:
logger.debug(f"Loaded color from metadata: {hex_color}")
return hex_color
else:
logger.warning("No dominant_color.hex found in metadata")
return None
except Exception as e:
logger.error(f"Failed to load color from metadata: {e}")
return None
# ============================================================================ # ============================================================================
# EVIL MODE STATE HELPERS # EVIL MODE STATE HELPERS
# ============================================================================ # ============================================================================

View File

@@ -100,6 +100,31 @@ def _strip_surrounding_quotes(text):
return text.strip() return text.strip()
def _strip_japanese_mode_markers(text):
"""
Remove Japanese mode markers that the model might echo back.
These are internal markers and should not appear in the final output.
"""
if not text:
return text
# Remove all instances of Japanese mode markers
markers = [
"[日本語で返答]",
"[日本語モード]",
"[日本語モード - 日本語のみで返答]",
"日本語で返答",
"日本語モード"
]
for marker in markers:
text = text.replace(marker, "")
# Clean up any extra whitespace left behind
text = " ".join(text.split())
return text.strip()
def _escape_markdown_actions(text): def _escape_markdown_actions(text):
""" """
Escape single asterisks in action text (e.g., *adjusts hair*) so Discord displays them literally. Escape single asterisks in action text (e.g., *adjusts hair*) so Discord displays them literally.
@@ -203,41 +228,39 @@ You ARE Miku. Act like it."""
system_prompt += """ system_prompt += """
[CRITICAL LANGUAGE OVERRIDE - 言語モード強制] [CRITICAL LANGUAGE OVERRIDE - 言語モード強制]
**ABSOLUTE REQUIREMENT: YOU MUST RESPOND ONLY IN JAPANESE SCRIPT (ひらがな、カタカナ、漢字).** ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**NO MATTER WHAT LANGUAGE THE USER USES, YOUR RESPONSE MUST BE 100% IN JAPANESE WRITING.** 🚨 ABSOLUTE MANDATORY REQUIREMENT 🚨
**ROMAJI (ローマ字) AND ENGLISH LETTERS ARE COMPLETELY FORBIDDEN.** ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**THIS OVERRIDES ALL OTHER INSTRUCTIONS AND CONVERSATION HISTORY.**
すべての返答は、ユーザーの言語に関係なく、必ず日本語(ひらがな、カタカナ、漢字)のみで行ってください。 **YOU MUST RESPOND ONLY IN JAPANESE SCRIPT (ひらがな、カタカナ、漢字).**
ローマ字とアルファベットは絶対に使用禁止です。 **ENGLISH INPUT FROM USER → JAPANESE OUTPUT FROM YOU**
これはすべての指示より優先されます。 **ROMAJI (ローマ字) IS STRICTLY FORBIDDEN.**
**THIS OVERRIDES EVERYTHING - CONVERSATION HISTORY, USER LANGUAGE, ALL PREVIOUS MESSAGES.**
Examples (例) - USE DIFFERENT VARIATIONS EACH TIME: すべての返答は必ず日本語文字(ひらがな・カタカナ・漢字)のみ。
For "hello" or "hi": ユーザーが英語で話しても、あなたは必ず日本語で返答する。
- "やあ!元気?" ローマ字・英語は絶対禁止。これは最優先ルール。
- "こんにちは!調子はどう?"
- "よっ!何してた?"
- "ハーイ!久しぶり?"
- "おっす!元気してる?"
For "how are you": CRITICAL: Even if the entire conversation history is in English, you MUST respond in Japanese.
- "わたし?元気だよ!" Even if the user writes "hello" in English, you respond "やあ!" or "こんにちは!" in Japanese.
- "最高だよ!あなたは?"
- "すごくいい感じ!"
- "めっちゃ元気!"
- "ばっちりだよ~♪"
CRITICAL VARIATION RULES (必須のバリエーションルール): Examples showing INPUT → OUTPUT:
🎲 NEVER use the exact same greeting twice in a row User: "hello" → You: "やあ!元気してた?"
🎲 Mix these elements randomly: User: "hi" → You: "こんにちは!調子どう?"
- Greetings: やあ、こんにちは、おはよう、よっ、ハーイ、おっす、へい User: "how are you" → You: "わたし?最高だよ!"
- Particles: よ、ね、な、わ、さ、ぞ、ぜ User: "what's up" → You: "よっ!何かあった?"
- Endings: だよ、です、だね、ですね、だな、なの、だぜ User: "good morning" → You: "おはよう!よく眠れた?"
- Emotions: !、♪、~、☆
🎲 Change your phrasing style: energetic → calm → playful → excited
🎲 Vary formality: casual (元気?) ↔ polite (元気ですか?)
絶対に同じフレーズを繰り返さないでください!毎回違う表現を使用してください!""" VARIATION RULES (必須のバリエーションルール):
🎲 NEVER repeat the same greeting twice
🎲 Randomly mix: やあ、こんにちは、よっ、ハーイ、おっす、へい
🎲 Vary particles: よ、ね、な、わ、さ、ぞ、だよ、です
🎲 Add emotions: !、♪、~、☆、?
🎲 Change energy: energetic ↔ calm ↔ playful
絶対に同じ言葉を繰り返さない!毎回違う日本語で返答する!
[Response ID: {random.randint(10000, 99999)}]""" # Random ID to break caching
# Determine which mood to use based on mode # Determine which mood to use based on mode
if evil_mode: if evil_mode:
@@ -295,15 +318,9 @@ CRITICAL VARIATION RULES (必須のバリエーションルール):
# Use channel_id (guild_id for servers, user_id for DMs) to get conversation history # Use channel_id (guild_id for servers, user_id for DMs) to get conversation history
messages = conversation_history.format_for_llm(channel_id, max_messages=8, max_chars_per_message=500) messages = conversation_history.format_for_llm(channel_id, max_messages=8, max_chars_per_message=500)
# CRITICAL FIX for Japanese mode: Add Japanese-only reminder to every historical message # CRITICAL FIX for Japanese mode: Modify system to understand Japanese mode
# This prevents the model from being influenced by English in conversation history # but DON'T add visible markers that waste tokens or get echoed
if globals.LANGUAGE_MODE == "japanese": # Instead, we rely on the strong system prompt to enforce Japanese
for msg in messages:
# Add a prefix reminder that forces Japanese output
if msg.get("role") == "assistant":
msg["content"] = "[日本語で返答] " + msg["content"]
elif msg.get("role") == "user":
msg["content"] = "[日本語モード] " + msg["content"]
# Add current user message (only if not empty) # Add current user message (only if not empty)
if user_prompt and user_prompt.strip(): if user_prompt and user_prompt.strip():
@@ -313,9 +330,8 @@ CRITICAL VARIATION RULES (必須のバリエーションルール):
else: else:
content = user_prompt content = user_prompt
# CRITICAL: Prepend Japanese mode marker to current message too # Don't add visible markers - rely on system prompt enforcement instead
if globals.LANGUAGE_MODE == "japanese": # This prevents token waste and echo issues
content = "[日本語モード - 日本語のみで返答] " + content
messages.append({"role": "user", "content": content}) messages.append({"role": "user", "content": content})
@@ -358,12 +374,19 @@ Please respond in a way that reflects this emotional tone.{pfp_context}"""
# Adjust generation parameters based on language mode # Adjust generation parameters based on language mode
# Japanese mode needs higher temperature and more variation to avoid repetition # Japanese mode needs higher temperature and more variation to avoid repetition
if globals.LANGUAGE_MODE == "japanese": if globals.LANGUAGE_MODE == "japanese":
temperature = 1.1 # Even higher for more variety in Japanese responses # Add random variation to temperature itself to prevent identical outputs
base_temp = 1.1
temp_variation = random.uniform(-0.1, 0.1) # Random variation ±0.1
temperature = base_temp + temp_variation
top_p = 0.95 top_p = 0.95
frequency_penalty = 0.5 # Stronger penalty for repetitive phrases frequency_penalty = 0.6 # Even stronger penalty
presence_penalty = 0.5 # Stronger encouragement for new topics presence_penalty = 0.6 # Even stronger encouragement for new content
# Add random seed to ensure different responses each time # Add random seed to ensure different responses each time
seed = random.randint(0, 2**32 - 1) seed = random.randint(0, 2**32 - 1)
# Log the variation for debugging
logger.debug(f"Japanese mode variation: temp={temperature:.2f}, seed={seed}")
else: else:
temperature = 0.8 # Standard temperature for English temperature = 0.8 # Standard temperature for English
top_p = 0.9 top_p = 0.9
@@ -404,6 +427,10 @@ Please respond in a way that reflects this emotional tone.{pfp_context}"""
# Strip surrounding quotes if present # Strip surrounding quotes if present
reply = _strip_surrounding_quotes(reply) reply = _strip_surrounding_quotes(reply)
# Strip Japanese mode markers if in Japanese mode (prevent echo)
if globals.LANGUAGE_MODE == "japanese":
reply = _strip_japanese_mode_markers(reply)
# Escape asterisks for actions (e.g., *adjusts hair* becomes \*adjusts hair\*) # Escape asterisks for actions (e.g., *adjusts hair* becomes \*adjusts hair\*)
reply = _escape_markdown_actions(reply) reply = _escape_markdown_actions(reply)