o site tinha "world.execute(me); Mili" hardcoded no widget de música. literalmente hardcoded. sempre essa música, independente do q eu tivesse ouvindo. dava até vergonha. >_<
ai decidi arrumar isso pra valer, queria q os widgets refletissem o q eu realmente estou jogando e estudando em tempo real. simples de explicar, menos simples de fazer num site 100% estático no GitHub Pages sem backend nenhum.
Analisando as Opções
cada widget tem uma fonte de dados diferente, e num site estático vc não pode fazer nada server-side. as opções viáveis eram:
| Widget | Melhor opção | Como funciona | Dificuldade |
|---|---|---|---|
| ouvindo agora | Last.fm + Web Scrobbler | extensão scrobble pro Last.fm, site faz fetch na API deles | preguiça u_u |
| agora jogando | Lanyard | Discord detecta o jogo via Rich Presence, Lanyard expõe como API pública | baixa |
| now studying | status.json manual | arquivo JSON no repo, site faz fetch, atualizo via git push | mt baixa |
Steam API seria outra opção pra gaming, mas tem CORS bloqueado e precisaria de um proxy. o Lanyard tem CORS liberado, é gratuito, open source, e o Discord já detecta os jogos automaticamente. escolha óbvia.
Discord Rich Presence só funciona com o app desktop, não o web. e no Linux a detecção de jogos é irregular, jogos Steam nativos geralmente funcionam, mas jogos via Proton/Wine são sorte, o Discord as vezes vê o processo wine em vez do jogo em si. no meu Nobara Linux tem funcionado razoavelmente bem.
A Implementação
a parte do código foi tranquila. três mudanças:
1. criei o status.json na raiz do site:
{
"studying": "Lua & C & C++"
}
2. adicionei IDs nos elementos HTML dos widgets e separei gaming de studying (antes tava tudo misturado num widget só com label errado):
<!-- widget gaming -->
<div class="track" id="game-track">—</div>
<div class="music-bars" id="game-bars" style="display:none">...</div>
<!-- widget studying (novo) -->
<div class="track" id="study-track">—</div>
3. ~40 linhas de JS inline no final do HTML. Lanyard com polling de 60s, status.json fetch único no load:
// Lanyard: polling a cada 60s
(function () {
var LANYARD = 'https://api.lanyard.rest/v1/users/SEU_ID';
function fetchGame() {
fetch(LANYARD)
.then(function (r) { return r.json(); })
.then(function (d) {
var track = document.getElementById('game-track');
var bars = document.getElementById('game-bars');
if (!track) return;
var acts = d.data && d.data.activities;
var game = acts && acts.find(function (a) { return a.type === 0; });
if (game) {
track.textContent = game.name;
if (bars) bars.style.display = ''; // mostra as barrinhas
} else {
track.textContent = 'nada no momento';
if (bars) bars.style.display = 'none';
}
})
.catch(function () {});
}
fetchGame();
setInterval(fetchGame, 60000);
})();
// status.json: fetch único no load
(function () {
fetch('/status.json')
.then(function (r) { return r.json(); })
.then(function (d) {
var el = document.getElementById('study-track');
if (el && d.studying) el.textContent = d.studying;
})
.catch(function () {});
})();
o studying funciona na primeira tacada. o gaming... não. (ꐦ°᷄д°᷅)
O Widget Não Atualizava
abri o localhost:8080 e o widget de gaming ficou mostrando "—". o de studying funcionou beleza (apareceu "Lua & C & C++"), mas o gaming nada.
abri o console do browser e rodei o fetch manualmente:
fetch('https://api.lanyard.rest/v1/users/1368778707334070343')
.then(r=>r.json()).then(d=>console.log(d))
o retorno foi:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at https://api.lanyard.rest/v1/users/...
(Reason: CORS request did not succeed). Status code: (null).
status (null). isso não é erro de CORS header, é falha de rede. o browser não chegou nem a receber resposta. fui pro terminal:
curl -s https://api.lanyard.rest/v1/users/1368778707334070343
output: absolutamente nada. nenhum JSON, nenhum erro visível. ¯\_(ツ)_/¯
ai rodei com verbose:
curl -v https://api.lanyard.rest/v1/users/1368778707334070343 2>&1 | head -30
* Could not resolve host: api.lanyard.rest
* Closing connection
curl: (6) Could not resolve host: api.lanyard.rest
DNS não resolvia o domínio. o servidor existe, o site existe, mas o meu DNS local simplesmente não sabia quem era api.lanyard.rest.
Diagnóstico: DNS do ISP Não Tem o Registro
testei com o DNS do Google pra confirmar:
nslookup api.lanyard.rest 8.8.8.8
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
api.lanyard.rest canonical name = lanyard.gateway.railway.app.
Name: lanyard.gateway.railway.app
Address: 151.101.2.15
com o 8.8.8.8 resolveu na hora. o problema é claro, o DNS padrão que eu tava usando não tinha o registro pro domínio .rest. o Lanyard fica hospedado no Railway (lanyard.gateway.railway.app) com IP 151.101.2.15.
Fix Rápido: /etc/hosts
com o IP em mãos, botei direto no /etc/hosts pra não depender de resolução DNS:
echo "151.101.2.15 api.lanyard.rest" | sudo tee -a /etc/hosts
testei:
curl -s https://api.lanyard.rest/v1/users/1368778707334070343
{"data":{"activities":[{"name":"Sixtar Gate STARTRAIL","type":0,...}],
"discord_status":"online",...},"success":true}
JSON lindo na tela. recarreguei o localhost:8080 e o widget apareceu, Sixtar Gate STARTRAIL. (✿◡‿◡)
o IP do Railway pode mudar. a solução permanente é trocar o DNS do sistema pra 1.1.1.1 (Cloudflare) ou 8.8.8.8 (Google) nas configurações de rede. no Nobara/Fedora: Settings → Network → sua conexão → aba IPv4 → DNS.
Resultado Final
| Widget | Antes | Depois |
|---|---|---|
| agora jogando | hardcoded, label errado ("now studying:") | Discord Rich Presence via Lanyard, polling 60s |
| now studying | hardcoded junto com o widget de gaming | widget separado, lê /status.json do repo |
| ouvindo agora | hardcoded "world.execute(me); Mili" | ainda hardcoded (Last.fm fica pra depois) |
pra atualizar o studying é só editar o status.json e dar git push. simples. nao tem motivo pra complicar o q é manual por natureza.
Lição
se vc usa o DNS padrão do seu ISP no Brasil, tem chance real de domínios com TLDs menos comuns (.rest, .dev, etc.) simplesmente não resolverem. o ISP não é obrigado a ter todos os registros atualizados.
troca pra 1.1.1.1 ou 8.8.8.8 e esquece o problema. é uma mudança de 30 segundos que salva dor de cabeça futura. (⌐■_■)
e se vc tiver um erro de CORS com status (null) no browser, não é CORS, é rede. o browser reporta qualquer falha de fetch cross-origin como "CORS error". vai direto pro terminal e testa com curl -v.
update: migrando pro Cloudflare Pages
mudei de GitHub Pages pro Cloudflare Pages pra ganhar edge functions. com isso, o widget "agora jogando" ganhou uma camada a mais de privacidade e performance.
antes, o client-side fazia fetch direto pro api.lanyard.rest, expondo meu Discord ID (1368778707334070343) no HTML pra qualquer um inspecionar. agora tem uma edge function em /api/now-playing que faz o fetch no servidor, cacheia por 60s, e o cliente só vê o proxy local. menos exposição, mais rápido, e a CSP ficou mais limpa também.
// antes (client-side, ID exposto)
fetch('https://api.lanyard.rest/v1/users/1368778707334070343')
// depois (edge function, ID escondido)
fetch('/api/now-playing')
a function é literalmente ~10 linhas. o Cloudflare faz o fetch, aplica cache no edge, e devolve o JSON. o navegador nunca toca no domínio externo.
IDs de usuário do Discord não são segredos de estado, mas expor qualquer identificador sem necessidade é má prática. se alguém quiser fazer scraping da minha presença 24/7, agora precisa bater no meu domínio. eu controlo o rate limit e o cache.
fora isso, o Cloudflare Pages ainda entrega o site mais perto dos visitantes (CDN global) e tem suporte nativo a functions. o GitHub Pages é ótimo pra sites 100% estáticos, mas pra qualquer coisa dinâmica, edge é o caminho.