feat: fix evil mode race conditions, expand moods and PFP detection
bipolar_mode.py: - Replace unsafe globals.EVIL_MODE temporary overrides with force_evil_context parameter to fix async race conditions (3 sites) moods.py: - Add 6 new evil mood emojis: bored, manic, jealous, melancholic, playful_cruel, contemptuous - Refactor rotate_dm_mood() to skip when evil mode active (evil mode has its own independent 2-hour rotation timer) persona_dialogue.py: - Same force_evil_context race condition fix (2 sites) - Fix over-aggressive response cleanup that stripped common words (YES/NO/HIGH) — now uses targeted regex for structural markers only - Update evil mood multipliers to match new mood set profile_picture_context: - Expand PFP detection regex for broader coverage (appearance questions, opinion queries, selection/change questions) - Add plugin.json metadata file
This commit is contained in:
@@ -883,23 +883,15 @@ async def run_argument(channel: discord.TextChannel, client, trigger_context: st
|
|||||||
if last_message is None:
|
if last_message is None:
|
||||||
init_prompt = get_argument_start_prompt(initiator, trigger_context)
|
init_prompt = get_argument_start_prompt(initiator, trigger_context)
|
||||||
|
|
||||||
# Temporarily set evil mode for query_llama if initiator is evil
|
# Use force_evil_context to avoid race condition with globals.EVIL_MODE
|
||||||
original_evil_mode = globals.EVIL_MODE
|
|
||||||
if initiator == "evil":
|
|
||||||
globals.EVIL_MODE = True
|
|
||||||
else:
|
|
||||||
globals.EVIL_MODE = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
initial_message = await query_llama(
|
initial_message = await query_llama(
|
||||||
user_prompt=init_prompt,
|
user_prompt=init_prompt,
|
||||||
user_id=argument_user_id,
|
user_id=argument_user_id,
|
||||||
guild_id=guild_id,
|
guild_id=guild_id,
|
||||||
response_type="autonomous_general",
|
response_type="autonomous_general",
|
||||||
model=globals.EVIL_TEXT_MODEL if initiator == "evil" else globals.TEXT_MODEL
|
model=globals.EVIL_TEXT_MODEL if initiator == "evil" else globals.TEXT_MODEL,
|
||||||
|
force_evil_context=(initiator == "evil")
|
||||||
)
|
)
|
||||||
finally:
|
|
||||||
globals.EVIL_MODE = original_evil_mode
|
|
||||||
|
|
||||||
if not initial_message or initial_message.startswith("Error") or initial_message.startswith("Sorry"):
|
if not initial_message or initial_message.startswith("Error") or initial_message.startswith("Sorry"):
|
||||||
logger.error("Failed to generate initial argument message")
|
logger.error("Failed to generate initial argument message")
|
||||||
@@ -994,23 +986,15 @@ async def run_argument(channel: discord.TextChannel, client, trigger_context: st
|
|||||||
# Add last message as context
|
# Add last message as context
|
||||||
response_prompt = f'The other Miku said: "{last_message}"\n\n{end_prompt}'
|
response_prompt = f'The other Miku said: "{last_message}"\n\n{end_prompt}'
|
||||||
|
|
||||||
# Temporarily set evil mode for query_llama
|
# Use force_evil_context to avoid race condition with globals.EVIL_MODE
|
||||||
original_evil_mode = globals.EVIL_MODE
|
|
||||||
if winner == "evil":
|
|
||||||
globals.EVIL_MODE = True
|
|
||||||
else:
|
|
||||||
globals.EVIL_MODE = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
final_message = await query_llama(
|
final_message = await query_llama(
|
||||||
user_prompt=response_prompt,
|
user_prompt=response_prompt,
|
||||||
user_id=argument_user_id,
|
user_id=argument_user_id,
|
||||||
guild_id=guild_id,
|
guild_id=guild_id,
|
||||||
response_type="autonomous_general",
|
response_type="autonomous_general",
|
||||||
model=globals.EVIL_TEXT_MODEL if winner == "evil" else globals.TEXT_MODEL
|
model=globals.EVIL_TEXT_MODEL if winner == "evil" else globals.TEXT_MODEL,
|
||||||
|
force_evil_context=(winner == "evil")
|
||||||
)
|
)
|
||||||
finally:
|
|
||||||
globals.EVIL_MODE = original_evil_mode
|
|
||||||
|
|
||||||
if final_message and not final_message.startswith("Error") and not final_message.startswith("Sorry"):
|
if final_message and not final_message.startswith("Error") and not final_message.startswith("Sorry"):
|
||||||
# Send winner's final message via webhook
|
# Send winner's final message via webhook
|
||||||
@@ -1059,23 +1043,15 @@ async def run_argument(channel: discord.TextChannel, client, trigger_context: st
|
|||||||
else:
|
else:
|
||||||
response_prompt = get_miku_argument_prompt(last_message, is_first_response=is_first_response)
|
response_prompt = get_miku_argument_prompt(last_message, is_first_response=is_first_response)
|
||||||
|
|
||||||
# Temporarily set evil mode for query_llama
|
# Use force_evil_context to avoid race condition with globals.EVIL_MODE
|
||||||
original_evil_mode = globals.EVIL_MODE
|
|
||||||
if current_speaker == "evil":
|
|
||||||
globals.EVIL_MODE = True
|
|
||||||
else:
|
|
||||||
globals.EVIL_MODE = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = await query_llama(
|
response = await query_llama(
|
||||||
user_prompt=response_prompt,
|
user_prompt=response_prompt,
|
||||||
user_id=argument_user_id,
|
user_id=argument_user_id,
|
||||||
guild_id=guild_id,
|
guild_id=guild_id,
|
||||||
response_type="autonomous_general",
|
response_type="autonomous_general",
|
||||||
model=globals.EVIL_TEXT_MODEL if current_speaker == "evil" else globals.TEXT_MODEL
|
model=globals.EVIL_TEXT_MODEL if current_speaker == "evil" else globals.TEXT_MODEL,
|
||||||
|
force_evil_context=(current_speaker == "evil")
|
||||||
)
|
)
|
||||||
finally:
|
|
||||||
globals.EVIL_MODE = original_evil_mode
|
|
||||||
|
|
||||||
if not response or response.startswith("Error") or response.startswith("Sorry"):
|
if not response or response.startswith("Error") or response.startswith("Sorry"):
|
||||||
logger.error(f"Failed to generate argument response")
|
logger.error(f"Failed to generate argument response")
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ EVIL_MOOD_EMOJIS = {
|
|||||||
"aggressive": "👿",
|
"aggressive": "👿",
|
||||||
"cunning": "🐍",
|
"cunning": "🐍",
|
||||||
"sarcastic": "😈",
|
"sarcastic": "😈",
|
||||||
"evil_neutral": ""
|
"evil_neutral": "",
|
||||||
|
"bored": "🥱",
|
||||||
|
"manic": "🤪",
|
||||||
|
"jealous": "💚",
|
||||||
|
"melancholic": "🌑",
|
||||||
|
"playful_cruel": "🎭",
|
||||||
|
"contemptuous": "👑"
|
||||||
}
|
}
|
||||||
|
|
||||||
def load_mood_description(mood_name: str) -> str:
|
def load_mood_description(mood_name: str) -> str:
|
||||||
@@ -150,14 +156,16 @@ def detect_mood_shift(response_text, server_context=None):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
async def rotate_dm_mood():
|
async def rotate_dm_mood():
|
||||||
"""Rotate DM mood automatically (handles both normal and evil mode)"""
|
"""Rotate DM mood automatically (normal mode only — evil has its own independent timer)"""
|
||||||
try:
|
try:
|
||||||
from utils.evil_mode import is_evil_mode, rotate_evil_mood
|
from utils.evil_mode import is_evil_mode
|
||||||
|
|
||||||
if is_evil_mode():
|
if is_evil_mode():
|
||||||
# Rotate evil mood instead
|
# Evil mode has its own independent 2-hour rotation timer in evil_mode.py
|
||||||
await rotate_evil_mood()
|
# Do nothing here — evil mood rotation is handled by start_evil_mood_rotation()
|
||||||
else:
|
logger.debug("Skipping DM mood rotation — evil mode has its own timer")
|
||||||
|
return
|
||||||
|
|
||||||
# Normal mood rotation
|
# Normal mood rotation
|
||||||
old_mood = globals.DM_MOOD
|
old_mood = globals.DM_MOOD
|
||||||
new_mood = old_mood
|
new_mood = old_mood
|
||||||
@@ -174,9 +182,6 @@ async def rotate_dm_mood():
|
|||||||
|
|
||||||
logger.info(f"DM mood rotated from {old_mood} to {new_mood}")
|
logger.info(f"DM mood rotated from {old_mood} to {new_mood}")
|
||||||
|
|
||||||
# Note: We don't update server nicknames here because servers have their own independent moods.
|
|
||||||
# DM mood only affects direct messages to users.
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Exception in rotate_dm_mood: {e}")
|
logger.error(f"Exception in rotate_dm_mood: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -264,12 +264,15 @@ class InterjectionScorer:
|
|||||||
if opposite_persona == "evil":
|
if opposite_persona == "evil":
|
||||||
MOOD_MULTIPLIERS = {
|
MOOD_MULTIPLIERS = {
|
||||||
"aggressive": 1.5,
|
"aggressive": 1.5,
|
||||||
"cruel": 1.3,
|
"manic": 1.4,
|
||||||
"mischievous": 1.2,
|
"jealous": 1.3,
|
||||||
"cunning": 1.0,
|
"cunning": 1.0,
|
||||||
"sarcastic": 1.1,
|
"sarcastic": 1.1,
|
||||||
|
"playful_cruel": 1.2,
|
||||||
|
"contemptuous": 0.7,
|
||||||
"evil_neutral": 0.8,
|
"evil_neutral": 0.8,
|
||||||
"contemplative": 0.6,
|
"bored": 0.5,
|
||||||
|
"melancholic": 0.6,
|
||||||
}
|
}
|
||||||
return MOOD_MULTIPLIERS.get(globals.EVIL_DM_MOOD, 1.0)
|
return MOOD_MULTIPLIERS.get(globals.EVIL_DM_MOOD, 1.0)
|
||||||
else:
|
else:
|
||||||
@@ -505,20 +508,15 @@ On a new line after your response, write:
|
|||||||
# Use appropriate model
|
# Use appropriate model
|
||||||
model = globals.EVIL_TEXT_MODEL if responding_persona == "evil" else globals.TEXT_MODEL
|
model = globals.EVIL_TEXT_MODEL if responding_persona == "evil" else globals.TEXT_MODEL
|
||||||
|
|
||||||
# Temporarily set evil mode for proper context
|
# Use force_evil_context to avoid race condition with globals.EVIL_MODE
|
||||||
original_evil_mode = globals.EVIL_MODE
|
|
||||||
globals.EVIL_MODE = (responding_persona == "evil")
|
|
||||||
|
|
||||||
try:
|
|
||||||
raw_response = await query_llama(
|
raw_response = await query_llama(
|
||||||
user_prompt=prompt,
|
user_prompt=prompt,
|
||||||
user_id=f"persona_dialogue_{channel.id}",
|
user_id=f"persona_dialogue_{channel.id}",
|
||||||
guild_id=channel.guild.id if hasattr(channel, 'guild') and channel.guild else None,
|
guild_id=channel.guild.id if hasattr(channel, 'guild') and channel.guild else None,
|
||||||
response_type="autonomous_general",
|
response_type="autonomous_general",
|
||||||
model=model
|
model=model,
|
||||||
|
force_evil_context=(responding_persona == "evil")
|
||||||
)
|
)
|
||||||
finally:
|
|
||||||
globals.EVIL_MODE = original_evil_mode
|
|
||||||
|
|
||||||
if not raw_response or raw_response.startswith("Error"):
|
if not raw_response or raw_response.startswith("Error"):
|
||||||
return None, False, "LOW"
|
return None, False, "LOW"
|
||||||
@@ -553,10 +551,12 @@ On a new line after your response, write:
|
|||||||
|
|
||||||
response_text = '\n'.join(response_lines).strip()
|
response_text = '\n'.join(response_lines).strip()
|
||||||
|
|
||||||
# Clean up any stray signal markers
|
# Clean up any stray [CONTINUE: markers that leaked into response lines
|
||||||
response_text = response_text.replace("[CONTINUE:", "").replace("]", "")
|
# Only strip the structural markers, NOT common words like YES/NO/HIGH etc.
|
||||||
response_text = response_text.replace("YES", "").replace("NO", "")
|
import re
|
||||||
response_text = response_text.replace("HIGH", "").replace("MEDIUM", "").replace("LOW", "")
|
response_text = re.sub(r'\[CONTINUE:\s*(YES|NO)\]\s*\[CONFIDENCE:\s*(HIGH|MEDIUM|LOW)\]', '', response_text)
|
||||||
|
response_text = re.sub(r'\[CONTINUE:\s*(YES|NO)\]', '', response_text)
|
||||||
|
response_text = re.sub(r'\[CONFIDENCE:\s*(HIGH|MEDIUM|LOW)\]', '', response_text)
|
||||||
response_text = response_text.strip()
|
response_text = response_text.strip()
|
||||||
|
|
||||||
# Override: If the response contains a question mark, always continue
|
# Override: If the response contains a question mark, always continue
|
||||||
@@ -727,19 +727,15 @@ Don't force a response if you have nothing meaningful to contribute."""
|
|||||||
|
|
||||||
model = globals.EVIL_TEXT_MODEL if persona == "evil" else globals.TEXT_MODEL
|
model = globals.EVIL_TEXT_MODEL if persona == "evil" else globals.TEXT_MODEL
|
||||||
|
|
||||||
original_evil_mode = globals.EVIL_MODE
|
# Use force_evil_context to avoid race condition with globals.EVIL_MODE
|
||||||
globals.EVIL_MODE = (persona == "evil")
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = await query_llama(
|
response = await query_llama(
|
||||||
user_prompt=prompt,
|
user_prompt=prompt,
|
||||||
user_id=f"persona_dialogue_{channel_id}",
|
user_id=f"persona_dialogue_{channel_id}",
|
||||||
guild_id=channel.guild.id if hasattr(channel, 'guild') and channel.guild else None,
|
guild_id=channel.guild.id if hasattr(channel, 'guild') and channel.guild else None,
|
||||||
response_type="autonomous_general",
|
response_type="autonomous_general",
|
||||||
model=model
|
model=model,
|
||||||
|
force_evil_context=(persona == "evil")
|
||||||
)
|
)
|
||||||
finally:
|
|
||||||
globals.EVIL_MODE = original_evil_mode
|
|
||||||
|
|
||||||
if not response:
|
if not response:
|
||||||
self.end_dialogue(channel_id)
|
self.end_dialogue(channel_id)
|
||||||
|
|||||||
10
cat-plugins/profile_picture_context/plugin.json
Normal file
10
cat-plugins/profile_picture_context/plugin.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "Profile Picture Context",
|
||||||
|
"description": "Injects profile picture description only when user asks about it using regex pattern matching",
|
||||||
|
"author_name": "Miku Bot Team",
|
||||||
|
"author_url": "",
|
||||||
|
"plugin_url": "",
|
||||||
|
"tags": "profile, picture, context, regex",
|
||||||
|
"thumb": "",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
@@ -14,12 +14,31 @@ import re
|
|||||||
|
|
||||||
# Regex patterns that match profile picture questions
|
# Regex patterns that match profile picture questions
|
||||||
PFP_PATTERNS = [
|
PFP_PATTERNS = [
|
||||||
r'\b(what|describe|tell me about|explain)\b.*\b(pfp|profile pic|avatar|picture)\b',
|
# Direct PFP references
|
||||||
r'\b(your|miku\'?s?)\b.*\b(pfp|profile pic|avatar|picture)\b',
|
r'\b(what|describe|tell me about|explain|show|how)\b.*\b(pfp|profile pic|avatar|picture|pic)\b',
|
||||||
r'\bwhat.*looking like\b',
|
r'\b(your|miku\'?s?)\b.*\b(pfp|profile pic|avatar|picture|pic)\b',
|
||||||
|
r'\b(pfp|profile pic|avatar|picture|pic)\b.*\b(is|look|show|about|like)',
|
||||||
|
|
||||||
|
# Questions about appearance
|
||||||
|
r'\b(what|how).*\b(you|miku)\b.*(look|looking|appear)',
|
||||||
r'\byour (new )?look\b',
|
r'\byour (new )?look\b',
|
||||||
r'\bhow.*look(ing)?\b.*today',
|
r'\b(what|how).*looking like\b',
|
||||||
r'\b(pfp|profile pic|avatar)\b.*\b(is|look|show)',
|
|
||||||
|
# Questions about the image itself
|
||||||
|
r'\b(think|feel|opinion|thoughts)\b.*\b(about|of)\b.*\b(your|that|the|this)?\b.*\b(pfp|profile|avatar|pic|picture|image)\b',
|
||||||
|
r'\b(why|how|when).*\b(pick|choose|chose|picked|select|change|changed)\b.*\b(pfp|profile|avatar|pic|picture|that)\b',
|
||||||
|
r'\b(new|current|latest)\b.*\b(pfp|profile pic|avatar|pic|picture)\b',
|
||||||
|
|
||||||
|
# "What do you think about your pfp"
|
||||||
|
r'\bthink.*\b(your|that|the|this)\b.*\b(pfp|profile|avatar|pic|picture)\b',
|
||||||
|
r'\b(your|that|the|this)\b.*\b(pfp|profile|avatar|pic|picture)\b.*\bthink\b',
|
||||||
|
|
||||||
|
# "How did you decide/pick"
|
||||||
|
r'\b(decide|decided|pick|picked|choose|chose|select)\b.*\b(pfp|profile|avatar|pic|picture|that|this)\b',
|
||||||
|
|
||||||
|
# "Tell me about that pfp" / "What's with the pfp"
|
||||||
|
r'\bwhat\'?s?\b.*\bwith\b.*\b(pfp|profile|avatar|pic|picture)\b',
|
||||||
|
r'\btell me\b.*\b(pfp|profile|avatar|pic|picture|that|this)\b',
|
||||||
]
|
]
|
||||||
|
|
||||||
def matches_pfp_query(text: str) -> bool:
|
def matches_pfp_query(text: str) -> bool:
|
||||||
|
|||||||
Reference in New Issue
Block a user