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