@luanxdd/meowfly
v0.6.0
Published
Cliente em **TypeScript/Node.js** para a API **Meowfly 0.6**.
Readme
🐱 @luanxdd/meowfly
Cliente em TypeScript/Node.js para a API Meowfly 0.6.
Use o SDK para pesquisar faixas, resolver links do Spotify, criar e acompanhar downloads e obter o artefato final — por caminho local, quando sua aplicação roda no mesmo servidor, ou por URL assinada para clientes remotos.
Node.js 20+ · ESM ·
fetchnativo · sem dependências de runtime
Instalação
npm install @luanxdd/meowflyInício rápido
import { Meowfly } from "@luanxdd/meowfly";
const meowfly = new Meowfly({
baseUrl: process.env.MEOWFLY_BASE_URL!,
token: process.env.MEOWFLY_TOKEN!,
});
const result = await meowfly.search.tracks(
"Savage Funk Anitta",
5,
);
const [track] = result.items;
if (!track) {
throw new Error("Nenhuma faixa encontrada");
}
const created = await meowfly.downloads.create(
track.id,
{ quality: "best" },
);
const job =
created.status === "completed"
? created
: await meowfly.downloads.wait(created.id);
console.log(job.artifact_id);Lyrics persistidas
Depois que uma faixa teve lyrics persistidas por um download de áudio, elas podem ser lidas sem nova consulta externa:
const lyrics = await meowfly.lyrics.get(track.id);
console.log(lyrics.source); // "spotify"
console.log(lyrics.synced);
console.log(lyrics.plain);
console.log(lyrics.lines); // [{ start_ms, text }, ...]Quando disponíveis, as mesmas lyrics também chegam em job.lyrics no Download concluído. O campo público source representa a identidade da Track e é sempre spotify.
Configuração
import { Meowfly } from "@luanxdd/meowfly";
const meowfly = new Meowfly({
baseUrl: "https://meowfly.example.com",
token: process.env.MEOWFLY_TOKEN!,
});O construtor recebe:
| Opção | Tipo | Obrigatória | Descrição |
| --- | --- | :---: | --- |
| baseUrl | string | Sim | URL base da instância Meowfly. |
| token | string | Sim | Token Bearer usado nas requisições. |
| fetch | typeof globalThis.fetch | Não | Implementação customizada de fetch. |
Exemplo com fetch customizado:
const meowfly = new Meowfly({
baseUrl: "https://meowfly.example.com",
token: process.env.MEOWFLY_TOKEN!,
fetch: customFetch,
});API do SDK
search.tracks()
Pesquisa faixas usando o endpoint POST /v1/search.
const result = await meowfly.search.tracks(
"Savage Funk Anitta",
5,
"auto",
);Assinatura:
meowfly.search.tracks(
query: string,
limit?: number,
strategy?: "auto" | "fresh" | "local",
): Promise<SearchResult>O limit padrão é 10. A API aceita valores de 1 a 10.
Estratégias de busca
| Estratégia | Comportamento |
| --- | --- |
| auto | Usa cache em memória, depois busca local e consulta o Spotify quando necessário. |
| fresh | Consulta o Spotify diretamente. |
| local | Pesquisa somente no PostgreSQL local. |
A resposta também informa a origem e métricas da busca:
const result = await meowfly.search.tracks("Anitta");
console.log(result.source); // "memory" | "local" | "spotify"
console.log(result.cache_hit); // boolean
console.log(result.local_hits); // number
console.log(result.external_hits); // number
console.log(result.took_ms); // numbertracks.resolve()
Resolve uma referência do Spotify para um Track normalizado pelo Meowfly.
Aceita:
- URL do Spotify;
- URI
spotify:track:...; - ID puro da faixa no Spotify.
const track = await meowfly.tracks.resolve(
"https://open.spotify.com/track/7fvR6AqV3IEj1ZKzyV0k0f",
);
console.log(track.id); // trk_...
console.log(track.title);
console.log(track.artists);Assinatura:
meowfly.tracks.resolve(
input: string,
): Promise<Track>downloads.create()
Cria um download para um track_id retornado por search.tracks() ou tracks.resolve().
const job = await meowfly.downloads.create(
track.id,
{
format: "m4a",
quality: "best",
},
);Assinatura:
meowfly.downloads.create(
trackId: string,
options?: {
mediaType?: "audio" | "video";
format?: "m4a" | "mp4";
quality?: AudioQuality | VideoQuality;
},
): Promise<DownloadJob>Qualidade
| Valor | Descrição |
| --- | --- |
| best | Perfil preferencial disponível. É o padrão. |
| high | Perfil AAC/M4A de alta qualidade. |
| low | Perfil AAC/M4A de menor largura de banda. |
| 320k | Alias legado, normalizado pela API para high. |
| 128k | Alias legado, normalizado pela API para low. |
Para código novo, prefira os nomes canônicos best, high e low.
A API pode retornar imediatamente um job completed quando existe um artefato válido em cache. Caso contrário, o download normalmente entra na fila e deve ser acompanhado.
clips.create()
Cria um download de clipe oficial/compatível para o mesmo Track que veio da busca/resolve do Spotify. A API pesquisa vídeos no Spotify e só aceita um candidato quando título, artista e duração atingem o limiar de confiança.
const clip = await meowfly.clips.create(track.id, {
quality: "best",
});
const completed =
clip.status === "completed"
? clip
: await meowfly.clips.wait(clip.id);
console.log(completed.media_type); // "video"
console.log(completed.format); // "mp4"
console.log(completed.artifact_id);Qualidades disponíveis:
| Valor | Descrição |
| --- | --- |
| best | Melhor resolução disponível, preferindo 1080p. |
| 1080p | Solicita Full HD; o backend Spotify pode escolher a melhor alternativa disponível. |
| 720p | Solicita HD. |
| 480p | Solicita SD 480p. |
| 360p | Solicita SD 360p. |
Assinatura:
meowfly.clips.create(
trackId: string,
options?: { quality?: "best" | "1080p" | "720p" | "480p" | "360p" },
): Promise<DownloadJob>clips.get() e clips.wait() usam a mesma state machine de downloads.
downloads.get()
Obtém o estado atual de um download.
const job = await meowfly.downloads.get("dwl_...");
console.log(job.status);
console.log(job.progress);Assinatura:
meowfly.downloads.get(
id: string,
): Promise<DownloadJob>Os estados possíveis são:
"queued"
| "resolving"
| "downloading"
| "finalizing"
| "completed"
| "failed"
| "expired"downloads.wait()
Aguarda um download terminar usando polling sobre downloads.get().
const job = await meowfly.downloads.wait(
created.id,
{
intervalMs: 500,
timeoutMs: 120_000,
},
);Assinatura:
meowfly.downloads.wait(
id: string,
options?: {
intervalMs?: number;
timeoutMs?: number;
},
): Promise<DownloadJob>Valores padrão:
| Opção | Padrão |
| --- | ---: |
| intervalMs | 250 ms |
| timeoutMs | 60_000 ms |
O método retorna quando o status chega a completed.
Se o job terminar como failed ou expired, o SDK lança MeowflyError. Um timeout também gera MeowflyError, com código timeout.
Uma forma prática de evitar polling desnecessário quando a API já respondeu com cache hit é:
const job =
created.status === "completed"
? created
: await meowfly.downloads.wait(created.id);artifacts.link()
Cria uma URL assinada e temporária para um artefato concluído.
const link = await meowfly.artifacts.link(
job.artifact_id!,
);
console.log(link.url);
console.log(link.expires_at);Também é possível solicitar um TTL específico, em segundos:
const link = await meowfly.artifacts.link(
job.artifact_id!,
300,
);Assinatura:
meowfly.artifacts.link(
artifactId: string,
ttlSeconds?: number,
): Promise<ArtifactLink>O TTL informado pode ser limitado pelo máximo configurado no servidor.
A resposta contém:
interface ArtifactLink {
artifact_id: string;
url: string;
filename: string;
content_type: string;
size_bytes: number;
etag: string;
expires_at: string;
}A URL gerada já contém a assinatura necessária para o acesso, portanto não exige o Bearer token. A rota de entrega também suporta HTTP Range.
Aplicações no mesmo VPS
Se seu bot ou aplicação roda no mesmo host do Meowfly, não é necessário baixar o arquivo novamente via HTTP.
Importe o helper específico para Node.js:
import { localArtifactPath } from "@luanxdd/meowfly/node";
const path = localArtifactPath(job);
console.log(path);
// /srv/meowfly/artifacts/art_....m4a ou art_....mp4O diretório padrão é:
/srv/meowfly/artifactsVocê pode sobrescrevê-lo:
const path = localArtifactPath(
job,
"/data/artifacts",
);O helper só aceita jobs concluídos que possuam artifact_id. O caminho final segue o formato:
<root>/<artifact_id>.<format>Fluxo completo no mesmo servidor
import { Meowfly } from "@luanxdd/meowfly";
import { localArtifactPath } from "@luanxdd/meowfly/node";
const meowfly = new Meowfly({
baseUrl: process.env.MEOWFLY_BASE_URL!,
token: process.env.MEOWFLY_TOKEN!,
});
const track = await meowfly.tracks.resolve(
"spotify:track:7fvR6AqV3IEj1ZKzyV0k0f",
);
const created = await meowfly.downloads.create(
track.id,
{ quality: "best" },
);
const job =
created.status === "completed"
? created
: await meowfly.downloads.wait(created.id);
const path = localArtifactPath(job);Aplicações remotas
Para clientes que não compartilham o filesystem com o Meowfly, use uma URL assinada:
const created = await meowfly.downloads.create(
track.id,
{ quality: "best" },
);
const job =
created.status === "completed"
? created
: await meowfly.downloads.wait(created.id);
if (!job.artifact_id) {
throw new Error("Download concluído sem artifact_id");
}
const artifact = await meowfly.artifacts.link(
job.artifact_id,
);
console.log(artifact.url);Tratamento de erros
Erros HTTP retornados pela API são convertidos em MeowflyError.
import {
Meowfly,
MeowflyError,
} from "@luanxdd/meowfly";
try {
await meowfly.downloads.get("dwl_inexistente");
} catch (error) {
if (error instanceof MeowflyError) {
console.error({
message: error.message,
status: error.status,
code: error.code,
requestId: error.requestId,
});
}
}Propriedades disponíveis:
| Propriedade | Tipo | Descrição |
| --- | --- | --- |
| message | string | Mensagem do erro. |
| status | number | Status HTTP associado. |
| code | string \| undefined | Código de erro retornado pela API. |
| requestId | string \| undefined | ID de correlação da requisição, quando disponível. |
Tipos exportados
O pacote exporta os tipos principais diretamente pela entrada principal:
import type {
ArtifactLink,
DownloadJob,
DownloadStatus,
MediaSnapshot,
SearchResult,
SearchStrategy,
Track,
} from "@luanxdd/meowfly";Também estão disponíveis as interfaces de configuração do cliente:
import type {
DownloadOptions,
DownloadWaitOptions,
MeowflyOptions,
} from "@luanxdd/meowfly";Escopos do token
A API Meowfly usa tokens com escopos.
| Operação do SDK | Escopo necessário |
| --- | --- |
| search.tracks() | search |
| tracks.resolve() | search |
| downloads.create() | downloads:write |
| downloads.get() | downloads:read |
| downloads.wait() | downloads:read |
| artifacts.link() | artifacts:read |
Um fluxo completo de pesquisa, criação, espera e entrega remota normalmente precisa de:
search
downloads:write
downloads:read
artifacts:readReferência rápida
| Método | Retorno |
| --- | --- |
| search.tracks(q, limit?, strategy?) | Promise<SearchResult> |
| tracks.resolve(input) | Promise<Track> |
| downloads.create(trackId, options?) | Promise<DownloadJob> |
| downloads.get(id) | Promise<DownloadJob> |
| downloads.wait(id, options?) | Promise<DownloadJob> |
| artifacts.link(id, ttlSeconds?) | Promise<ArtifactLink> |
| localArtifactPath(job, root?) | string |
Limites atuais do SDK
O SDK TypeScript cobre o fluxo principal de busca, resolução, download e entrega de artefatos.
A API HTTP do Meowfly também possui endpoints que ainda não têm wrappers dedicados neste pacote, incluindo:
- stream de eventos de download via SSE;
- health checks;
- bootstrap e gerenciamento de tokens;
- consulta de uso diário.
Quando necessário, esses endpoints podem ser consumidos diretamente pela API HTTP.
