nothumanallowed 16.0.17 → 16.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/constants.mjs +1 -1
- package/src/services/message-responder.mjs +273 -19
- package/src/services/tool-executor.mjs +113 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.19",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '16.0.
|
|
8
|
+
export const VERSION = '16.0.19';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -1596,21 +1596,152 @@ class TelegramResponder {
|
|
|
1596
1596
|
}
|
|
1597
1597
|
|
|
1598
1598
|
/**
|
|
1599
|
-
* Detect a generic anaphoric
|
|
1600
|
-
*
|
|
1599
|
+
* Detect a generic anaphoric verb. Covers 15 verbs in IT+EN.
|
|
1600
|
+
* Order matters: more-specific patterns come BEFORE generic delete/edit
|
|
1601
|
+
* (e.g. "spostalo" must NOT be classified as delete because of '...alo').
|
|
1602
|
+
* Returns the verb string or null.
|
|
1601
1603
|
*/
|
|
1602
1604
|
_detectAnaphoricAction(userMessage) {
|
|
1603
1605
|
const t = (userMessage || '').trim();
|
|
1604
1606
|
if (!t) return null;
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
+
|
|
1608
|
+
// YES/CONFIRM — short standalone tokens
|
|
1609
|
+
if (/^\s*(s[ìi]\b|si\s|sì\s|ok\b|okay\b|certo\b|certamente\b|d'?accordo\b|fai\b|fallo|procedi|esegui|conferm[oa]|yes\b|yep\b|confirm\b|do\s*it|go\s*ahead)/i.test(t)) return 'confirm';
|
|
1610
|
+
|
|
1611
|
+
// MOVE/RESCHEDULE — order: BEFORE delete, else "sposta" can be confused
|
|
1612
|
+
if (/\b(spost[ao]l?[oaie]?|sposta|rimand|rinvi[ao]|riprogramm|posticip|sposta?lo|sposta?la|move|reschedul|postpone)\w*/i.test(t)) return 'move';
|
|
1613
|
+
|
|
1614
|
+
// RENAME
|
|
1615
|
+
if (/\b(rinomin|rename|chiamalo|chiamala)\w*/i.test(t)) return 'rename';
|
|
1616
|
+
|
|
1617
|
+
// MARK READ / UNREAD (must come before generic "open/leggi")
|
|
1618
|
+
if (/\b(segna(?:lo|la|li|le)?\s+come\s+(non\s+letto|unread)|mark\s+unread)\b/i.test(t)) return 'mark_unread';
|
|
1619
|
+
if (/\b(segna(?:lo|la|li|le)?\s+come\s+letto|mark\s+(?:as\s+)?read|gi[àa]\s+letto|letto\s+gi[àa])\b/i.test(t)) return 'mark_read';
|
|
1620
|
+
|
|
1621
|
+
// ARCHIVE
|
|
1622
|
+
if (/\b(archivi)\w*/i.test(t)) return 'archive';
|
|
1623
|
+
|
|
1624
|
+
// LABEL / TAG
|
|
1625
|
+
if (/\b(etichett|categoriz|tag\s|aggiungi\s+(?:label|etichetta)|label\b)\w*/i.test(t)) return 'label';
|
|
1626
|
+
|
|
1627
|
+
// FORWARD
|
|
1628
|
+
if (/\b(inoltra|inoltralo|inoltrala|forward|gira(?:lo|la)?)\w*/i.test(t)) return 'forward';
|
|
1629
|
+
|
|
1630
|
+
// SHARE
|
|
1631
|
+
if (/\b(condivid|condividilo|condividila|share|invia\s+link)\w*/i.test(t)) return 'share';
|
|
1632
|
+
|
|
1633
|
+
// PRIORITY change
|
|
1634
|
+
if (/\b(prioriti?z|priority|priorit[àa])\w*/i.test(t)) return 'priority';
|
|
1635
|
+
|
|
1636
|
+
// SNOOZE
|
|
1637
|
+
if (/\b(snooze|rimanda\s+notifica|posticipa\s+(?:notifica|reminder|promemoria))\w*/i.test(t)) return 'snooze';
|
|
1638
|
+
|
|
1639
|
+
// UNDO
|
|
1640
|
+
if (/\b(annulla|undo|disfa|disfa\s+l'?ultima)\w*/i.test(t)) return 'undo';
|
|
1641
|
+
|
|
1642
|
+
// EDIT/MODIFY — generic. Must come AFTER specific edits (rename/label/priority).
|
|
1643
|
+
if (/\b(modifi|aggiorn|cambi(?!a\s+canale)|edit|update)\w*/i.test(t)) return 'edit';
|
|
1644
|
+
|
|
1645
|
+
// DELETE — generic
|
|
1607
1646
|
if (/(cancell|elimin|rimuov|delete|remove)\w*\s*[!.?]?$/i.test(t)) return 'delete';
|
|
1608
|
-
|
|
1647
|
+
|
|
1648
|
+
// COMPLETE / DONE
|
|
1649
|
+
if (/(complet|don[ei]\b|spunt|finit|fatt)\w*\s*[!.?]?$/i.test(t)) return 'complete';
|
|
1650
|
+
|
|
1651
|
+
// REPLY
|
|
1609
1652
|
if (/(rispond|reply|risp\b)\w*\s*[!.?]?$/i.test(t)) return 'reply';
|
|
1610
|
-
|
|
1653
|
+
|
|
1654
|
+
// OPEN / VIEW / READ
|
|
1655
|
+
if (/(apri|open|leggi|read|mostra|view|visualizz)\w*\s*[!.?]?$/i.test(t)) return 'open';
|
|
1656
|
+
|
|
1611
1657
|
return null;
|
|
1612
1658
|
}
|
|
1613
1659
|
|
|
1660
|
+
/**
|
|
1661
|
+
* Extract structured parameters for a given verb against a target item.
|
|
1662
|
+
* Uses cheap regex first, then a tiny LLM NLU call when needed.
|
|
1663
|
+
* Returns {} for verbs that don't need params (delete, complete, archive...).
|
|
1664
|
+
*/
|
|
1665
|
+
async _extractParamsForVerb(verb, kind, userText, config) {
|
|
1666
|
+
const text = String(userText || '');
|
|
1667
|
+
// ── No-param verbs ────────────────────────────────────────────────────
|
|
1668
|
+
if (['delete', 'complete', 'archive', 'mark_read', 'mark_unread', 'share', 'open', 'snooze', 'undo', 'confirm'].includes(verb)) {
|
|
1669
|
+
return {};
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// ── PRIORITY: regex extract high/medium/low ──────────────────────────
|
|
1673
|
+
if (verb === 'priority') {
|
|
1674
|
+
if (/\b(alta|high|urgent[ei]?|importante)\b/i.test(text)) return { priority: 'high' };
|
|
1675
|
+
if (/\b(media|medium|normale)\b/i.test(text)) return { priority: 'medium' };
|
|
1676
|
+
if (/\b(bassa|low|secondaria|non\s+urgente)\b/i.test(text)) return { priority: 'low' };
|
|
1677
|
+
return {};
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// ── LABEL: extract quoted or bare label after the keyword ─────────────
|
|
1681
|
+
if (verb === 'label') {
|
|
1682
|
+
const m = text.match(/(?:etichett[ao]|tag(?:ga)?(?:lo|la)?|label)\s+(?:come\s+|with\s+|as\s+)?["']?([\w\-#/]+)["']?/i);
|
|
1683
|
+
if (m) return { label: m[1] };
|
|
1684
|
+
return {};
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// ── FORWARD: extract recipient email ─────────────────────────────────
|
|
1688
|
+
if (verb === 'forward') {
|
|
1689
|
+
const m = text.match(/[\w.+-]+@[\w-]+\.[\w.-]+/);
|
|
1690
|
+
if (m) return { to: m[0] };
|
|
1691
|
+
return {};
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// ── RENAME: extract new name (quoted or after "in/a") ─────────────────
|
|
1695
|
+
if (verb === 'rename') {
|
|
1696
|
+
const m1 = text.match(/(?:rinomina(?:lo|la)?|rename(?:\s+it)?|chiamal[oa])\s+(?:in\s+|a\s+|to\s+|as\s+)?["']([^"']+)["']/i);
|
|
1697
|
+
if (m1) return { newName: m1[1] };
|
|
1698
|
+
const m2 = text.match(/(?:rinomina(?:lo|la)?|rename(?:\s+it)?|chiamal[oa])\s+(?:in\s+|a\s+|to\s+|as\s+)?([\w\-./]+\.\w{2,5})/i);
|
|
1699
|
+
if (m2) return { newName: m2[1] };
|
|
1700
|
+
const m3 = text.match(/(?:rinomina(?:lo|la)?|rename(?:\s+it)?|chiamal[oa])\s+(?:in\s+|a\s+|to\s+|as\s+)([^\n!.?]{3,80})/i);
|
|
1701
|
+
if (m3) return { newName: m3[1].trim() };
|
|
1702
|
+
return {};
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// ── MOVE: needs newStart/newEnd — use existing _nluExtractCalendarMove ──
|
|
1706
|
+
if (verb === 'move' && kind === 'calendar') {
|
|
1707
|
+
try {
|
|
1708
|
+
const parsed = await this._nluExtractCalendarMove(text, config);
|
|
1709
|
+
if (parsed) return { newStart: parsed.newStart, newEnd: parsed.newEnd };
|
|
1710
|
+
} catch {}
|
|
1711
|
+
return {};
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// ── EDIT: free-form. Tiny LLM extractor returning a partial object ────
|
|
1715
|
+
if (verb === 'edit') {
|
|
1716
|
+
try {
|
|
1717
|
+
const sys =
|
|
1718
|
+
'You are a parameter extractor for an EDIT command. Given the user instruction and ' +
|
|
1719
|
+
`the entity kind (${kind}), return STRICT JSON with the fields to update. ` +
|
|
1720
|
+
'Fields by kind: ' +
|
|
1721
|
+
'calendar={summary?,start?,end?,location?,description?}; ' +
|
|
1722
|
+
'email={subject?,body?}; ' +
|
|
1723
|
+
'task={description?,priority?,due?}; ' +
|
|
1724
|
+
'contact={name?,email?,phone?}; ' +
|
|
1725
|
+
'drive={name?}; note={title?,body?}; ' +
|
|
1726
|
+
'reminder={message?,when?}; gtask={title?,due?}. ' +
|
|
1727
|
+
'ONLY include fields the user explicitly mentioned. No extra prose.';
|
|
1728
|
+
const raw = await callLLM(config, sys, text, { max_tokens: 200, temperature: 0.1 });
|
|
1729
|
+
const m = raw.match(/\{[\s\S]*\}/);
|
|
1730
|
+
if (m) return JSON.parse(m[0]);
|
|
1731
|
+
} catch {}
|
|
1732
|
+
return {};
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// ── REPLY: body extraction — let downstream handler ask user if missing ──
|
|
1736
|
+
if (verb === 'reply') {
|
|
1737
|
+
const m = text.match(/(?:rispond[ie](?:gli)?|reply)\s+(?:con\s+|with\s+)?["']([^"']+)["']/i);
|
|
1738
|
+
if (m) return { body: m[1] };
|
|
1739
|
+
return {};
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
return {};
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1614
1745
|
/**
|
|
1615
1746
|
* Execute an anaphoric verb (delete/complete/reply/open/confirm) against
|
|
1616
1747
|
* an item resolved by _resolveAnaphoric. The (verb, kind) pair maps to a
|
|
@@ -1624,11 +1755,104 @@ class TelegramResponder {
|
|
|
1624
1755
|
if (chatId) this._recordAudit(chatId, { tool: toolName, success, summary });
|
|
1625
1756
|
return { action: toolName, success, message };
|
|
1626
1757
|
};
|
|
1758
|
+
const safe = async (toolName, args, okMsg, summary) => {
|
|
1759
|
+
try { await executeTool(toolName, args, config); return auditAndReturn(toolName, true, okMsg, summary); }
|
|
1760
|
+
catch (e) { return auditAndReturn(toolName, false, `Errore: ${e.message}`, ''); }
|
|
1761
|
+
};
|
|
1762
|
+
const id = item.eventId || item.messageId || item.fileId || item.taskId || item.id;
|
|
1763
|
+
const label = item.subject || item.summary || item.name || item.title || item.description || item.message || String(id);
|
|
1764
|
+
const params = await this._extractParamsForVerb(verb, kind, userText, config);
|
|
1627
1765
|
|
|
1628
1766
|
// confirm = treat as the pending action (default: delete) when there's
|
|
1629
1767
|
// a single recently-listed item.
|
|
1630
1768
|
const effective = verb === 'confirm' ? 'delete' : verb;
|
|
1631
1769
|
|
|
1770
|
+
// ── MOVE/RESCHEDULE ──────────────────────────────────────────────────
|
|
1771
|
+
if (effective === 'move') {
|
|
1772
|
+
if (kind === 'calendar' && id && params.newStart) {
|
|
1773
|
+
return await safe('calendar_move',
|
|
1774
|
+
{ eventId: id, newStart: params.newStart, newEnd: params.newEnd || this._addMinutesIso(params.newStart, 60) },
|
|
1775
|
+
`Spostato "${label}" a ${this._formatDateIT(params.newStart.slice(0, 10))} alle ${params.newStart.slice(11, 16)}.`, label);
|
|
1776
|
+
}
|
|
1777
|
+
if (kind === 'drive' && id && params.newName) {
|
|
1778
|
+
return await safe('drive_move', { fileId: id, folderId: params.newName }, `Spostato "${label}".`, label);
|
|
1779
|
+
}
|
|
1780
|
+
if (!params.newStart) {
|
|
1781
|
+
return { action: 'move_pending', success: false, message: `A quando vuoi spostare "${label}"? Es. "venerdì 23 maggio alle 15".` };
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// ── RENAME ────────────────────────────────────────────────────────────
|
|
1786
|
+
if (effective === 'rename' && params.newName) {
|
|
1787
|
+
if (kind === 'drive' && id) return await safe('drive_rename', { fileId: id, newName: params.newName }, `Rinominato "${label}" → "${params.newName}".`, params.newName);
|
|
1788
|
+
if (kind === 'note' && id) return await safe('note_update', { id, title: params.newName }, `Rinominata la nota "${label}" → "${params.newName}".`, params.newName);
|
|
1789
|
+
if (kind === 'contact' && id) return await safe('contact_update', { id, name: params.newName }, `Rinominato il contatto "${label}" → "${params.newName}".`, params.newName);
|
|
1790
|
+
if (kind === 'task' && id) return await safe('task_edit', { id, description: params.newName }, `Rinominato il task → "${params.newName}".`, params.newName);
|
|
1791
|
+
}
|
|
1792
|
+
if (effective === 'rename' && !params.newName) {
|
|
1793
|
+
return { action: 'rename_pending', success: false, message: `Come vuoi rinominare "${label}"?` };
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// ── EDIT (free-form fields) ──────────────────────────────────────────
|
|
1797
|
+
if (effective === 'edit' && Object.keys(params).length > 0 && id) {
|
|
1798
|
+
if (kind === 'calendar') return await safe('calendar_update', { eventId: id, ...params }, `Aggiornato "${label}".`, label);
|
|
1799
|
+
if (kind === 'email') return await safe('gmail_draft_update', { messageId: id, ...params }, `Aggiornata la bozza email.`, label);
|
|
1800
|
+
if (kind === 'task') return await safe('task_edit', { id, ...params }, `Aggiornato il task "${label}".`, label);
|
|
1801
|
+
if (kind === 'contact') return await safe('contact_update', { id, ...params }, `Aggiornato il contatto "${label}".`, label);
|
|
1802
|
+
if (kind === 'note') return await safe('note_update', { id, ...params }, `Aggiornata la nota "${label}".`, label);
|
|
1803
|
+
if (kind === 'reminder') return await safe('reminder_update', { id, ...params }, `Aggiornato il promemoria.`, label);
|
|
1804
|
+
if (kind === 'gtask') return await safe('gtask_edit', { id, ...params }, `Aggiornato il task Google.`, label);
|
|
1805
|
+
if (kind === 'drive' && params.name) return await safe('drive_rename', { fileId: id, newName: params.name }, `Rinominato il file → "${params.name}".`, params.name);
|
|
1806
|
+
}
|
|
1807
|
+
if (effective === 'edit' && Object.keys(params).length === 0) {
|
|
1808
|
+
return { action: 'edit_pending', success: false, message: `Cosa vuoi modificare di "${label}"? (es. titolo, orario, descrizione, priorità)` };
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// ── MARK READ/UNREAD (email) ─────────────────────────────────────────
|
|
1812
|
+
if (effective === 'mark_read' && kind === 'email' && id) {
|
|
1813
|
+
return await safe('gmail_mark_read', { messageId: id }, `Segnata come letta: "${label}".`, label);
|
|
1814
|
+
}
|
|
1815
|
+
if (effective === 'mark_unread' && kind === 'email' && id) {
|
|
1816
|
+
return await safe('gmail_mark_unread', { messageId: id }, `Segnata come NON letta: "${label}".`, label);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// ── ARCHIVE ──────────────────────────────────────────────────────────
|
|
1820
|
+
if (effective === 'archive') {
|
|
1821
|
+
if (kind === 'email' && id) return await safe('gmail_archive', { messageId: id }, `Archiviata email "${label}".`, label);
|
|
1822
|
+
if (kind === 'task' && id) return await safe('task_archive', { id }, `Archiviato task "${label}".`, label);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// ── LABEL/TAG ────────────────────────────────────────────────────────
|
|
1826
|
+
if (effective === 'label' && params.label) {
|
|
1827
|
+
if (kind === 'email' && id) return await safe('gmail_label', { messageId: id, label: params.label }, `Aggiunta etichetta "${params.label}" a "${label}".`, params.label);
|
|
1828
|
+
if (kind === 'task' && id) return await safe('task_tag', { id, tag: params.label }, `Aggiunto tag "${params.label}" al task.`, params.label);
|
|
1829
|
+
}
|
|
1830
|
+
if (effective === 'label' && !params.label) {
|
|
1831
|
+
return { action: 'label_pending', success: false, message: `Quale etichetta vuoi applicare a "${label}"?` };
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// ── FORWARD (email) ──────────────────────────────────────────────────
|
|
1835
|
+
if (effective === 'forward' && kind === 'email' && id) {
|
|
1836
|
+
if (!params.to) return { action: 'forward_pending', success: false, message: `A chi vuoi inoltrare "${label}"?` };
|
|
1837
|
+
return await safe('gmail_forward', { messageId: id, to: params.to }, `Inoltrata "${label}" a ${params.to}.`, params.to);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
// ── SHARE (drive, calendar) ──────────────────────────────────────────
|
|
1841
|
+
if (effective === 'share') {
|
|
1842
|
+
if (kind === 'drive' && id) return await safe('drive_share', { fileId: id }, `Condiviso "${label}" (link copiato).`, label);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
// ── PRIORITY change (task) ───────────────────────────────────────────
|
|
1846
|
+
if (effective === 'priority' && params.priority) {
|
|
1847
|
+
if (kind === 'task' && id) return await safe('task_edit', { id, priority: params.priority }, `Priorità di "${label}" → ${params.priority}.`, params.priority);
|
|
1848
|
+
if (kind === 'gtask' && id) return await safe('gtask_edit', { id, priority: params.priority }, `Priorità task Google → ${params.priority}.`, params.priority);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
// ── SNOOZE (reminder) ───────────────────────────────────────────────
|
|
1852
|
+
if (effective === 'snooze' && kind === 'reminder' && id) {
|
|
1853
|
+
return await safe('reminder_snooze', { id, minutes: 30 }, `Posticipato di 30 minuti il promemoria "${label}".`, label);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1632
1856
|
// ── DELETE family ─────────────────────────────────────────────────────
|
|
1633
1857
|
if (effective === 'delete') {
|
|
1634
1858
|
if (kind === 'calendar' && item.eventId) {
|
|
@@ -2378,12 +2602,22 @@ class TelegramResponder {
|
|
|
2378
2602
|
if (extracted.date || extracted.title) {
|
|
2379
2603
|
let events = [];
|
|
2380
2604
|
try {
|
|
2605
|
+
const { listEvents } = await import('./google-calendar.mjs');
|
|
2381
2606
|
if (extracted.date) {
|
|
2382
|
-
const
|
|
2383
|
-
|
|
2607
|
+
const [yy, mm, dd] = extracted.date.split('-').map(n => parseInt(n, 10));
|
|
2608
|
+
const from = new Date(yy, mm - 1, dd);
|
|
2609
|
+
const to = new Date(from.getTime() + 86400000);
|
|
2610
|
+
const evs = await listEvents(config, 'primary', from, to);
|
|
2611
|
+
events = (evs || []).map(e => ({
|
|
2612
|
+
eventId: e.id, summary: e.summary || '', time: (e.start || '').slice(11, 16),
|
|
2613
|
+
}));
|
|
2384
2614
|
} else if (extracted.title) {
|
|
2385
|
-
const
|
|
2386
|
-
|
|
2615
|
+
const from = new Date();
|
|
2616
|
+
const to = new Date(from.getTime() + 60 * 86400000);
|
|
2617
|
+
const evs = await listEvents(config, 'primary', from, to);
|
|
2618
|
+
events = (evs || []).map(e => ({
|
|
2619
|
+
eventId: e.id, summary: e.summary || '', time: (e.start || '').slice(11, 16),
|
|
2620
|
+
}));
|
|
2387
2621
|
}
|
|
2388
2622
|
} catch (e) {
|
|
2389
2623
|
return { action: 'calendar_verify', success: false, message: `Errore durante la verifica: ${e.message}` };
|
|
@@ -2464,16 +2698,26 @@ class TelegramResponder {
|
|
|
2464
2698
|
if (isMove && !isDelete && !isVerify && !isCreate) {
|
|
2465
2699
|
const parsed = await this._nluExtractCalendarMove(userMessage, config);
|
|
2466
2700
|
if (parsed && (parsed.title || parsed.oldDate) && parsed.newStart) {
|
|
2467
|
-
// Find the source event
|
|
2701
|
+
// Find the source event — use listEvents directly (parser fails
|
|
2702
|
+
// because calendar_date/find don't expose eventIds in their text).
|
|
2468
2703
|
let candidates = [];
|
|
2469
2704
|
try {
|
|
2705
|
+
const { listEvents } = await import('./google-calendar.mjs');
|
|
2470
2706
|
if (parsed.oldDate) {
|
|
2471
|
-
const
|
|
2472
|
-
|
|
2707
|
+
const [yy, mm, dd] = parsed.oldDate.split('-').map(n => parseInt(n, 10));
|
|
2708
|
+
const from = new Date(yy, mm - 1, dd);
|
|
2709
|
+
const to = new Date(from.getTime() + 86400000);
|
|
2710
|
+
const evs = await listEvents(config, 'primary', from, to);
|
|
2711
|
+
candidates = (evs || []).map(e => ({ eventId: e.id, summary: e.summary || '' }));
|
|
2473
2712
|
}
|
|
2474
2713
|
if (candidates.length === 0 && parsed.title) {
|
|
2475
|
-
|
|
2476
|
-
|
|
2714
|
+
// Broad search across next 60 days
|
|
2715
|
+
const from = new Date();
|
|
2716
|
+
const to = new Date(from.getTime() + 60 * 86400000);
|
|
2717
|
+
const evs = await listEvents(config, 'primary', from, to);
|
|
2718
|
+
candidates = (evs || [])
|
|
2719
|
+
.filter(e => String(e.summary || '').toLowerCase().includes(parsed.title.toLowerCase()))
|
|
2720
|
+
.map(e => ({ eventId: e.id, summary: e.summary || '' }));
|
|
2477
2721
|
}
|
|
2478
2722
|
} catch (e) {
|
|
2479
2723
|
return { action: 'calendar_move', success: false, message: `Errore nella ricerca dell'evento: ${e.message}` };
|
|
@@ -2685,13 +2929,23 @@ class TelegramResponder {
|
|
|
2685
2929
|
|
|
2686
2930
|
let candidates = [];
|
|
2687
2931
|
try {
|
|
2932
|
+
const { listEvents } = await import('./google-calendar.mjs');
|
|
2688
2933
|
if (extracted.date) {
|
|
2689
|
-
const
|
|
2690
|
-
|
|
2934
|
+
const [yy, mm, dd] = extracted.date.split('-').map(n => parseInt(n, 10));
|
|
2935
|
+
const from = new Date(yy, mm - 1, dd);
|
|
2936
|
+
const to = new Date(from.getTime() + 86400000);
|
|
2937
|
+
const evs = await listEvents(config, 'primary', from, to);
|
|
2938
|
+
candidates = (evs || []).map(e => ({
|
|
2939
|
+
eventId: e.id, summary: e.summary || '', time: (e.start || '').slice(11, 16),
|
|
2940
|
+
}));
|
|
2691
2941
|
}
|
|
2692
2942
|
if (candidates.length === 0 && extracted.title) {
|
|
2693
|
-
const
|
|
2694
|
-
|
|
2943
|
+
const from = new Date();
|
|
2944
|
+
const to = new Date(from.getTime() + 60 * 86400000);
|
|
2945
|
+
const evs = await listEvents(config, 'primary', from, to);
|
|
2946
|
+
candidates = (evs || [])
|
|
2947
|
+
.filter(e => String(e.summary || '').toLowerCase().includes(extracted.title.toLowerCase()))
|
|
2948
|
+
.map(e => ({ eventId: e.id, summary: e.summary || '', time: (e.start || '').slice(11, 16) }));
|
|
2695
2949
|
}
|
|
2696
2950
|
} catch (err) {
|
|
2697
2951
|
this.log(`[Telegram] direct-fresh delete lookup failed: ${err.message}`);
|
|
@@ -3191,6 +3191,119 @@ export async function executeTool(action, params, config) {
|
|
|
3191
3191
|
return `File ${params.fileId} moved to trash.`;
|
|
3192
3192
|
}
|
|
3193
3193
|
|
|
3194
|
+
case 'drive_rename': {
|
|
3195
|
+
if (!params.fileId || !params.newName) return 'Error: fileId and newName required.';
|
|
3196
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3197
|
+
const res = await fetch(`https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=id,name,webViewLink`, {
|
|
3198
|
+
method: 'PATCH',
|
|
3199
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
3200
|
+
body: JSON.stringify({ name: params.newName }),
|
|
3201
|
+
});
|
|
3202
|
+
if (!res.ok) {
|
|
3203
|
+
const err = await res.text();
|
|
3204
|
+
throw new Error(`Drive rename ${res.status}: ${err.slice(0, 200)}`);
|
|
3205
|
+
}
|
|
3206
|
+
const data = await res.json();
|
|
3207
|
+
return `Renamed: ${data.name} — ${data.webViewLink || data.id}`;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
case 'drive_move': {
|
|
3211
|
+
if (!params.fileId || !params.folderId) return 'Error: fileId and folderId required.';
|
|
3212
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3213
|
+
// Need to first get current parents to remove them
|
|
3214
|
+
const cur = await fetch(`https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=parents`, { headers: { 'Authorization': `Bearer ${token}` } });
|
|
3215
|
+
const curData = await cur.json();
|
|
3216
|
+
const oldParents = (curData.parents || []).join(',');
|
|
3217
|
+
const res = await fetch(`https://www.googleapis.com/drive/v3/files/${params.fileId}?addParents=${params.folderId}&removeParents=${oldParents}&fields=id,name,parents`, {
|
|
3218
|
+
method: 'PATCH',
|
|
3219
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
3220
|
+
});
|
|
3221
|
+
if (!res.ok) throw new Error(`Drive move ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
3222
|
+
return `Moved: ${(await res.json()).name}`;
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
case 'drive_share': {
|
|
3226
|
+
if (!params.fileId) return 'Error: fileId required.';
|
|
3227
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3228
|
+
const role = params.role || 'reader';
|
|
3229
|
+
const res = await fetch(`https://www.googleapis.com/drive/v3/files/${params.fileId}/permissions`, {
|
|
3230
|
+
method: 'POST',
|
|
3231
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
3232
|
+
body: JSON.stringify({ type: 'anyone', role }),
|
|
3233
|
+
});
|
|
3234
|
+
if (!res.ok) throw new Error(`Drive share ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
3235
|
+
const meta = await fetch(`https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=webViewLink,name`, { headers: { 'Authorization': `Bearer ${token}` } });
|
|
3236
|
+
const m = await meta.json();
|
|
3237
|
+
return `Shared "${m.name}" (${role}): ${m.webViewLink}`;
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
case 'gmail_label': {
|
|
3241
|
+
if (!params.messageId || !params.label) return 'Error: messageId and label required.';
|
|
3242
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3243
|
+
// Find or create the label by name
|
|
3244
|
+
const labelsRes = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/labels', { headers: { 'Authorization': `Bearer ${token}` } });
|
|
3245
|
+
const labelsData = await labelsRes.json();
|
|
3246
|
+
let labelId = (labelsData.labels || []).find(l => l.name.toLowerCase() === params.label.toLowerCase())?.id;
|
|
3247
|
+
if (!labelId) {
|
|
3248
|
+
const createRes = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/labels', {
|
|
3249
|
+
method: 'POST',
|
|
3250
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
3251
|
+
body: JSON.stringify({ name: params.label, labelListVisibility: 'labelShow', messageListVisibility: 'show' }),
|
|
3252
|
+
});
|
|
3253
|
+
if (!createRes.ok) throw new Error(`Label create ${createRes.status}`);
|
|
3254
|
+
labelId = (await createRes.json()).id;
|
|
3255
|
+
}
|
|
3256
|
+
const res = await fetch(`https://gmail.googleapis.com/gmail/v1/users/me/messages/${params.messageId}/modify`, {
|
|
3257
|
+
method: 'POST',
|
|
3258
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
3259
|
+
body: JSON.stringify({ addLabelIds: [labelId] }),
|
|
3260
|
+
});
|
|
3261
|
+
if (!res.ok) throw new Error(`Gmail label ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
3262
|
+
return `Applied label "${params.label}" to message.`;
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
case 'gmail_forward': {
|
|
3266
|
+
if (!params.messageId || !params.to) return 'Error: messageId and to required.';
|
|
3267
|
+
const { getMessage, sendEmail } = await import('./google-gmail.mjs');
|
|
3268
|
+
const orig = await getMessage(config, params.messageId);
|
|
3269
|
+
const body = `\n\n---------- Messaggio inoltrato ----------\nDa: ${orig.from}\nData: ${orig.date}\nOggetto: ${orig.subject}\n\n${orig.body}`;
|
|
3270
|
+
await sendEmail(config, {
|
|
3271
|
+
to: params.to,
|
|
3272
|
+
subject: `Fwd: ${orig.subject}`,
|
|
3273
|
+
body: (params.note || '') + body,
|
|
3274
|
+
});
|
|
3275
|
+
return `Forwarded "${orig.subject}" to ${params.to}.`;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
case 'gtask_update': {
|
|
3279
|
+
if (!params.id) return 'Error: id required.';
|
|
3280
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3281
|
+
const listId = params.listId || '@default';
|
|
3282
|
+
const patch = {};
|
|
3283
|
+
if (params.title) patch.title = params.title;
|
|
3284
|
+
if (params.due) patch.due = new Date(params.due).toISOString();
|
|
3285
|
+
if (params.notes) patch.notes = params.notes;
|
|
3286
|
+
const res = await fetch(`https://tasks.googleapis.com/tasks/v1/lists/${listId}/tasks/${params.id}`, {
|
|
3287
|
+
method: 'PATCH',
|
|
3288
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
3289
|
+
body: JSON.stringify(patch),
|
|
3290
|
+
});
|
|
3291
|
+
if (!res.ok) throw new Error(`GTask update ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
3292
|
+
return `Updated Google Task.`;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
case 'gtask_delete': {
|
|
3296
|
+
if (!params.id) return 'Error: id required.';
|
|
3297
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3298
|
+
const listId = params.listId || '@default';
|
|
3299
|
+
const res = await fetch(`https://tasks.googleapis.com/tasks/v1/lists/${listId}/tasks/${params.id}`, {
|
|
3300
|
+
method: 'DELETE',
|
|
3301
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
3302
|
+
});
|
|
3303
|
+
if (!res.ok && res.status !== 204) throw new Error(`GTask delete ${res.status}`);
|
|
3304
|
+
return `Deleted Google Task.`;
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3194
3307
|
case 'drive_info': {
|
|
3195
3308
|
if (!params.fileId) return 'Error: fileId required.';
|
|
3196
3309
|
const drv = await import('./google-drive.mjs');
|