MyGo 云音乐
用 Go 与 MyGo 写成的第三方网易云音乐桌面客户端。原生界面,没有 WebView;边下边播,无损与 Hi-Res,逐字歌词与「律动」,刘海播放器。
Translation pending
Importe artigos de qualquer site e leia no seu ritmo, com controle de velocidade e acompanhamento de progresso.
Translation pending
MediaTypeScript · JavaScriptGitHub @RenanKohlerOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Aplicacao de leitura dinamica: importa artigos de qualquer URL (ou aceita texto colado) e apresenta o conteudo em ritmo controlado, com acompanhamento de velocidade e progresso. Next.js 16 (App Router) + React 19 + PostgreSQL via Drizzle ORM. Requisitos: Node.js 20+ e um PostgreSQL (local ou gerenciado). A aplicacao sobe em. O seed cria a conta [email protected] / demo1234. Para exibir essa dica na tela de login durante testes, defina NEXT_PUBLIC_DEMO_HINT=true (o valor e lido no momento do build).Read the full README ↗
Aplicacao de leitura dinamica: importa artigos de qualquer URL (ou aceita texto colado) e apresenta o conteudo em ritmo controlado, com acompanhamento de velocidade e progresso.
Next.js 16 (App Router) + React 19 + PostgreSQL via Drizzle ORM.
Requisitos: Node.js 22 (o mesmo de engines no package.json) e um PostgreSQL (local ou gerenciado).
npm install
cp .env.example .env.local
npm run secret # gera o JWT_SECRET; cole no .env.local
npm run db:migrate # cria as tabelas
npm run db:seed # opcional: dados de demonstracao
npm run dev
A aplicacao sobe em http://localhost:3000.
O seed cria a conta [email protected] / demo1234. Para exibir essa dica na
tela de login durante testes, defina NEXT_PUBLIC_DEMO_HINT=true (o valor e
lido no momento do build).
Postgres gerenciado, plano gratuito sem cartao. A conexao vem de
DATABASE_URL: em desenvolvimento pelo .env.local, em producao pela variavel
de ambiente do host.
Use a Pooled connection do Neon — o host termina em -pooler. Em ambiente
serverless cada requisicao pode abrir uma conexao nova, e o endpoint direto
esgota o limite rapido.
O TLS e resolvido automaticamente: a verificacao de certificado liga quando o
host nao e local, respeitando o sslmode da connection string.
As migrations sao aplicadas antes do build (vercel-build), entao o schema
acompanha o deploy sem passo manual.
Um banco gratuito hiberna apos alguns minutos sem uso. A primeira requisicao depois disso leva alguns segundos, o que e esperado.
Em producao: https://rk-leitura.vercel.app.
O projeto rk-leitura esta ligado a este repositorio, com DATABASE_URL e
JWT_SECRET definidos como variaveis sensiveis e as funcoes na regiao gru1
(Sao Paulo) - perto de quem usa e do banco. Cada push na main gera um deploy.
O script vercel-build aplica as migrations antes de compilar, so no deploy de
Production (VERCEL_ENV=production), entao o schema acompanha o deploy sem
passo manual. Se as migrations falharem, o build para e o deploy anterior
continua no ar. Os deploys de Preview usam o mesmo banco de producao e por isso
nao migram (scripts/migrate-on-deploy.mjs): uma migration de PR ainda nao
aprovado nao altera o banco do app no ar.
Regra das migrations: sempre aditivas. Expandir antes, remover depois - coluna ou tabela nova entra numa versao, o codigo passa a usa-la, e o que deixou de ser usado sai numa versao seguinte, depois que nenhum deploy no ar depende dele. Como o Preview roda contra o schema de producao, um PR que depende de coluna nova so funciona por inteiro no Preview depois do merge.
Para criar o banco: projeto novo em https://neon.tech (gratuito, sem cartao),
regiao sa-east-1, e copie a Pooled connection.
Dois detalhes que valem em qualquer host:
.env.local presente vai junto e sobrescreve a
conexao de producao com a do seu Postgres local. Se GET /api/health
responder pointsToLocalhost: true, foi isso. O deploy ligado ao git nao tem
esse problema, porque parte do que esta versionado.Trocar o JWT_SECRET invalida todas as sessoes ativas. Os codigos de
recuperacao usam o RECOVERY_PEPPER como tempero; sem ele, usam o
JWT_SECRET, e troca-lo tambem invalida os codigos ja emitidos. O
/api/health informa se o RECOVERY_PEPPER esta definido. Para adota-lo sem
invalidar codigos antigos, defina-o com o valor atual do JWT_SECRET.
| Variavel | Obrigatoria | Descricao |
|---|---|---|
DATABASE_URL | sim | Connection string do Postgres (no Neon, a pooled connection). |
JWT_SECRET | sim | Chave de assinatura das sessoes, minimo 32 caracteres. |
RECOVERY_PEPPER | recomendada | Tempero dos codigos de recuperacao. Sem ela vale o JWT_SECRET, e troca-lo invalida os codigos. |
DATABASE_POOL_MAX | nao | Tamanho maximo do pool (padrao 5). |
NEXT_PUBLIC_DEMO_HINT | nao | true mostra as credenciais de demo no login. |
ANTHROPIC_API_KEY | nao | Funcoes de IA: questionario, dicionario, explicacao, perguntas ao texto, resumos, limpeza da importacao, etiquetas, sinopse e sintese. Sem ela, cada tela diz que a funcionalidade nao esta configurada e o resto do app segue igual. |
AI_USAGE_SECRET | nao | Protege GET /api/uso-ia, o uso e o custo estimado da IA por dia e por funcionalidade. Sem ela a rota responde 401. |
NEXT_PUBLIC_VAPID_KEY | nao | Chave publica do lembrete diario. |
VAPID_PRIVATE_KEY | nao | Chave privada do lembrete diario. |
VAPID_SUBJECT | nao | mailto: de contato exigido pelo protocolo de push. |
CRON_SECRET | nao | Separa o agendador de quem descobrir as rotas do cron (lembrete e acompanhamento de series e feeds). |
As tres variaveis do lembrete sao opcionais e andam juntas: sem elas, o cartao some dos Ajustes e a rota do agendador responde 401. O resto da aplicacao nao muda.
O par VAPID e gerado localmente, sem contratar nada:
node -e "const k=require('web-push').generateVAPIDKeys();console.log('NEXT_PUBLIC_VAPID_KEY='+k.publicKey);console.log('VAPID_PRIVATE_KEY='+k.privateKey)"
node -e "console.log('CRON_SECRET='+require('crypto').randomBytes(32).toString('base64url'))"
Na Vercel, VAPID_PRIVATE_KEY e CRON_SECRET entram como variaveis
sensitive; NEXT_PUBLIC_VAPID_KEY e publica por definicao, porque vai para o
navegador.
O plano Hobby da Vercel aceita um disparo por dia por cron - um
vercel.json com expressao mais frequente que isso faz a API recusar o deploy
inteiro com cron_jobs_limits_reached, e nao so ignorar o agendamento. Por
isso vercel.json marca 0 2 * * * (02:00 UTC, 23:00 em Brasilia).
Um disparo por dia entrega o lembrete sempre no mesmo horario, nao no que a
pessoa escolheu. Quem cobre a diferenca e
.github/workflows/lembretes.yml: bate na mesma rota de hora em hora, sem
custo. Para ativar, cadastre CRON_SECRET em *Settings > Secrets and variables
Actions* do repositorio, com o mesmo valor que esta na Vercel. Sem o segredo o fluxo termina sem fazer nada; o cron diario da Vercel continua valendo como rede de seguranca.
Os dois chamando a mesma rota nao duplicam nada. A regra de envio pergunta "ja passou da hora escolhida, hoje, sem leitura?" e marca o dia ao enviar, entao o lembrete sai uma vez so - e sai correto tanto de hora em hora quanto uma vez por dia.
O mesmo fluxo chama, em um segundo passo, /api/cron/acompanhamento: a
verificacao das series acompanhadas e dos feeds assinados. Cada origem e
consultada no maximo a cada 6 horas, entao a frequencia horaria nao
sobrecarrega os sites. Essa rota nao esta no vercel.json, pelo mesmo limite
de um disparo por dia do plano Hobby; sem CRON_SECRET no GitHub, series e
feeds simplesmente nao sao verificados.
O agendamento do GitHub tem duas limitacoes conhecidas, ambas aceitaveis aqui: execucao agendada pode atrasar alguns minutos quando a fila esta cheia, e o GitHub desativa fluxos agendados em repositorio sem commit ha 60 dias.
NEXT_PUBLIC_BASE_URLA variavel foi removida. Ela existia porque a rota de cadastro checava se o e-mail ja estava em uso fazendo uma requisicao HTTP para a propria aplicacao:
await fetch(`${process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"}/api/auth/check`, ...)
Isso trazia tres problemas: dependia da variavel estar correta em producao (com
o padrao localhost:3000, o cadastro quebrava silenciosamente no deploy),
gastava uma ida e volta de rede para uma consulta que o processo ja podia fazer,
e expunha /api/auth/check, um endpoint publico que respondia se um e-mail
estava cadastrado — ou seja, permitia enumerar contas.
Hoje o cadastro consulta o banco diretamente e o endpoint /api/auth/check nao
existe mais. Nao ha nada a configurar no lugar.
| Comando | O que faz |
|---|---|
npm run dev | Servidor de desenvolvimento. |
npm run build / npm start | Build e execucao em producao. |
npm run lint / npm run typecheck | ESLint e TypeScript. |
npm test / npm run test:watch | Testes das regras puras (Vitest). |
npm run test:e2e | Testes no navegador (Playwright), contra a aplicacao com banco. |
npm run db:generate | Gera migration a partir do schema. |
npm run db:migrate | Aplica as migrations pendentes. |
npm run db:push | Sincroniza o schema sem migration (so em desenvolvimento). |
npm run db:seed | Dados de demonstracao. |
npm run secret | Gera um JWT_SECRET. |
src/
app/
(app)/ telas autenticadas: dashboard, textos, leitor, historico, ajustes
(auth)/ login e cadastro
api/ rotas HTTP
components/ navegacao, providers e primitivas de interface
db/ schema, conexao, migrations e seed
hooks/ useResource, useWakeLock
lib/ auth, sessao, parser, leitura, rate limit, busca protegida
middleware.ts protecao de rotas no servidor
drizzle/ migrations SQL versionadas
docs/backlog.md backlog de produto, com o status de cada story
tests/ testes das regras puras
e2e/ testes no navegador: fluxo principal e acessibilidade
JWT_SECRET em cookie httpOnly. A aplicacao
falha na inicializacao se a variavel nao existir — nao ha chave padrao.useEffect no cliente.texts.content em tempo de execucao. Nao ha tabela
com uma linha por palavra.reading_mode, words_per_chunk,
paragraph_pause e dim_lines ficam no banco sem uso.pause(motivo): freio, folha aberta, aba
escondida, marco, ponto de parada e descanso atualizam os mesmos
acumuladores. O prazo de cada palavra e absoluto (inicio + duracao), entao o
custo de render nao se acumula e mudar a velocidade no meio nao reinicia a
palavra. A pagina fica montada e memoizada por baixo do Word Runner, e o
rodape tem altura fixa: play e freio nao refazem a paginacao.src/lib/pacing.ts, pauses.ts, sentences.ts): peso
lexical normalizado (tamanho, digito, nome proprio, titulo, palavra ja
consultada) mais uma pausa depois da palavra - virgula, fim de frase e fim
de paragrafo, pela lei P300 x (duracao/200)^0,8 com P300 = 160/360/520 ms.
Um segmentador de frase unico (abreviaturas, tratamentos e iniciais por
idioma) serve as pausas, a navegacao por frase, o recuo ao retomar, o
destaque de frase e a narracao. A rampa de aquecimento e o recuo ao retomar
sao proporcionais ao tempo parado.runner, narracao, pagina),
contabilizada em src/lib/reader-session.ts. As pausas de pontuacao e o
atraso da rampa saem do tempo gravado: o ppm do historico e o das palavras,
e as previsoes de tempo (predictRunnerMs, ) somam as pausas
de volta. Virar a pagina para a frente conta a pagina quando o tempo nela e
plausivel (teto de 2 min por pagina). A sessao e gravada ao esconder a aba
e no , com ou sem leitura correndo, junto com os freios e recuos
(sinal para a sugestao de reduzir a velocidade).pauseOverheadpagehidearticleBody declarado em JSON-LD,
depois o container marcado com itemprop="articleBody" (microdado
schema.org), depois <article> e main, e so por ultimo o palpite pelo
maior container. O microdado e o caminho usado no Literotica, e com ele o
bloco de anuncios que vinha antes do conto some por completo. Quando o
container e exato, nenhum bloco e descartado por tamanho: em ficcao as falas
de dialogo sao curtas e o filtro antigo apagava boa parte do texto.<p> para cada um
(titulos com role="heading"). A regua de medicao monta os mesmos
paragrafos, senao a quebra de pagina calcularia uma altura menor que a real.?page= incrementado. A base e sempre a URL importada, que conta como pagina
1, nunca a da ultima busca - assim o parametro nao se acumula a cada chamada.
Nenhum desfecho esperado vira erro: "nao ha mais paginas", "a origem nao
respondeu" e "veio a mesma pagina de novo" voltam com 200 e um status
proprio, e a tela apenas informa. A mesma protecao anti-SSRF da importacao
vale aqui, e paginas repetidas sao detectadas para nao duplicar o texto./api/texts e /api/reading-sessions aceitam page e
perPage e devolvem o total. /api/stats calcula as somas do painel no
banco, em vez de baixar o historico inteiro para somar no cliente.lib/safe-fetch.ts, que resolve o
DNS e recusa enderecos de rede interna, revalidando cada redirecionamento.share_target, entao o
app instalado aparece na folha de compartilhamento do sistema: tocar em
Compartilhar no navegador manda o link para /compartilhar, que importa e
abre o leitor. Um endereco que ja esta na biblioteca nao e buscado de novo -
a leitura abre onde parou, e lib/source-url.ts cuida para que rastreamento,
barra final e fragmento nao facam o mesmo conto virar duas entradas. Quando a
origem manda uma selecao em vez de um link, o texto cai no formulario de
texto colado.share_target usa GET, entao a navegacao
em si nao grava nada: a importacao sai de POST /api/share. E ela so comeca
sozinha quando o cabecalho Sec-Fetch-Site indica que a navegacao nao veio de
outro site. O cookie de sessao e SameSite=Lax e acompanha navegacao de topo,
entao sem essa checagem qualquer pagina poderia apontar para /compartilhar e
fazer o servidor buscar um endereco escolhido por ela. Vindo de fora, a tela
espera um toque.drizzle/ e a unica fonte, aplicada por npm run db:migrate
— localmente a mao e, no deploy, pelo vercel-build antes do next build.lib/queries.ts guarda essas
consultas e serve tanto as telas quanto as rotas de API, para nao existirem
duas versoes da mesma pergunta ao banco.loading.tsx. Alem de responder
ao toque na hora, ele permite que o servidor envie a estrutura da pagina antes
de a consulta terminar - o que importa porque o banco do plano gratuito
hiberna e a primeira consulta depois disso demora.GET /api/health responde se o banco esta acessivel e
quais variaveis estao presentes, sem expor nenhum valor.e2e/ percorre o fluxo principal (cadastro, texto
colado, leitura ate o fim, historico), a pagina e o Word Runner, o motor do
leitor (folhas pausam, aba escondida grava, pagina virada conta, toque duplo
no freio), a leitura guiada por tempo livre, a aprendizagem (lacunas,
revisao de destaques, busca no conteudo, lote de links) e a verificacao de
acessibilidade com axe nos temas claro,
escuro e de alto contraste. Roda na CI em um job proprio, com Postgres de
servico. Localmente, npm run test:e2e reaproveita o servidor que estiver
na porta 3200 (ou E2E_PORT); PW_CHROMIUM_PATH aponta um Chromium ja
instalado. Cada teste se apresenta com um IP proprio em x-forwarded-for,
para o limite de cadastros por IP nao barrar a suite.requireSession e no
layout autenticado.POST /api/erros.src/lib/ai.ts:
chave, modelo por tarefa, fallback de recusa, traducao de erro, registro de
uso (ai_usage) e consentimento da conta. Modelos da familia 5.5 por
tarefa: Opus no questionario e nas perguntas ao texto, Sonnet na explicacao
e nos resumos, Haiku no dicionario, na limpeza da importacao, nas etiquetas
e na sinopse. O Haiku nao tem fallback no servidor, entao a recusa cai na
alternativa local de cada tela.speed_settings.ai_enabled). A rota recusa com 403 e consent, e
a folha que pediu mostra o aviso no lugar da resposta. Ajustes > Recursos
de IA liga, desliga e mostra o uso do dia.lang da pagina ou do dc:language do EPUB,
portugues quando ausente. Decide a voz da narracao e o pedido ao modelo no
dicionario e no questionario.leitura-vozes, que o service worker e a saida da conta preservam, e a
sintese roda em um Web Worker (public/piper-worker.js). O motor (ONNX
Runtime Web e o conversor de fonemas em wasm) e copiado de node_modules
para public/tts no postinstall; a pasta nao e versionada.src/lib/xray.ts) e
guarda marcadores de posicao (tabela bookmarks). Um salto maior que uma
pagina deixa o atalho "Voltar para onde parou". O teclado muda a velocidade
(setas para cima e para baixo), anda por pagina ou por frase (setas), abre o
significado da palavra atual (D), destaca a frase (H); "?" mostra a ajuda.
As regras ficam em src/lib/navigation.ts.recovery_codes, so o
hash) redefinem a senha em /recuperar. Cada login cria uma linha em
auth_sessions e o token leva o id dela: Ajustes lista os aparelhos e
desconecta um sem derrubar os outros.src/lib/backup.ts) e volta pela restauracao em Ajustes, em lotes de ate
3 MB com desfazer. Arquivos .docx sao convertidos em Markdown no navegador.
A tela de destaques baixa o texto inteiro anotado.