spotify-tools.ts•17.2 kB
import { SpotifyAuth } from '../auth/spotify-auth.js';
import { SpotifyTrack, SpotifyPlaylist, SpotifyDevice } from '../types/index.js';
import open from 'open';
export class SpotifyTools {
constructor(private spotifyAuth: SpotifyAuth) {}
/**
* Inicia o processo de autenticação
*/
async authenticate() {
if (this.spotifyAuth.isAuthenticated()) {
return {
content: [
{
type: 'text',
text: '✅ Já autenticado com o Spotify!',
},
],
};
}
const authUrl = this.spotifyAuth.getAuthorizationUrl();
// Abrir navegador automaticamente
await open(authUrl);
return {
content: [
{
type: 'text',
text: `🔐 Abra este link no navegador para autenticar com o Spotify:\n\n${authUrl}\n\nApós autorizar, você receberá um código. Use o comando spotify_set_tokens com o código recebido.`,
},
],
};
}
/**
* Define tokens após autenticação
*/
async setTokens(code: string) {
try {
const tokens = await this.spotifyAuth.exchangeCodeForTokens(code);
return {
content: [
{
type: 'text',
text: '✅ Autenticação com Spotify concluída com sucesso!',
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro na autenticação: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Busca por conteúdo no Spotify
*/
async search(query: string, type: 'track' | 'artist' | 'album' | 'playlist', limit: number = 20) {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const response = await spotifyApi.search(query, [type], { limit });
const results = response.body;
let content = `🔍 Resultados para "${query}" (${type}):\n\n`;
switch (type) {
case 'track':
const tracks = results.tracks?.items || [];
tracks.forEach((track: any, index: number) => {
const artists = track.artists.map((a: any) => a.name).join(', ');
content += `${index + 1}. **${track.name}** - ${artists}\n`;
content += ` ID: ${track.id}\n`;
content += ` Duração: ${Math.floor(track.duration_ms / 60000)}:${String(Math.floor((track.duration_ms % 60000) / 1000)).padStart(2, '0')}\n`;
content += ` Link: ${track.external_urls.spotify}\n\n`;
});
break;
case 'artist':
const artists = results.artists?.items || [];
artists.forEach((artist: any, index: number) => {
content += `${index + 1}. **${artist.name}**\n`;
content += ` ID: ${artist.id}\n`;
content += ` Seguidores: ${artist.followers.total.toLocaleString()}\n`;
content += ` Gêneros: ${artist.genres.join(', ')}\n`;
content += ` Link: ${artist.external_urls.spotify}\n\n`;
});
break;
case 'album':
const albums = results.albums?.items || [];
albums.forEach((album: any, index: number) => {
const artists = album.artists.map((a: any) => a.name).join(', ');
content += `${index + 1}. **${album.name}** - ${artists}\n`;
content += ` ID: ${album.id}\n`;
content += ` Lançamento: ${album.release_date}\n`;
content += ` Total de músicas: ${album.total_tracks}\n`;
content += ` Link: ${album.external_urls.spotify}\n\n`;
});
break;
case 'playlist':
const playlists = results.playlists?.items || [];
playlists.forEach((playlist: any, index: number) => {
content += `${index + 1}. **${playlist.name}**\n`;
content += ` ID: ${playlist.id}\n`;
content += ` Descrição: ${playlist.description}\n`;
content += ` Total de músicas: ${playlist.tracks.total}\n`;
content += ` Link: ${playlist.external_urls.spotify}\n\n`;
});
break;
}
return {
content: [
{
type: 'text',
text: content,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro na busca: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Toca uma música específica
*/
async play(trackId: string, deviceId?: string) {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const options: any = { uris: [`spotify:track:${trackId}`] };
if (deviceId) {
options.device_id = deviceId;
}
await spotifyApi.play(options);
return {
content: [
{
type: 'text',
text: `▶️ Tocando música com ID: ${trackId}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao tocar música: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Pausa a reprodução
*/
async pause(deviceId?: string) {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const options = deviceId ? { device_id: deviceId } : {};
await spotifyApi.pause(options);
return {
content: [
{
type: 'text',
text: '⏸️ Reprodução pausada',
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao pausar: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Retoma a reprodução
*/
async resume(deviceId?: string) {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const options = deviceId ? { device_id: deviceId } : {};
await spotifyApi.play(options);
return {
content: [
{
type: 'text',
text: '▶️ Reprodução retomada',
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao retomar: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Pula para a próxima música
*/
async next(deviceId?: string) {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const options = deviceId ? { device_id: deviceId } : {};
await spotifyApi.skipToNext(options);
return {
content: [
{
type: 'text',
text: '⏭️ Pulou para a próxima música',
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao pular música: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Volta para a música anterior
*/
async previous(deviceId?: string) {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const options = deviceId ? { device_id: deviceId } : {};
await spotifyApi.skipToPrevious(options);
return {
content: [
{
type: 'text',
text: '⏮️ Voltou para a música anterior',
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao voltar música: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Obtém informações sobre a música atual
*/
async getCurrentPlaying() {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const response = await spotifyApi.getMyCurrentPlayingTrack();
const track = response.body.item;
if (!track) {
return {
content: [
{
type: 'text',
text: '❌ Nenhuma música está tocando no momento',
},
],
};
}
const artists = track.artists.map((a: any) => a.name).join(', ');
const duration = Math.floor(track.duration_ms / 60000);
const durationSeconds = Math.floor((track.duration_ms % 60000) / 1000);
const progress = Math.floor((response.body.progress_ms || 0) / 60000);
const progressSeconds = Math.floor(((response.body.progress_ms || 0) % 60000) / 1000);
let content = `🎵 **Música atual:**\n\n`;
content += `**${track.name}** - ${artists}\n`;
content += `Álbum: ${track.album.name}\n`;
content += `Duração: ${duration}:${String(durationSeconds).padStart(2, '0')}\n`;
content += `Progresso: ${progress}:${String(progressSeconds).padStart(2, '0')}\n`;
content += `Link: ${track.external_urls.spotify}\n`;
return {
content: [
{
type: 'text',
text: content,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao obter música atual: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Lista dispositivos disponíveis
*/
async getDevices() {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const response = await spotifyApi.getMyDevices();
const devices = response.body.devices;
if (devices.length === 0) {
return {
content: [
{
type: 'text',
text: '❌ Nenhum dispositivo encontrado. Certifique-se de que o Spotify está aberto em algum dispositivo.',
},
],
};
}
let content = `📱 **Dispositivos disponíveis:**\n\n`;
devices.forEach((device: any, index: number) => {
const status = device.is_active ? '🟢 Ativo' : '⚪ Inativo';
content += `${index + 1}. **${device.name}** (${device.type})\n`;
content += ` ID: ${device.id}\n`;
content += ` Status: ${status}\n`;
content += ` Volume: ${device.volume_percent}%\n\n`;
});
return {
content: [
{
type: 'text',
text: content,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao obter dispositivos: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Lista playlists do usuário
*/
async getPlaylists(limit: number = 20) {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const response = await spotifyApi.getUserPlaylists({ limit });
const playlists = response.body.items;
if (playlists.length === 0) {
return {
content: [
{
type: 'text',
text: '❌ Nenhuma playlist encontrada',
},
],
};
}
let content = `📋 **Suas playlists:**\n\n`;
playlists.forEach((playlist: any, index: number) => {
content += `${index + 1}. **${playlist.name}**\n`;
content += ` ID: ${playlist.id}\n`;
content += ` Descrição: ${playlist.description || 'Sem descrição'}\n`;
content += ` Total de músicas: ${playlist.tracks.total}\n`;
content += ` Pública: ${playlist.public ? 'Sim' : 'Não'}\n`;
content += ` Link: ${playlist.external_urls.spotify}\n\n`;
});
return {
content: [
{
type: 'text',
text: content,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao obter playlists: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Toca uma playlist específica
*/
async playPlaylist(playlistId: string, deviceId?: string) {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const options: any = { context_uri: `spotify:playlist:${playlistId}` };
if (deviceId) {
options.device_id = deviceId;
}
await spotifyApi.play(options);
return {
content: [
{
type: 'text',
text: `▶️ Tocando playlist com ID: ${playlistId}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao tocar playlist: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Cria uma nova playlist usando fetch direto
*/
async createPlaylist(name: string, description?: string, isPublic: boolean = false) {
try {
await this.spotifyAuth.ensureValidToken();
const spotifyApi = this.spotifyAuth.getSpotifyApi();
const accessToken = await this.spotifyAuth.ensureValidToken();
// Primeiro, obter o ID do usuário atual
const userResponse = await fetch('https://api.spotify.com/v1/me', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
if (!userResponse.ok) {
throw new Error(`Erro ao obter dados do usuário: ${userResponse.status}`);
}
const userData = await userResponse.json();
const userId = userData.id;
// Criar a playlist
const playlistResponse = await fetch(`https://api.spotify.com/v1/users/${userId}/playlists`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: name,
description: description || '',
public: isPublic
})
});
if (!playlistResponse.ok) {
const errorData = await playlistResponse.json();
throw new Error(`Erro ao criar playlist: ${playlistResponse.status} - ${errorData.error?.message || 'Erro desconhecido'}`);
}
const playlist = await playlistResponse.json();
return {
content: [
{
type: 'text',
text: `✅ Playlist "${name}" criada com sucesso!\n\n**Detalhes:**\n- Nome: ${playlist.name}\n- ID: ${playlist.id}\n- Descrição: ${playlist.description || 'Sem descrição'}\n- Pública: ${playlist.public ? 'Sim' : 'Não'}\n- Link: ${playlist.external_urls.spotify}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao criar playlist: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
/**
* Adiciona músicas a uma playlist existente
*/
async addTracksToPlaylist(playlistId: string, trackIds: string[]) {
try {
await this.spotifyAuth.ensureValidToken();
const accessToken = await this.spotifyAuth.ensureValidToken();
// Adicionar músicas à playlist
const response = await fetch(`https://api.spotify.com/v1/playlists/${playlistId}/tracks`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
uris: trackIds.map(id => `spotify:track:${id}`)
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Erro ao adicionar músicas: ${response.status} - ${errorData.error?.message || 'Erro desconhecido'}`);
}
const result = await response.json();
return {
content: [
{
type: 'text',
text: `✅ ${trackIds.length} música(s) adicionada(s) à playlist com sucesso!\n\n**Detalhes:**\n- Playlist ID: ${playlistId}\n- Músicas adicionadas: ${trackIds.length}\n- Snapshot ID: ${result.snapshot_id}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ Erro ao adicionar músicas à playlist: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
}