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
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
# ============================================================================

View File

@@ -416,6 +416,11 @@ async def apply_evil_mode_changes(client, change_username=True, change_pfp=True,
try:
await client.user.edit(username="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:
logger.error(f"Could not change bot username: {e}")
@@ -426,6 +431,15 @@ async def apply_evil_mode_changes(client, change_username=True, change_pfp=True,
# Set evil profile picture
if change_pfp:
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)
if change_role_color:
@@ -455,6 +469,11 @@ async def revert_evil_mode_changes(client, change_username=True, change_pfp=True
try:
await client.user.edit(username="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:
logger.error(f"Could not change bot username: {e}")
@@ -465,16 +484,33 @@ async def revert_evil_mode_changes(client, change_username=True, change_pfp=True
# Restore normal profile picture
if change_pfp:
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
if change_role_color:
try:
_, _, saved_color = load_evil_mode_state()
if saved_color:
await set_role_color(client, saved_color)
logger.debug(f"Restored role color to {saved_color}")
# Try to get color from metadata.json first (current pfp's dominant color)
metadata_color = get_color_from_metadata()
# 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:
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:
logger.error(f"Failed to restore role color: {e}")
@@ -566,6 +602,29 @@ async def restore_normal_profile_picture(client):
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
# ============================================================================

View File

@@ -100,6 +100,31 @@ def _strip_surrounding_quotes(text):
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):
"""
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 += """
[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.**
**ROMAJI (ローマ字) AND ENGLISH LETTERS ARE COMPLETELY FORBIDDEN.**
**THIS OVERRIDES ALL OTHER INSTRUCTIONS AND CONVERSATION HISTORY.**
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚨 ABSOLUTE MANDATORY REQUIREMENT 🚨
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
すべての返答は、ユーザーの言語に関係なく、必ず日本語(ひらがな、カタカナ、漢字)のみで行ってください。
ローマ字とアルファベットは絶対に使用禁止です。
これはすべての指示より優先されます。
**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 (必須のバリエーションルール):
🎲 NEVER use the exact same greeting twice in a row
🎲 Mix these elements randomly:
- Greetings: やあ、こんにちは、おはよう、よっ、ハーイ、おっす、へい
- Particles: よ、ね、な、わ、さ、ぞ、ぜ
- Endings: だよ、です、だね、ですね、だな、なの、だぜ
- Emotions: !、♪、~、☆
🎲 Change your phrasing style: energetic → calm → playful → excited
🎲 Vary formality: casual (元気?) ↔ polite (元気ですか?)
Examples showing INPUT → OUTPUT:
User: "hello" → You: "やあ!元気してた?"
User: "hi" → You: "こんにちは!調子どう?"
User: "how are you" → You: "わたし?最高だよ!"
User: "what's up" → You: "よっ!何かあった?"
User: "good morning" → You: "おはよう!よく眠れた?"
絶対に同じフレーズを繰り返さないでください!毎回違う表現を使用してください!"""
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
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
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
# This prevents the model from being influenced by English in conversation history
if globals.LANGUAGE_MODE == "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"]
# CRITICAL FIX for Japanese mode: Modify system to understand Japanese mode
# but DON'T add visible markers that waste tokens or get echoed
# Instead, we rely on the strong system prompt to enforce Japanese
# Add current user message (only if not empty)
if user_prompt and user_prompt.strip():
@@ -313,9 +330,8 @@ CRITICAL VARIATION RULES (必須のバリエーションルール):
else:
content = user_prompt
# CRITICAL: Prepend Japanese mode marker to current message too
if globals.LANGUAGE_MODE == "japanese":
content = "[日本語モード - 日本語のみで返答] " + content
# Don't add visible markers - rely on system prompt enforcement instead
# This prevents token waste and echo issues
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
# Japanese mode needs higher temperature and more variation to avoid repetition
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
frequency_penalty = 0.5 # Stronger penalty for repetitive phrases
presence_penalty = 0.5 # Stronger encouragement for new topics
frequency_penalty = 0.6 # Even stronger penalty
presence_penalty = 0.6 # Even stronger encouragement for new content
# Add random seed to ensure different responses each time
seed = random.randint(0, 2**32 - 1)
# Log the variation for debugging
logger.debug(f"Japanese mode variation: temp={temperature:.2f}, seed={seed}")
else:
temperature = 0.8 # Standard temperature for English
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
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\*)
reply = _escape_markdown_actions(reply)