Add 'Detect and Join Conversation' button to Web UI and CLI

- Added /autonomous/join-conversation API endpoint in api.py
- Added 'Detect and Join Conversation' button to Web UI under Autonomous Actions
- Added 'autonomous join-conversation' command to CLI tool (miku-cli.py)
- Updated miku_detect_and_join_conversation_for_server to support force=True parameter
- When force=True: skips time limit, activity checks, and random chance
- Force mode uses last 10 user messages regardless of age
- Manual triggers via Web UI/CLI now work even with old messages
This commit is contained in:
2025-12-10 14:57:59 +02:00
parent 76aaf6c437
commit 65e6c3e7ea
4 changed files with 82 additions and 18 deletions

View File

@@ -27,7 +27,8 @@ from utils.autonomous import (
miku_say_something_general, miku_say_something_general,
miku_engage_random_user, miku_engage_random_user,
share_miku_tweet, share_miku_tweet,
handle_custom_prompt handle_custom_prompt,
miku_detect_and_join_conversation
) )
import asyncio import asyncio
import nest_asyncio import nest_asyncio
@@ -338,6 +339,28 @@ async def trigger_autonomous_reaction(guild_id: int = None):
else: else:
return {"status": "error", "message": "Bot not ready"} return {"status": "error", "message": "Bot not ready"}
@app.post("/autonomous/join-conversation")
async def trigger_detect_and_join_conversation(guild_id: int = None):
# If guild_id is provided, detect and join conversation only for that server
# If no guild_id, trigger for all servers
print(f"🔍 [API] Join conversation endpoint called with guild_id={guild_id}")
if globals.client and globals.client.loop and globals.client.loop.is_running():
if guild_id is not None:
# Trigger for specific server only (force=True to bypass checks when manually triggered)
print(f"🔍 [API] Importing and calling miku_detect_and_join_conversation_for_server({guild_id}, force=True)")
from utils.autonomous import miku_detect_and_join_conversation_for_server
globals.client.loop.create_task(miku_detect_and_join_conversation_for_server(guild_id, force=True))
return {"status": "ok", "message": f"Detect and join conversation queued for server {guild_id}"}
else:
# Trigger for all servers (force=True to bypass checks when manually triggered)
print(f"🔍 [API] Importing and calling miku_detect_and_join_conversation() for all servers")
from utils.autonomous import miku_detect_and_join_conversation
globals.client.loop.create_task(miku_detect_and_join_conversation(force=True))
return {"status": "ok", "message": "Detect and join conversation queued for all servers"}
else:
print(f"⚠️ [API] Bot not ready: client={globals.client}, loop={globals.client.loop if globals.client else None}")
return {"status": "error", "message": "Bot not ready"}
@app.post("/profile-picture/change") @app.post("/profile-picture/change")
async def trigger_profile_picture_change( async def trigger_profile_picture_change(
guild_id: int = None, guild_id: int = None,

View File

@@ -576,6 +576,7 @@
<button onclick="triggerAutonomous('engage')">Engage Random User</button> <button onclick="triggerAutonomous('engage')">Engage Random User</button>
<button onclick="triggerAutonomous('tweet')">Share Tweet</button> <button onclick="triggerAutonomous('tweet')">Share Tweet</button>
<button onclick="triggerAutonomous('reaction')">React to Message</button> <button onclick="triggerAutonomous('reaction')">React to Message</button>
<button onclick="triggerAutonomous('join-conversation')">Detect and Join Conversation</button>
<button onclick="toggleCustomPrompt()">Custom Prompt</button> <button onclick="toggleCustomPrompt()">Custom Prompt</button>
</div> </div>

View File

@@ -209,8 +209,14 @@ async def miku_engage_random_user_for_server(guild_id: int):
except Exception as e: except Exception as e:
print(f"⚠️ Failed to engage user: {e}") print(f"⚠️ Failed to engage user: {e}")
async def miku_detect_and_join_conversation_for_server(guild_id: int): async def miku_detect_and_join_conversation_for_server(guild_id: int, force: bool = False):
"""Miku detects and joins conversations in a specific server""" """Miku detects and joins conversations in a specific server
Args:
guild_id: The server ID
force: If True, bypass activity checks and random chance (for manual triggers)
"""
print(f"🔍 [Join Conv] Called for server {guild_id} (force={force})")
server_config = server_manager.get_server_config(guild_id) server_config = server_manager.get_server_config(guild_id)
if not server_config: if not server_config:
print(f"⚠️ No config found for server {guild_id}") print(f"⚠️ No config found for server {guild_id}")
@@ -224,25 +230,41 @@ async def miku_detect_and_join_conversation_for_server(guild_id: int):
# Fetch last 20 messages (for filtering) # Fetch last 20 messages (for filtering)
try: try:
messages = [msg async for msg in channel.history(limit=20)] messages = [msg async for msg in channel.history(limit=20)]
print(f"📜 [Join Conv] Fetched {len(messages)} messages from history")
except Exception as e: except Exception as e:
print(f"⚠️ Failed to fetch channel history for server {guild_id}: {e}") print(f"⚠️ Failed to fetch channel history for server {guild_id}: {e}")
return return
# Filter to messages in last 10 minutes from real users (not bots) # Filter messages based on force mode
if force:
# When forced, use messages from real users (no time limit) - but limit to last 10
recent_msgs = [msg for msg in messages if not msg.author.bot][:10]
print(f"📊 [Join Conv] Force mode: Using last {len(recent_msgs)} messages from users (no time limit)")
else:
# Normal mode: Filter to messages in last 10 minutes from real users (not bots)
recent_msgs = [ recent_msgs = [
msg for msg in messages msg for msg in messages
if not msg.author.bot if not msg.author.bot
and (datetime.now(msg.created_at.tzinfo) - msg.created_at).total_seconds() < 600 and (datetime.now(msg.created_at.tzinfo) - msg.created_at).total_seconds() < 600
] ]
print(f"📊 [Join Conv] Found {len(recent_msgs)} recent messages from users (last 10 min)")
user_ids = set(msg.author.id for msg in recent_msgs) user_ids = set(msg.author.id for msg in recent_msgs)
if not force:
if len(recent_msgs) < 5 or len(user_ids) < 2: if len(recent_msgs) < 5 or len(user_ids) < 2:
# Not enough activity # Not enough activity
print(f"⚠️ [Join Conv] Not enough activity: {len(recent_msgs)} messages, {len(user_ids)} users (need 5+ messages, 2+ users)")
return return
if random.random() > 0.5: if random.random() > 0.5:
print(f"🎲 [Join Conv] Random chance failed (50% chance)")
return # 50% chance to engage return # 50% chance to engage
else:
print(f"✅ [Join Conv] Force mode - bypassing activity checks")
if len(recent_msgs) < 1:
print(f"⚠️ [Join Conv] No messages found in channel history")
return
# Use last 10 messages for context (oldest to newest) # Use last 10 messages for context (oldest to newest)
convo_lines = reversed(recent_msgs[:10]) convo_lines = reversed(recent_msgs[:10])
@@ -374,10 +396,14 @@ async def miku_engage_random_user():
for guild_id in server_manager.servers: for guild_id in server_manager.servers:
await miku_engage_random_user_for_server(guild_id) await miku_engage_random_user_for_server(guild_id)
async def miku_detect_and_join_conversation(): async def miku_detect_and_join_conversation(force: bool = False):
"""Legacy function - now runs for all servers""" """Legacy function - now runs for all servers
Args:
force: If True, bypass activity checks and random chance (for manual triggers)
"""
for guild_id in server_manager.servers: for guild_id in server_manager.servers:
await miku_detect_and_join_conversation_for_server(guild_id) await miku_detect_and_join_conversation_for_server(guild_id, force=force)
async def share_miku_tweet(): async def share_miku_tweet():
"""Legacy function - now runs for all servers""" """Legacy function - now runs for all servers"""

View File

@@ -171,6 +171,14 @@ class MikuCLI:
result = self._post("/autonomous/reaction") result = self._post("/autonomous/reaction")
print(f"{result['message']}") print(f"{result['message']}")
def autonomous_join_conversation(self, guild_id: Optional[int] = None):
"""Trigger detect and join conversation."""
if guild_id:
result = self._post(f"/autonomous/join-conversation?guild_id={guild_id}")
else:
result = self._post("/autonomous/join-conversation")
print(f"{result['message']}")
def autonomous_custom(self, prompt: str, guild_id: Optional[int] = None): def autonomous_custom(self, prompt: str, guild_id: Optional[int] = None):
"""Send custom autonomous message.""" """Send custom autonomous message."""
if guild_id: if guild_id:
@@ -408,6 +416,7 @@ def print_help():
autonomous-engage [server_id] - Engage with conversation autonomous-engage [server_id] - Engage with conversation
autonomous-tweet [server_id] - Post tweet-style message autonomous-tweet [server_id] - Post tweet-style message
autonomous-reaction [server_id] - Generate reaction autonomous-reaction [server_id] - Generate reaction
autonomous-join-conversation [server_id] - Detect and join conversation
autonomous-custom <prompt> [server_id] - Custom autonomous action autonomous-custom <prompt> [server_id] - Custom autonomous action
autonomous-stats - Show autonomous statistics autonomous-stats - Show autonomous statistics
@@ -489,6 +498,9 @@ def execute_command(cli: MikuCLI, command: str, args: list):
elif command == 'autonomous-reaction': elif command == 'autonomous-reaction':
server_id = int(args[0]) if args and args[0].isdigit() else None server_id = int(args[0]) if args and args[0].isdigit() else None
cli.autonomous_reaction(server_id) cli.autonomous_reaction(server_id)
elif command == 'autonomous-join-conversation':
server_id = int(args[0]) if args and args[0].isdigit() else None
cli.autonomous_join_conversation(server_id)
elif command == 'autonomous-custom': elif command == 'autonomous-custom':
if not args: if not args:
print("❌ Usage: autonomous-custom <prompt> [server_id]") print("❌ Usage: autonomous-custom <prompt> [server_id]")
@@ -610,7 +622,7 @@ Examples:
# Autonomous commands # Autonomous commands
auto_parser = subparsers.add_parser('autonomous', help='Autonomous actions') auto_parser = subparsers.add_parser('autonomous', help='Autonomous actions')
auto_parser.add_argument('action', choices=['general', 'engage', 'tweet', 'reaction', 'custom', 'stats']) auto_parser.add_argument('action', choices=['general', 'engage', 'tweet', 'reaction', 'join-conversation', 'custom', 'stats'])
auto_parser.add_argument('--prompt', help='Custom prompt (for custom action)') auto_parser.add_argument('--prompt', help='Custom prompt (for custom action)')
auto_parser.add_argument('--server', type=int, help='Server/guild ID') auto_parser.add_argument('--server', type=int, help='Server/guild ID')
@@ -702,6 +714,8 @@ Examples:
cli.autonomous_tweet(args.server) cli.autonomous_tweet(args.server)
elif args.action == 'reaction': elif args.action == 'reaction':
cli.autonomous_reaction(args.server) cli.autonomous_reaction(args.server)
elif args.action == 'join-conversation':
cli.autonomous_join_conversation(args.server)
elif args.action == 'custom': elif args.action == 'custom':
if not args.prompt: if not args.prompt:
print("❌ --prompt is required for custom action") print("❌ --prompt is required for custom action")