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
This commit is contained in:
41
bot/api.py
41
bot/api.py
@@ -268,20 +268,37 @@ async def trigger_autonomous_general(guild_id: int = None):
|
|||||||
return {"status": "error", "message": "Bot not ready"}
|
return {"status": "error", "message": "Bot not ready"}
|
||||||
|
|
||||||
@app.post("/autonomous/engage")
|
@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 guild_id is provided, send autonomous engagement only to that server
|
||||||
# If no guild_id, send to all servers (legacy behavior)
|
# 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 globals.client and globals.client.loop and globals.client.loop.is_running():
|
||||||
if guild_id is not None:
|
if guild_id is not None:
|
||||||
# Send to specific server only
|
# Send to specific server only
|
||||||
from utils.autonomous import miku_engage_random_user_for_server
|
from utils.autonomous import miku_engage_random_user_for_server
|
||||||
globals.client.loop.create_task(miku_engage_random_user_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))
|
||||||
return {"status": "ok", "message": f"Autonomous user engagement queued for server {guild_id}"}
|
|
||||||
|
# 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:
|
else:
|
||||||
# Send to all servers (legacy behavior)
|
# Send to all servers (legacy behavior)
|
||||||
from utils.autonomous import miku_engage_random_user
|
from utils.autonomous import miku_engage_random_user
|
||||||
globals.client.loop.create_task(miku_engage_random_user())
|
globals.client.loop.create_task(miku_engage_random_user(user_id=user_id, engagement_type=engagement_type))
|
||||||
return {"status": "ok", "message": "Autonomous user engagement queued for all servers"}
|
|
||||||
|
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:
|
else:
|
||||||
return {"status": "error", "message": "Bot not ready"}
|
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}"}
|
return {"status": "error", "message": f"Failed to trigger autonomous message: {e}"}
|
||||||
|
|
||||||
@app.post("/servers/{guild_id}/autonomous/engage")
|
@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"""
|
"""Trigger autonomous user engagement for a specific server"""
|
||||||
from utils.autonomous import miku_engage_random_user_for_server
|
from utils.autonomous import miku_engage_random_user_for_server
|
||||||
try:
|
try:
|
||||||
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)
|
||||||
return {"status": "ok", "message": f"Autonomous user engagement triggered for server {guild_id}"}
|
|
||||||
|
# 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:
|
except Exception as e:
|
||||||
return {"status": "error", "message": f"Failed to trigger user engagement: {e}"}
|
return {"status": "error", "message": f"Failed to trigger user engagement: {e}"}
|
||||||
|
|
||||||
|
|||||||
@@ -702,7 +702,38 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="triggerAutonomous('general')">Say Something General</button>
|
<button onclick="triggerAutonomous('general')">Say Something General</button>
|
||||||
<button onclick="triggerAutonomous('engage')">Engage Random User</button>
|
|
||||||
|
<!-- Engage User Submenu -->
|
||||||
|
<div style="margin-bottom: 1rem;">
|
||||||
|
<button onclick="toggleEngageSubmenu()">Engage User ▼</button>
|
||||||
|
<div id="engage-submenu" style="display: none; margin-left: 1rem; margin-top: 0.5rem; padding: 1rem; background: #1e1e1e; border: 1px solid #444; border-radius: 4px;">
|
||||||
|
<div style="margin-bottom: 0.5rem;">
|
||||||
|
<label for="engage-user-id" style="display: block; margin-bottom: 0.3rem;">User ID (leave empty for random):</label>
|
||||||
|
<input type="text" id="engage-user-id" placeholder="User ID" style="width: 200px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 0.5rem;">
|
||||||
|
<label style="display: block; margin-bottom: 0.3rem;">Engagement Type:</label>
|
||||||
|
<div style="margin-left: 0.5rem;">
|
||||||
|
<label style="display: block; margin-bottom: 0.2rem;">
|
||||||
|
<input type="radio" name="engage-type" value="random" checked> Random (auto-detect)
|
||||||
|
</label>
|
||||||
|
<label style="display: block; margin-bottom: 0.2rem;">
|
||||||
|
<input type="radio" name="engage-type" value="activity"> Activity-based (comment on what they're doing)
|
||||||
|
</label>
|
||||||
|
<label style="display: block; margin-bottom: 0.2rem;">
|
||||||
|
<input type="radio" name="engage-type" value="general"> General conversation
|
||||||
|
</label>
|
||||||
|
<label style="display: block; margin-bottom: 0.2rem;">
|
||||||
|
<input type="radio" name="engage-type" value="status"> Status-based (online/idle/invisible)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onclick="triggerEngageUser()">🚀 Engage User</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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="triggerAutonomous('join-conversation')">Detect and Join Conversation</button>
|
||||||
@@ -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
|
// Profile Picture Management
|
||||||
async function changeProfilePicture() {
|
async function changeProfilePicture() {
|
||||||
const selectedServer = document.getElementById('server-select').value;
|
const selectedServer = document.getElementById('server-select').value;
|
||||||
|
|||||||
@@ -121,8 +121,14 @@ async def miku_say_something_general_for_server(guild_id: int):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Failed to send autonomous message: {e}")
|
print(f"⚠️ Failed to send autonomous message: {e}")
|
||||||
|
|
||||||
async def miku_engage_random_user_for_server(guild_id: int):
|
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"""
|
"""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)
|
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}")
|
||||||
@@ -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}")
|
print(f"⚠️ Autonomous channel not found for server {guild_id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
members = [
|
# Get target user
|
||||||
m for m in guild.members
|
if user_id:
|
||||||
if m.status in {Status.online, Status.idle, Status.dnd} and not m.bot
|
# 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()
|
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
|
# Initialize server-specific user engagements
|
||||||
if guild_id not in _server_user_engagements:
|
if guild_id not in _server_user_engagements:
|
||||||
_server_user_engagements[guild_id] = {}
|
_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
|
is_invisible = target.status == Status.offline
|
||||||
display_name = target.display_name
|
display_name = target.display_name
|
||||||
|
|
||||||
prompt = (
|
# Build prompt based on engagement_type
|
||||||
f"Miku is feeling {mood} {emoji} during the {time_of_day}. "
|
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 engagement_type == 'activity':
|
||||||
|
# Force activity-based engagement
|
||||||
if is_invisible:
|
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 += (
|
prompt += (
|
||||||
f"Miku suspects that {display_name} is being sneaky and invisible 👻. "
|
f"Miku wants to casually start a conversation with {display_name}, "
|
||||||
f"She wants to playfully call them out in a fun, teasing, but still affectionate way. "
|
f"maybe ask how they're doing, what they're up to, or talk about something random."
|
||||||
)
|
|
||||||
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:
|
else:
|
||||||
prompt += (
|
# Auto-detect (original behavior)
|
||||||
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"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 += (
|
prompt += (
|
||||||
f"\nThe message should be short and reflect Miku's current mood."
|
f"\nThe message should be short and reflect Miku's current mood."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if engagement_type:
|
||||||
|
print(f"💬 Engagement type: {engagement_type}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Use consistent user_id for engaging users to enable conversation history
|
# 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:
|
for guild_id in server_manager.servers:
|
||||||
await miku_say_something_general_for_server(guild_id)
|
await miku_say_something_general_for_server(guild_id)
|
||||||
|
|
||||||
async def miku_engage_random_user():
|
async def miku_engage_random_user(user_id: str = None, engagement_type: str = None):
|
||||||
"""Legacy function - now runs for all servers"""
|
"""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:
|
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):
|
async def miku_detect_and_join_conversation(force: bool = False):
|
||||||
"""Legacy function - now runs for all servers
|
"""Legacy function - now runs for all servers
|
||||||
|
|||||||
Reference in New Issue
Block a user