From b38bdf2435443dbbe59d07a005252a7260f705a4 Mon Sep 17 00:00:00 2001 From: koko210Serve Date: Tue, 16 Dec 2025 23:13:19 +0200 Subject: [PATCH] feat: Add advanced engagement submenu with user targeting and engagement types - Replace simple 'Engage Random User' button with expandable submenu - Add user ID input field for targeting specific users - Add engagement type selection: random, activity-based, general, status-based - Update API endpoints to accept user_id and engagement_type parameters - Modify autonomous functions to support targeted engagement - Maintain backward compatibility with random user/type selection as default --- bot/api.py | 41 ++++++++-- bot/static/index.html | 92 +++++++++++++++++++++- bot/utils/autonomous_v1_legacy.py | 124 ++++++++++++++++++++++-------- 3 files changed, 217 insertions(+), 40 deletions(-) diff --git a/bot/api.py b/bot/api.py index b601afe..ae90e20 100644 --- a/bot/api.py +++ b/bot/api.py @@ -268,20 +268,37 @@ async def trigger_autonomous_general(guild_id: int = None): return {"status": "error", "message": "Bot not ready"} @app.post("/autonomous/engage") -async def trigger_autonomous_engage_user(guild_id: int = None): +async def trigger_autonomous_engage_user(guild_id: int = None, user_id: str = None, engagement_type: str = None): # If guild_id is provided, send autonomous engagement only to that server # If no guild_id, send to all servers (legacy behavior) + # user_id: Optional specific user to engage (Discord user ID as string) + # engagement_type: Optional type - 'activity', 'general', 'status', or None for random if globals.client and globals.client.loop and globals.client.loop.is_running(): if guild_id is not None: # Send to specific server only from utils.autonomous import miku_engage_random_user_for_server - globals.client.loop.create_task(miku_engage_random_user_for_server(guild_id)) - return {"status": "ok", "message": f"Autonomous user engagement queued for server {guild_id}"} + globals.client.loop.create_task(miku_engage_random_user_for_server(guild_id, user_id=user_id, engagement_type=engagement_type)) + + # Build detailed message + msg_parts = [f"Autonomous user engagement queued for server {guild_id}"] + if user_id: + msg_parts.append(f"targeting user {user_id}") + if engagement_type: + msg_parts.append(f"with {engagement_type} engagement") + + return {"status": "ok", "message": " ".join(msg_parts)} else: # Send to all servers (legacy behavior) from utils.autonomous import miku_engage_random_user - globals.client.loop.create_task(miku_engage_random_user()) - return {"status": "ok", "message": "Autonomous user engagement queued for all servers"} + globals.client.loop.create_task(miku_engage_random_user(user_id=user_id, engagement_type=engagement_type)) + + msg_parts = ["Autonomous user engagement queued for all servers"] + if user_id: + msg_parts.append(f"targeting user {user_id}") + if engagement_type: + msg_parts.append(f"with {engagement_type} engagement") + + return {"status": "ok", "message": " ".join(msg_parts)} else: return {"status": "error", "message": "Bot not ready"} @@ -841,12 +858,20 @@ async def trigger_autonomous_general_for_server(guild_id: int): return {"status": "error", "message": f"Failed to trigger autonomous message: {e}"} @app.post("/servers/{guild_id}/autonomous/engage") -async def trigger_autonomous_engage_for_server(guild_id: int): +async def trigger_autonomous_engage_for_server(guild_id: int, user_id: str = None, engagement_type: str = None): """Trigger autonomous user engagement for a specific server""" from utils.autonomous import miku_engage_random_user_for_server try: - await miku_engage_random_user_for_server(guild_id) - return {"status": "ok", "message": f"Autonomous user engagement triggered for server {guild_id}"} + await miku_engage_random_user_for_server(guild_id, user_id=user_id, engagement_type=engagement_type) + + # Build detailed message + msg_parts = [f"Autonomous user engagement triggered for server {guild_id}"] + if user_id: + msg_parts.append(f"targeting user {user_id}") + if engagement_type: + msg_parts.append(f"with {engagement_type} engagement") + + return {"status": "ok", "message": " ".join(msg_parts)} except Exception as e: return {"status": "error", "message": f"Failed to trigger user engagement: {e}"} diff --git a/bot/static/index.html b/bot/static/index.html index f620f84..1a622e4 100644 --- a/bot/static/index.html +++ b/bot/static/index.html @@ -702,7 +702,38 @@ - + + +
+ + +
+ @@ -1967,6 +1998,65 @@ async function triggerAutonomous(actionType) { } } +// Toggle Engage User Submenu +function toggleEngageSubmenu() { + const submenu = document.getElementById('engage-submenu'); + if (submenu.style.display === 'none') { + submenu.style.display = 'block'; + } else { + submenu.style.display = 'none'; + } +} + +// Trigger Engage User with parameters +async function triggerEngageUser() { + const selectedServer = document.getElementById('server-select').value; + const userId = document.getElementById('engage-user-id').value.trim(); + const engageType = document.querySelector('input[name="engage-type"]:checked').value; + + try { + let endpoint = '/autonomous/engage'; + const params = new URLSearchParams(); + + // Add guild_id if a specific server is selected + if (selectedServer !== 'all') { + params.append('guild_id', selectedServer); + } + + // Add user_id if specified + if (userId) { + params.append('user_id', userId); + } + + // Add engagement_type if not random + if (engageType !== 'random') { + params.append('engagement_type', engageType); + } + + if (params.toString()) { + endpoint += `?${params.toString()}`; + } + + const response = await fetch(endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + + const result = await response.json(); + + if (response.ok) { + showNotification(result.message || 'Engagement triggered successfully'); + // Optionally collapse the submenu after successful trigger + // toggleEngageSubmenu(); + } else { + throw new Error(result.message || 'Failed to trigger engagement'); + } + } catch (error) { + console.error('Failed to trigger user engagement:', error); + showNotification(error.message || 'Failed to trigger engagement', 'error'); + } +} + // Profile Picture Management async function changeProfilePicture() { const selectedServer = document.getElementById('server-select').value; diff --git a/bot/utils/autonomous_v1_legacy.py b/bot/utils/autonomous_v1_legacy.py index eab4703..4c42cf4 100644 --- a/bot/utils/autonomous_v1_legacy.py +++ b/bot/utils/autonomous_v1_legacy.py @@ -121,8 +121,14 @@ async def miku_say_something_general_for_server(guild_id: int): except Exception as e: print(f"⚠️ Failed to send autonomous message: {e}") -async def miku_engage_random_user_for_server(guild_id: int): - """Miku engages a random user in a specific server""" +async def miku_engage_random_user_for_server(guild_id: int, user_id: str = None, engagement_type: str = None): + """Miku engages a random user in a specific server + + Args: + guild_id: The server ID + user_id: Optional specific user ID to engage (as string). If None, picks random user + engagement_type: Optional engagement style - 'activity', 'general', 'status', or None for auto-detect + """ server_config = server_manager.get_server_config(guild_id) if not server_config: print(f"⚠️ No config found for server {guild_id}") @@ -138,19 +144,37 @@ async def miku_engage_random_user_for_server(guild_id: int): print(f"⚠️ Autonomous channel not found for server {guild_id}") return - members = [ - m for m in guild.members - if m.status in {Status.online, Status.idle, Status.dnd} and not m.bot - ] + # Get target user + if user_id: + # Target specific user + try: + target = guild.get_member(int(user_id)) + if not target: + print(f"⚠️ User {user_id} not found in server {guild_id}") + return + if target.bot: + print(f"⚠️ Cannot engage bot user {user_id}") + return + print(f"🎯 Targeting specific user: {target.display_name} (ID: {user_id})") + except ValueError: + print(f"⚠️ Invalid user ID: {user_id}") + return + else: + # Pick random user + members = [ + m for m in guild.members + if m.status in {Status.online, Status.idle, Status.dnd} and not m.bot + ] + + if not members: + print(f"😴 No available members to talk to in server {guild_id}.") + return + + target = random.choice(members) + print(f"🎲 Randomly selected user: {target.display_name}") time_of_day = get_time_of_day() - if not members: - print(f"😴 No available members to talk to in server {guild_id}.") - return - - target = random.choice(members) - # Initialize server-specific user engagements if guild_id not in _server_user_engagements: _server_user_engagements[guild_id] = {} @@ -176,29 +200,62 @@ async def miku_engage_random_user_for_server(guild_id: int): is_invisible = target.status == Status.offline display_name = target.display_name - prompt = ( - f"Miku is feeling {mood} {emoji} during the {time_of_day}. " - f"She notices {display_name}'s current status is {target.status.name}. " - ) - - if is_invisible: + # Build prompt based on engagement_type + prompt = f"Miku is feeling {mood} {emoji} during the {time_of_day}. " + + if engagement_type == 'activity': + # Force activity-based engagement + if activity_name: + prompt += ( + f"She notices {display_name} is currently playing or doing: {activity_name}. " + f"Miku wants to comment on this activity and start a friendly conversation about it." + ) + else: + prompt += ( + f"She wants to ask {display_name} what they're up to or what they like to do for fun." + ) + elif engagement_type == 'status': + # Force status-based engagement + prompt += f"She notices {display_name}'s current status is {target.status.name}. " + if is_invisible: + prompt += ( + f"Miku suspects that {display_name} is being sneaky and invisible 👻. " + f"She wants to playfully call them out in a fun, teasing, but still affectionate way." + ) + else: + prompt += ( + f"Miku wants to comment on their current status and start a conversation." + ) + elif engagement_type == 'general': + # Force general conversation prompt += ( - f"Miku suspects that {display_name} is being sneaky and invisible 👻. " - f"She wants to playfully call them out in a fun, teasing, but still affectionate way. " - ) - elif activity_name: - prompt += ( - f"They appear to be playing or doing: {activity_name}. " - f"Miku wants to comment on this and start a friendly conversation." + f"Miku wants to casually start a conversation with {display_name}, " + f"maybe ask how they're doing, what they're up to, or talk about something random." ) else: - prompt += ( - f"Miku wants to casually start a conversation with them, maybe ask how they're doing, what they're up to, or even talk about something random with them." - ) + # Auto-detect (original behavior) + prompt += f"She notices {display_name}'s current status is {target.status.name}. " + if is_invisible: + prompt += ( + f"Miku suspects that {display_name} is being sneaky and invisible 👻. " + f"She wants to playfully call them out in a fun, teasing, but still affectionate way. " + ) + elif activity_name: + prompt += ( + f"They appear to be playing or doing: {activity_name}. " + f"Miku wants to comment on this and start a friendly conversation." + ) + else: + prompt += ( + f"Miku wants to casually start a conversation with them, maybe ask how they're doing, what they're up to, or even talk about something random with them." + ) prompt += ( f"\nThe message should be short and reflect Miku's current mood." ) + + if engagement_type: + print(f"💬 Engagement type: {engagement_type}") try: # Use consistent user_id for engaging users to enable conversation history @@ -391,10 +448,15 @@ async def miku_say_something_general(): for guild_id in server_manager.servers: await miku_say_something_general_for_server(guild_id) -async def miku_engage_random_user(): - """Legacy function - now runs for all servers""" +async def miku_engage_random_user(user_id: str = None, engagement_type: str = None): + """Legacy function - now runs for all servers + + Args: + user_id: Optional specific user ID to engage + engagement_type: Optional engagement style + """ 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, user_id=user_id, engagement_type=engagement_type) async def miku_detect_and_join_conversation(force: bool = False): """Legacy function - now runs for all servers