add thread_id for telegram notification
This commit is contained in:
parent
9865caa789
commit
5cbdcbbfe5
3 changed files with 161 additions and 3 deletions
|
|
@ -997,6 +997,7 @@ class AutomationEngine:
|
|||
"""Send message via Telegram Bot API."""
|
||||
bot_token = config.get('bot_token', '').strip()
|
||||
chat_id = config.get('chat_id', '').strip()
|
||||
thread_id = config.get('thread_id', '').strip()
|
||||
if not bot_token or not chat_id:
|
||||
raise ValueError("Bot token and chat ID are required for Telegram")
|
||||
|
||||
|
|
@ -1007,7 +1008,7 @@ class AutomationEngine:
|
|||
|
||||
resp = requests.post(
|
||||
f'https://api.telegram.org/bot{bot_token}/sendMessage',
|
||||
json={"chat_id": chat_id, "text": message, "parse_mode": "HTML"},
|
||||
json={"chat_id": chat_id, "message_thread_id": thread_id, "text": message, "parse_mode": "HTML"},
|
||||
timeout=10,
|
||||
)
|
||||
data = resp.json() if resp.status_code == 200 else {}
|
||||
|
|
|
|||
|
|
@ -4890,7 +4890,7 @@ function _promptNotifyConfig(groupName) {
|
|||
if (type === 'discord_webhook') {
|
||||
fieldsDiv.innerHTML = '<input id="deploy-notify-url" type="text" placeholder="Discord Webhook URL" style="width:100%;padding:9px 12px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:8px;color:#fff;font-size:0.88em;margin-top:6px;box-sizing:border-box;">';
|
||||
} else if (type === 'telegram') {
|
||||
fieldsDiv.innerHTML = '<input id="deploy-notify-token" type="text" placeholder="Bot Token" style="width:100%;padding:9px 12px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:8px;color:#fff;font-size:0.88em;margin-top:6px;box-sizing:border-box;"><input id="deploy-notify-chat" type="text" placeholder="Chat ID" style="width:100%;padding:9px 12px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:8px;color:#fff;font-size:0.88em;margin-top:6px;box-sizing:border-box;">';
|
||||
fieldsDiv.innerHTML = '<input id="deploy-notify-token" type="text" placeholder="Bot Token" style="width:100%;padding:9px 12px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:8px;color:#fff;font-size:0.88em;margin-top:6px;box-sizing:border-box;"><input id="deploy-notify-chat" type="text" placeholder="Chat ID" style="width:100%;padding:9px 12px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:8px;color:#fff;font-size:0.88em;margin-top:6px;box-sizing:border-box;"><input id="deploy-notify-thread" type="text" placeholder="Thread ID" style="width:100%;padding:9px 12px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:8px;color:#fff;font-size:0.88em;margin-top:6px;box-sizing:border-box;">';
|
||||
} else if (type === 'pushbullet') {
|
||||
fieldsDiv.innerHTML = '<input id="deploy-notify-token" type="text" placeholder="Access Token" style="width:100%;padding:9px 12px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:8px;color:#fff;font-size:0.88em;margin-top:6px;box-sizing:border-box;">';
|
||||
} else {
|
||||
|
|
@ -4911,7 +4911,7 @@ function _promptNotifyConfig(groupName) {
|
|||
if (type === 'discord_webhook') {
|
||||
config = { webhook_url: (overlay.querySelector('#deploy-notify-url')?.value || '').trim() };
|
||||
} else if (type === 'telegram') {
|
||||
config = { bot_token: (overlay.querySelector('#deploy-notify-token')?.value || '').trim(), chat_id: (overlay.querySelector('#deploy-notify-chat')?.value || '').trim() };
|
||||
config = { bot_token: (overlay.querySelector('#deploy-notify-token')?.value || '').trim(), chat_id: (overlay.querySelector('#deploy-notify-chat')?.value || '').trim(), thread_id: (overlay.querySelector('#deploy-notify-thread')?.value || '').trim() };
|
||||
} else if (type === 'pushbullet') {
|
||||
config = { access_token: (overlay.querySelector('#deploy-notify-token')?.value || '').trim() };
|
||||
} else {
|
||||
|
|
@ -6004,6 +6004,7 @@ function _renderBlockConfigFields(slotKey, blockType, config) {
|
|||
if (blockType === 'telegram') {
|
||||
const botToken = _escAttr(config.bot_token || '');
|
||||
const chatId = _escAttr(config.chat_id || '');
|
||||
const threadID = _escAttr(config.thread_id || '');
|
||||
return `<div class="config-row">
|
||||
<label>Bot Token</label>
|
||||
<input type="text" id="cfg-${slotKey}-bot_token" value="${botToken}" placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11">
|
||||
|
|
@ -6012,6 +6013,10 @@ function _renderBlockConfigFields(slotKey, blockType, config) {
|
|||
<label>Chat ID</label>
|
||||
<input type="text" id="cfg-${slotKey}-chat_id" value="${chatId}" placeholder="-1001234567890 or @channelname">
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label>Thread ID</label>
|
||||
<input type="text" id="cfg-${slotKey}-thread_id" value="${threadID}" placeholder="Optional integer">
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label>Message</label>
|
||||
<textarea id="cfg-${slotKey}-message" placeholder="Message with {variables}...">${config.message || '{name} completed with status: {status}'}</textarea>
|
||||
|
|
@ -6291,6 +6296,7 @@ function _readPlacedConfig(slotKey) {
|
|||
return {
|
||||
bot_token: document.getElementById('cfg-' + slotKey + '-bot_token')?.value?.trim() || '',
|
||||
chat_id: document.getElementById('cfg-' + slotKey + '-chat_id')?.value?.trim() || '',
|
||||
thread_id: document.getElementById('cfg-' + slotKey + '-thread_id')?.value?.trim() || '',
|
||||
message: document.getElementById('cfg-' + slotKey + '-message')?.value || '',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3094,6 +3094,150 @@ function openLibraryHistoryModal() {
|
|||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Quarantine tab — rendered inside the Library History modal as a third
|
||||
// tab next to Downloads + Server Imports. Reuses the existing list +
|
||||
// pagination chrome; provides per-row Approve / Recover / Delete actions.
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
async function loadQuarantineList() {
|
||||
const list = document.getElementById('library-history-list');
|
||||
const pagination = document.getElementById('library-history-pagination');
|
||||
const sourceBar = document.getElementById('history-source-bar');
|
||||
if (!list) return;
|
||||
list.innerHTML = '<div class="library-history-loading">Loading...</div>';
|
||||
if (pagination) pagination.innerHTML = '';
|
||||
if (sourceBar) sourceBar.style.display = 'none';
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/quarantine/list');
|
||||
const data = await resp.json();
|
||||
const entries = data.entries || [];
|
||||
const countEl = document.getElementById('history-quarantine-count');
|
||||
if (countEl) countEl.textContent = entries.length;
|
||||
|
||||
if (!data.success) {
|
||||
list.innerHTML = `<div class="library-history-empty">Error: ${escapeHtml(data.error || 'Failed to load')}</div>`;
|
||||
return;
|
||||
}
|
||||
if (entries.length === 0) {
|
||||
list.innerHTML = '<div class="library-history-empty">🛡️<br><br>No quarantined files. Nice and clean.</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = entries.map(renderQuarantineEntry).join('');
|
||||
} catch (err) {
|
||||
console.error('Error loading quarantine entries:', err);
|
||||
list.innerHTML = '<div class="library-history-empty">Error loading quarantine</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderQuarantineEntry(entry) {
|
||||
const triggerLabels = { integrity: 'Duration / Integrity', acoustid: 'AcoustID Mismatch', bit_depth: 'Bit Depth Filter', unknown: 'Unknown' };
|
||||
const triggerColors = { integrity: '#facc15', acoustid: '#ef5350', bit_depth: '#fb923c', unknown: '#888' };
|
||||
const triggerLabel = triggerLabels[entry.trigger] || entry.trigger || 'Unknown';
|
||||
const triggerColor = triggerColors[entry.trigger] || '#888';
|
||||
|
||||
const id = escapeHtml(entry.id);
|
||||
const approveLabel = entry.has_full_context ? 'Approve' : 'Recover';
|
||||
const approveTitle = entry.has_full_context
|
||||
? 'Re-run post-processing with only the failing check skipped'
|
||||
: 'Legacy entry — move to Staging, finish via Import flow';
|
||||
const approveCall = entry.has_full_context
|
||||
? `approveQuarantineEntry('${id}')`
|
||||
: `recoverQuarantineEntry('${id}')`;
|
||||
|
||||
const meta = [entry.expected_artist, entry.original_filename].filter(Boolean).join(' — ');
|
||||
const triggerBadge = `<span class="library-history-badge" style="border-color:${triggerColor};color:${triggerColor}">${escapeHtml(triggerLabel)}</span>`;
|
||||
const reasonDetail = `<div class="library-history-entry-source"><span class="lh-prov-label">Reason:</span> ${escapeHtml(entry.reason || 'Unknown')}</div>`;
|
||||
|
||||
return `<div class="library-history-entry lh-expandable" onclick="this.classList.toggle('lh-expanded')">
|
||||
<div class="library-history-thumb-placeholder">🛡️</div>
|
||||
<div class="library-history-entry-content">
|
||||
<div class="library-history-entry-row1">
|
||||
<div class="library-history-entry-text">
|
||||
<div class="library-history-entry-title">${escapeHtml(entry.expected_track || entry.original_filename || 'Unknown')}</div>
|
||||
<div class="library-history-entry-meta">${escapeHtml(meta)}</div>
|
||||
</div>
|
||||
<div class="library-history-entry-badges">${triggerBadge}</div>
|
||||
<div class="library-history-entry-time">${formatHistoryTime(entry.timestamp)}</div>
|
||||
<button class="lh-audit-btn" title="${approveTitle}" onclick="event.stopPropagation();${approveCall}">${approveLabel}</button>
|
||||
<button class="lh-audit-btn" title="Delete permanently" style="border-color:rgba(248,113,113,0.4);color:#f87171" onclick="event.stopPropagation();deleteQuarantineEntry('${id}')">Delete</button>
|
||||
<span class="lh-expand-btn">▾</span>
|
||||
</div>
|
||||
<div class="library-history-entry-details">
|
||||
${reasonDetail}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function approveQuarantineEntry(entryId) {
|
||||
const ok = await showConfirmDialog({
|
||||
title: 'Approve Quarantined File',
|
||||
message: 'Re-run post-processing for this file with only the failing check skipped. The file will be tagged, lyrics generated, and moved into your library. Other quality gates (AcoustID + bit-depth) still run.',
|
||||
confirmText: 'Approve & Import',
|
||||
cancelText: 'Cancel',
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
const r = await fetch(`/api/quarantine/${encodeURIComponent(entryId)}/approve`, { method: 'POST' });
|
||||
const data = await r.json();
|
||||
if (!data.success) {
|
||||
showToast(`Approve failed: ${data.error}`, 'error');
|
||||
} else {
|
||||
showToast(`Approved — skipped ${data.trigger_bypassed} check, re-running pipeline.`, 'success');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(`Approve failed: ${err.message}`, 'error');
|
||||
}
|
||||
loadQuarantineList();
|
||||
}
|
||||
|
||||
async function recoverQuarantineEntry(entryId) {
|
||||
const ok = await showConfirmDialog({
|
||||
title: 'Recover To Staging',
|
||||
message: 'Legacy entry — no embedded context. The file will be moved to your Staging folder so you can finish via the Import page (manual match).',
|
||||
confirmText: 'Move To Staging',
|
||||
cancelText: 'Cancel',
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
const r = await fetch(`/api/quarantine/${encodeURIComponent(entryId)}/recover`, { method: 'POST' });
|
||||
const data = await r.json();
|
||||
if (!data.success) {
|
||||
showToast(`Recover failed: ${data.error}`, 'error');
|
||||
} else {
|
||||
showToast('Moved to Staging — finish via the Import page.', 'success');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(`Recover failed: ${err.message}`, 'error');
|
||||
}
|
||||
loadQuarantineList();
|
||||
}
|
||||
|
||||
async function deleteQuarantineEntry(entryId) {
|
||||
const ok = await showConfirmDialog({
|
||||
title: 'Delete Quarantined File',
|
||||
message: 'This permanently removes the file and its metadata sidecar. Cannot be undone.',
|
||||
confirmText: 'Delete',
|
||||
cancelText: 'Cancel',
|
||||
destructive: true,
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
const r = await fetch(`/api/quarantine/${encodeURIComponent(entryId)}`, { method: 'DELETE' });
|
||||
const data = await r.json();
|
||||
if (!data.success) {
|
||||
showToast(`Delete failed: ${data.error}`, 'error');
|
||||
} else {
|
||||
showToast('Quarantined file deleted.', 'success');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(`Delete failed: ${err.message}`, 'error');
|
||||
}
|
||||
loadQuarantineList();
|
||||
}
|
||||
|
||||
function closeLibraryHistoryModal() {
|
||||
const overlay = document.getElementById('library-history-overlay');
|
||||
if (overlay) overlay.classList.add('hidden');
|
||||
|
|
@ -3110,6 +3254,12 @@ function switchHistoryTab(tab) {
|
|||
|
||||
async function loadLibraryHistory() {
|
||||
const { tab, page, limit } = _libraryHistoryState;
|
||||
if (tab === 'quarantine') {
|
||||
// Refresh the count for the other two tabs in the background so
|
||||
// the badge stays accurate when the user switches over.
|
||||
loadQuarantineList();
|
||||
return;
|
||||
}
|
||||
const list = document.getElementById('library-history-list');
|
||||
const pagination = document.getElementById('library-history-pagination');
|
||||
if (!list) return;
|
||||
|
|
@ -6199,6 +6349,7 @@ const TOOL_HELP_CONTENT = {
|
|||
<ul>
|
||||
<li><strong>Bot Token:</strong> Your Telegram bot token (from @BotFather)</li>
|
||||
<li><strong>Chat ID:</strong> The chat/group ID to send messages to</li>
|
||||
<li><strong>Thread ID:</strong> Thread ID</li>
|
||||
<li><strong>Message Template:</strong> Custom message with variable placeholders</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue