import { randomUUID } from 'node:crypto'; import WebSocket from 'ws'; import { searchSpotify, playlistTool } from './spotify.mjs'; const spotifyUri = /^spotify:(track|artist|album|playlist|episode):[A-Za-z0-9]{22}$/; export function deviceTools(client, signal) { const pending = new Map(); signal.addEventListener('abort', () => { for (const { reject, timeout } of pending.values()) { clearTimeout(timeout); reject(new Error('device_disconnected')); } pending.clear(); }, { once: true }); function request(name, args) { if (signal.aborted || client.readyState !== WebSocket.OPEN) throw new Error('device_disconnected'); const id = randomUUID(); return new Promise((resolve, reject) => { const timeout = setTimeout(() => { pending.delete(id); reject(new Error('device_tool_timeout')); }, 17000); pending.set(id, { resolve, reject, timeout }); client.send(JSON.stringify({ type: 'device_tool', id, name, arguments: args })); }); } return { accept(message) { if (message?.type !== 'device_tool_result') return false; const call = pending.get(message.id); if (!call) return true; clearTimeout(call.timeout); pending.delete(message.id); call.resolve(message.result ?? { error: 'empty_device_result' }); return true; }, async execute(name, args) { if (name === 'spotify_playlist') return playlistTool(args, signal); const enqueue = name === 'spotify_control' && args.action === 'enqueue'; if (name !== 'spotify_play' && !enqueue) return request(name, args); let selected; if (args.uri) { if (!spotifyUri.test(args.uri)) return { error: 'invalid_spotify_uri' }; selected = { uri: args.uri }; } else { try { selected = (await searchSpotify(args.query, signal, enqueue ? 'track' : args.type))[0]; } catch { return { error: 'spotify_search_unavailable', hint: 'Подключи Spotify в пульте Telegram и повтори запрос.' }; } } if (!selected) return { error: 'spotify_not_found' }; const result = await request(name, enqueue ? { action: 'enqueue', uri: selected.uri } : { uri: selected.uri }); return { ...result, selection: selected }; }, }; }