feat: add 'state' field to mood activities for richer Discord presence

- Add 'state' field to all 139 activity entries in activities.yaml
  - Songs: state shows artist (e.g. 'by kz (livetune)')
  - Games: state shows genre (e.g. 'Rhythm Game', 'Sandbox', 'FPS')
- Update pick_activity_for_mood() to return 3-tuple (type, name, state)
- Update update_bot_presence() to pass state to discord.Activity()
- Add state validation in set_activities_for_mood() (optional string)
- Update Web UI editor: view shows state, edit form has state input
- State is fully optional — backward compatible, no breaking changes

The 'state' field appears as a secondary text line in Discord profile
popup, the richest display possible for bot accounts (full Rich Presence
with cover art/buttons is server-side restricted to OAuth applications).
This commit is contained in:
2026-04-24 16:46:39 +03:00
parent 4dc24b7da8
commit 9bc618b526
3 changed files with 582 additions and 460 deletions

View File

@@ -6883,7 +6883,9 @@ function activitiesRenderView(section, mood, entries) {
const label = entry.type === 'listening' ? 'Listening to' : 'Playing';
html += `<div class="act-entry">`;
html += `<span class="act-entry-icon">${icon}</span>`;
html += `<span style="flex:1;">${escapeHtml(entry.name)}</span>`;
html += `<span style="flex:1;">${escapeHtml(entry.name)}`;
if (entry.state) html += ` <span style="color:#aaa; font-size:0.85rem;">— ${escapeHtml(entry.state)}</span>`;
html += `</span>`;
html += `<span style="color:#888; font-size:0.8rem;">weight: ${entry.weight}</span>`;
html += `</div>`;
}
@@ -6902,8 +6904,9 @@ function activitiesRenderEditForm(section, mood, entries) {
html += `<option value="listening" ${e.type === 'listening' ? 'selected' : ''}>🎵 Listening</option>`;
html += `<option value="playing" ${e.type === 'playing' ? 'selected' : ''}>🎮 Playing</option>`;
html += `</select>`;
html += `<input type="text" id="act-name-${section}-${mood}-${i}" value="${escapeHtml(e.name)}" placeholder="Song/Game name">`;
html += `<input type="number" id="act-weight-${section}-${mood}-${i}" value="${e.weight}" min="1" max="20">`;
html += `<input type="text" id="act-name-${section}-${mood}-${i}" value="${escapeHtml(e.name)}" placeholder="Song/Game name" style="flex:2; min-width:120px;">`;
html += `<input type="text" id="act-state-${section}-${mood}-${i}" value="${escapeHtml(e.state || '')}" placeholder="Artist / Genre (optional)" style="flex:1.5; min-width:100px;">`;
html += `<input type="number" id="act-weight-${section}-${mood}-${i}" value="${e.weight}" min="1" max="20" style="width:60px;">`;
html += `<button onclick="activitiesRemoveEntry('${section}','${mood}',${i})" style="background:#c0392b; padding:0.3rem 0.5rem;" title="Remove">✕</button>`;
html += `</div>`;
}
@@ -6949,7 +6952,7 @@ function activitiesAddEntry(section, mood) {
const key = `${section}/${mood}`;
// First, sync current form values to cache
activitiesSyncFormToCache(section, mood);
activitiesEditCache[key].push({ type: 'listening', name: '', weight: 1 });
activitiesEditCache[key].push({ type: 'listening', name: '', state: '', weight: 1 });
activitiesRenderSection(section);
// Keep the mood panel open
const el = document.getElementById(`act-content-${section}-${mood}`);
@@ -6971,9 +6974,11 @@ function activitiesSyncFormToCache(section, mood) {
for (let i = 0; i < entries.length; i++) {
const typeEl = document.getElementById(`act-type-${section}-${mood}-${i}`);
const nameEl = document.getElementById(`act-name-${section}-${mood}-${i}`);
const stateEl = document.getElementById(`act-state-${section}-${mood}-${i}`);
const weightEl = document.getElementById(`act-weight-${section}-${mood}-${i}`);
if (typeEl) entries[i].type = typeEl.value;
if (nameEl) entries[i].name = nameEl.value;
if (stateEl) entries[i].state = stateEl.value || undefined;
if (weightEl) entries[i].weight = parseInt(weightEl.value) || 1;
}
activitiesEditCache[key] = entries;