Cabin Fever Coding: um tutorial de back-end do Node.js
Publicados: 2022-03-11O bloqueio do COVID-19 deixou muitos de nós presos em casa, talvez esperando que a mera febre da cabine seja o pior tipo de febre que experimentaremos. Muitos de nós estão consumindo mais conteúdo de vídeo do que nunca. Embora o exercício seja especialmente importante agora, às vezes há nostalgia pelo luxo de um bom e antiquado controle remoto quando o laptop está fora de alcance.
É aí que entra este projeto: a oportunidade de transformar qualquer smartphone - mesmo um antigo que é inútil por falta de atualizações - em um controle remoto útil para o próximo Netflix/YouTube/Amazon Prime Video/etc. binge-watch. É também um tutorial de back-end do Node.js: uma chance de aprender os fundamentos do JavaScript de back-end usando a estrutura Express e o mecanismo de modelo Pug (anteriormente Jade).
Se isso parece assustador, o projeto completo do Node.js será apresentado no final; os leitores só precisam aprender o quanto estiverem interessados em aprender, e haverá um número razoável de explicações mais suaves de alguns conceitos básicos ao longo do caminho que os leitores mais experientes podem pular.
Por que não apenas...?
Os leitores podem se perguntar: "Por que codificar um back-end Node.js?" (Além da oportunidade de aprendizado, é claro.) "Já não existe um aplicativo para isso?"
Claro, muitos deles. Mas há duas razões principais que isso pode não ser desejável:
- Para aqueles que tentam redirecionar um telefone antigo, isso pode simplesmente não ser mais uma opção , como é o caso do dispositivo Windows Phone 8.1 que eu queria usar. (A loja de aplicativos foi oficialmente fechada no final de 2019.)
- Confiança (ou falta dela). Como muitos aplicativos encontrados em qualquer plataforma móvel, eles geralmente vêm com a exigência de que os usuários concedam muito mais permissões do que o aplicativo precisa para o que se propõe a fazer. Mas mesmo que esse aspecto seja adequadamente limitado, a natureza de um aplicativo de controle remoto significa que os usuários ainda precisam confiar que os desenvolvedores de aplicativos não estão abusando de seus privilégios na área de trabalho da solução, incluindo spyware ou outro malware.
Esses problemas existem há muito tempo e foram até a motivação para um projeto semelhante de 2014 encontrado no GitHub. nvm
facilita a instalação de versões mais antigas do Node.js e, mesmo que algumas dependências precisem ser atualizadas, o Node.js tem uma grande reputação de ser compatível com versões anteriores.
Infelizmente, o bitrot venceu. Uma abordagem obstinada e a compatibilidade de back-end do Node.js não foram páreo para intermináveis depreciações e loops de dependência impossíveis entre versões antigas do Grunt, Bower e dezenas de outros componentes. Horas depois, ficou mais do que claro que seria muito mais fácil começar do zero – apesar do conselho do próprio autor contra reinventar a roda.
Novos Gizmos do Antigo: Reaproveitando telefones como controles remotos usando um back-end Node.js
Em primeiro lugar, observe que este projeto Node.js é atualmente específico para Linux - desenvolvido e testado no Linux Mint 19 e Linux Mint 19.3, em particular - mas o suporte para outras plataformas certamente pode ser adicionado. Pode já funcionar em um Mac.
Supondo que uma versão moderna do Node.js esteja instalada e um prompt de comando esteja aberto em um novo diretório que servirá como raiz do projeto, estamos prontos para começar com o Express:
npx express-generator --view=pug
Observação: aqui, npx
é uma ferramenta útil que vem com npm
, o gerenciador de pacotes Node.js que acompanha o Node.js. Estamos usando-o para executar o gerador de esqueleto de aplicativo do Express. No momento em que este artigo foi escrito, o gerador cria um projeto Express/Node.js que, por padrão, ainda recebe um mecanismo de modelo chamado Jade, embora o projeto Jade tenha se renomeado para “Pug” da versão 2.0 em diante. Portanto, para estar atualizado e usar o Pug imediatamente - além disso, evite avisos de descontinuação - --view=pug
, uma opção de linha de comando para o script express-generator
sendo executado por npx
.
Feito isso, precisamos instalar alguns pacotes da lista de dependências recém-preenchida do nosso projeto Node.js em package.json
. A maneira tradicional de fazer isso é executar npm i
( i
para “instalar”). Mas alguns ainda preferem a velocidade do Yarn, então se você tiver instalado, simplesmente execute o yarn
sem parâmetros.
Nesse caso, deve ser seguro ignorar o aviso de descontinuação (esperamos que seja corrigido em breve) de uma das subdependências do Pug, desde que o acesso seja mantido conforme necessário na rede local.
Um início rápido de yarn start
ou npm start
, seguido de navegação para localhost:3000
em um navegador, mostra que nosso back-end básico do Node.js baseado em Express funciona. Podemos matá-lo com Ctrl+C
.
Tutorial de back-end do Node.js, Etapa 2: como enviar pressionamentos de tecla na máquina host
Com a parte remota já na metade, vamos voltar nossa atenção para a parte de controle . Precisamos de algo que possa controlar programaticamente a máquina na qual executaremos nosso back-end Node.js, fingindo que está pressionando teclas no teclado.
Para isso, instalaremos xdotool
usando suas instruções oficiais. Um teste rápido de seu comando de exemplo em um terminal:
xdotool search "Mozilla Firefox" windowactivate --sync key --clearmodifiers ctrl+l
…deve fazer exatamente o que diz, supondo que o Mozilla Firefox esteja aberto no momento. Isso é bom! É fácil fazer com que nosso projeto Node.js chame ferramentas de linha de comando como xdotool
, como veremos em breve.
Tutorial de back-end do Node.js, Etapa 3: design de recursos
Isso pode não ser verdade para todos, mas pessoalmente, acho que muitos controles remotos físicos modernos têm cerca de cinco vezes mais botões do que eu jamais usarei. Então, para este projeto, estamos olhando para um layout de tela cheia com uma grade de três por três de botões bonitos, grandes e fáceis de direcionar. Depende da preferência pessoal quais podem ser esses nove botões.
Acontece que os atalhos de teclado para as funções mais simples não são idênticos no Netflix, YouTube e Amazon Prime Video. Esses serviços também não funcionam com chaves de mídia genéricas, como é provável que um aplicativo de player de música nativo funcione. Além disso, algumas funções podem não estar disponíveis em todos os serviços.
Então, o que precisamos fazer é definir um layout de controle remoto diferente para cada serviço e fornecer uma maneira de alternar entre eles.
Definindo layouts de controle remoto e mapeando-os para atalhos de teclado
Vamos obter um protótipo rápido trabalhando com um punhado de predefinições. Vamos colocá-los em common/preset_commands.js
—“common” porque incluiremos esses dados de mais de um arquivo:
module.exports = { // We could use ️ but some older phones (eg, Android 5.1.1) won't show it, hence ️ instead 'Netflix': { commands: { '-': 'Escape', '+': 'f', '': 'Up', '⇤': 'XF86Back', '️': 'Return', '': 'Down', '': 'Left', '': 'Right', '': 'm', }, }, 'YouTube': { commands: { '⇤': 'shift+p', '⇥': 'shift+n', '': 'Up', 'CC': 'c', '️': 'k', '': 'Down', '': 'j', '': 'l', '': 'm', }, }, 'Amazon Prime Video': { window_name_override: 'Prime Video', commands: { '⇤': 'Escape', '+': 'f', '': 'Up', 'CC': 'c', '️': 'space', '': 'Down', '': 'Left', '': 'Right', '': 'm', }, }, 'Generic / Music Player': { window_name_override: '', commands: { '⇤': 'XF86AudioPrev', '⇥': 'XF86AudioNext', '': 'XF86AudioRaiseVolume', '': 'r', '️': 'XF86AudioPlay', '': 'XF86AudioLowerVolume', '': 'Left', '': 'Right', '': 'XF86AudioMute', }, }, };
Os valores do código-chave podem ser encontrados usando xev
. (Para mim, os “audio mute” e “audio play” não eram detectáveis usando este método, então também consultei uma lista de teclas de mídia.)
Os leitores podem notar a diferença entre maiúsculas e minúsculas entre space
e Return
— independentemente do motivo, esse detalhe deve ser respeitado para que xdotool
funcione corretamente. Relacionado a isso, temos algumas definições escritas explicitamente – por exemplo, shift+p
, embora P
também funcione – apenas para manter nossas intenções claras.
Tutorial de back-end do Node.js, Etapa 4: o endpoint “chave” da nossa API (desculpe o trocadilho)
Precisaremos de um endpoint para o POST
, que por sua vez simulará pressionamentos de tecla usando xdotool
. Como teremos diferentes grupos de chaves que podemos enviar (uma para cada serviço), chamaremos o endpoint para um group
específico. Redefiniremos o endpoint de users
gerado renomeando routes/users.js
para routes/group.js
e fazendo as alterações correspondentes em app.js
:
// ... var indexRouter = require('./routes/index'); var groupRouter = require('./routes/group'); // ... app.use('/', indexRouter); app.use('/group', groupRouter); // ...
A principal funcionalidade é usar xdotool
por meio de uma chamada de shell do sistema em routes/group.js
. Vamos codificar o YouTube
como o menu de escolha no momento, apenas para fins de teste.
const express = require('express'); const router = express.Router(); const debug = require('debug')('app'); const cp = require('child_process'); const preset_commands = require('../common/preset_commands'); /* POST keystroke to simulate */ router.post('/', function(req, res, next) { const keystroke_name = req.body.keystroke_name; const keystroke_code = preset_commands['YouTube'].commands[keystroke_name]; const final_command = `xdotool \ search "YouTube" \ windowactivate --sync \ key --clearmodifiers ${keystroke_code}`; debug(`Executing ${final_command}`); cp.exec(final_command, (err, stdout, stderr) => { debug(`Executed ${keystroke_name}`); return res.redirect(req.originalUrl); }); }); module.exports = router;
Aqui, pegamos a chave solicitada “name” do corpo da solicitação POST
( req.body
) sob o parâmetro chamado keystroke_name
. Isso vai ser algo como ️
. Em seguida, usamos isso para procurar o código correspondente do objeto de commands
de preset_commands['YouTube']
.
O comando final está em mais de uma linha, então o \
s no final de cada linha une todas as partes em um único comando:
-
search "YouTube"
busca a primeira janela com "YouTube" no título. -
windowactivate --sync
ativa a janela buscada e espera até que esteja pronta para receber um pressionamento de tecla. -
key --clearmodifiers ${keystroke_code}
envia o pressionamento de tecla, certificando-se de limpar temporariamente as teclas modificadoras como Caps Lock que podem interferir no que estamos enviando.
Neste ponto, o código assume que estamos fornecendo uma entrada válida — algo sobre o qual teremos mais cuidado mais tarde.
Para simplificar, o código também assumirá que há apenas uma janela de aplicativo aberta com “YouTube” em seu título — se houver mais de uma correspondência, não há garantia de que enviaremos pressionamentos de tecla para a janela pretendida. Se isso for um problema, pode ser útil que os títulos das janelas possam ser alterados simplesmente alternando as guias do navegador em todas as janelas além daquela a ser controlada remotamente.
Com isso pronto, podemos iniciar nosso servidor novamente, mas desta vez com a depuração habilitada para que possamos ver a saída de nossas chamadas de debug
. Para fazer isso, basta executar DEBUG=old-fashioned-remote:* yarn start
ou DEBUG=old-fashioned-remote:* npm start
. Quando estiver em execução, reproduza um vídeo no YouTube, abra outra janela de terminal e tente uma chamada cURL:
curl --data "keystroke_name=️" http://localhost:3000/group
Isso envia uma solicitação POST
com o nome do pressionamento de tecla solicitado em seu corpo para nossa máquina local na porta 3000
, a porta em que nosso back-end está escutando. A execução desse comando deve gerar notas sobre Executing
e Executed
na janela npm
e, mais importante, abrir o navegador e pausar seu vídeo. A execução desse comando novamente deve fornecer a mesma saída e despausá-la.
Tutorial de back-end do Node.js, Etapa 5: vários layouts de controle remoto
Nosso back-end ainda não está pronto. Também precisaremos dele para poder:
- Produza uma lista de layouts de controle remoto a partir de
preset_commands
. - Produza uma lista de “nomes” de pressionamento de tecla depois de escolher um layout de controle remoto específico. (Também poderíamos ter escolhido usar
common/preset_commands.js
diretamente no front-end, já que já é JavaScript, e filtrado lá. Essa é uma das vantagens potenciais de um back-end Node.js, simplesmente não o usamos aqui .)
Esses dois recursos são onde nosso tutorial de back-end do Node.js se cruza com o front-end baseado em Pug que estaremos construindo.
Usando o modelo Pug para apresentar uma lista de controles remotos
A parte de back-end da equação significa modificar routes/index.js
para ficar assim:
const express = require('express'); const router = express.Router(); const preset_commands = require('../common/preset_commands'); /* GET home page. */ router.get('/', function(req, res, next) { const group_names = Object.keys(preset_commands); res.render('index', { title: 'Which Remote?', group_names, portrait_css: `.group_bar { height: calc(100%/${Math.min(4, group_names.length)}); line-height: calc(100vh/${Math.min(4, group_names.length)}); }`, landscape_css: `.group_bar { height: calc(100%/${Math.min(2, group_names.length)}); line-height: calc(100vh/${Math.min(2, group_names.length)}); }`, }); }); module.exports = router;
Aqui, pegamos nossos nomes de layout de controle remoto ( group_names
) chamando Object.keys
em nosso arquivo preset_commands
. Em seguida, os enviamos e alguns outros dados necessários para o mecanismo de modelo do Pug que é chamado automaticamente via res.render()
.
Cuidado para não confundir o significado das keys
aqui com os toques de tecla que estamos enviando: Object.keys
nos dá um array (uma lista ordenada) contendo todas as chaves dos pares chave-valor que compõem um objeto em JavaScript:

const my_object = { 'a key' : 'its corresponding value' , 'another key' : 'its separate corresponding value' , };
Se observarmos common/preset_commands.js
, veremos o padrão acima e nossas chaves (no sentido de objeto) são os nomes de nossos grupos: 'Netflix'
, 'YouTube'
, etc. Seus valores correspondentes não são strings simples como my_object
tem acima - eles são objetos inteiros, com suas próprias chaves, ou seja, commands
e possivelmente window_name_override
.
O CSS personalizado que está sendo passado aqui é, reconhecidamente, um pouco hack. A razão pela qual precisamos dele em vez de usar uma solução moderna baseada em flexbox é para melhor compatibilidade com o maravilhoso mundo dos navegadores móveis em suas encarnações antigas ainda mais maravilhosas. Neste caso, a principal coisa a notar é que no modo paisagem, estamos mantendo os botões grandes mostrando não mais que duas opções por tela; no modo retrato, quatro.
Mas onde isso realmente é transformado em HTML para ser enviado ao navegador? É aí que entra o views/index.pug
, que queremos ficar assim:
extends layout block header_injection style(media='(orientation: portrait)') #{portrait_css} style(media='(orientation: landscape)') #{landscape_css} block content each group_name in group_names span(class="group_bar") a(href='/group/?group_name=' + group_name) #{group_name}
A primeira linha é importante: extends layout
significa que o Pug pegará este arquivo no contexto de views/layout.pug
, que é uma espécie de template pai que vamos reutilizar aqui e também em outra view. Precisaremos adicionar algumas linhas após a linha do link
para que o arquivo final fique assim:
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') block header_injection meta(name='viewport', content='user-scalable=no') body block content
Não entraremos no básico do HTML aqui, mas para leitores não familiarizados com eles, este código Pug espelha o código HTML de tarifa padrão encontrado em quase todos os lugares. O aspecto de modelagem começa com title= title
, que define o título HTML para qualquer valor correspondente à chave de title
do objeto que passamos ao Pug via res.render
.
Podemos ver um aspecto diferente de modelar duas linhas depois com um block
que estamos nomeando header_injection
. Blocos como esses são espaços reservados que podem ser substituídos por modelos que estendem o atual. (Não relacionado, a linha meta
é simplesmente uma solução rápida para navegadores móveis, portanto, quando os usuários tocam nos controles de volume várias vezes seguidas, o telefone se abstém de aumentar ou diminuir o zoom.)
De volta aos nossos block
: É por isso que views/index.pug
define seus próprios block
com os mesmos nomes encontrados em views/layout.pug
. Neste caso de header_injection
, isso nos permite usar CSS específico para orientações de retrato ou paisagem em que o telefone estará.
content
é onde colocamos a parte principal visível da página web, que neste caso:
- Faz um loop no array
group_names
que passamos, - cria um elemento
<span>
para cada um com a classe CSSgroup_bar
aplicada a ele, e - cria um link dentro de cada
<span>
com base nogroup_name
.
A classe CSS group_bar
podemos definir no arquivo obtido via views/layout.pug
, ou seja, public/stylesheets/style.css
:
html, body, form { padding: 0; margin: 0; height: 100%; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } .group_bar, .group_bar a, .remote_button { box-sizing: border-box; border: 1px solid white; color: greenyellow; background-color: black; } .group_bar { width: 100%; font-size: 6vh; text-align: center; display: inline-block; } .group_bar a { text-decoration: none; display: block; }
Neste ponto, se npm start
ainda estiver em execução, acessar http://localhost:3000/
em um navegador de desktop deve mostrar dois botões muito grandes para Netflix e YouTube, com o restante disponível rolando para baixo.
Mas se clicarmos neles neste ponto, eles não funcionarão, porque ainda não definimos a rota à qual eles se vinculam (o GET
ting de /group
.)
Mostrando o layout do controle remoto escolhido
Para fazer isso, adicionaremos isso a routes/group.js
antes da linha final module.exports
:
router.get('/', function(req, res, next) { const group_name = req.query.group_name || ''; const group = preset_commands[group_name]; return res.render('group', { keystroke_names: Object.keys(group.commands), group_name, title: `${group_name.match(/([AZ])/g).join('')}-Remote` }); });
Isso fará com que o nome do grupo seja enviado para o terminal (por exemplo, colocando ?group_name=Netflix
no final de /group/
), e use-o para obter o valor dos commands
do grupo correspondente. Esse valor ( group.commands
) é um objeto, e as chaves desse objeto são os nomes ( keystroke_names
) que exibiremos em nosso layout de controle remoto.
Observação: desenvolvedores inexperientes não precisarão entrar em detalhes de como funciona, mas o valor de title
usa um pouco de expressões regulares para transformar nossos nomes de grupos/layout em acrônimos. Por exemplo, nosso controle remoto do YouTube terá o título do navegador YT-Remote
. Dessa forma, se estivermos depurando em nossa máquina host antes de tentar as coisas em um telefone, não teremos xdotool
pegando a própria janela do navegador de controle remoto, em vez da que estamos tentando controlar. Enquanto isso, em nossos telefones, o título será agradável e curto, caso queiramos marcar o controle remoto.
Assim como em nosso encontro anterior com res.render
, este está enviando seus dados para se misturar com o template views/group.pug
. Vamos criar esse arquivo e preenchê-lo com isso:
extends layout block header_injection script(type='text/javascript', src='/javascript/group-client.js') block content form(action="/group?group_name=" + group_name, method="post") each keystroke_name in keystroke_names input(type="submit", name="keystroke_name", value=keystroke_name, class="remote_button")
Assim como em views/index.pug
, estamos substituindo os dois blogs de views/layout.pug
. Desta vez, não é CSS que estamos colocando no cabeçalho, mas algum JavaScript do lado do cliente, ao qual chegaremos em breve. (E sim, em um momento de perspicácia, renomeei os javascripts
pluralizados incorretamente …)
O content
principal aqui é um formulário HTML feito de vários botões de envio diferentes, um para cada keystroke_name
. Cada botão envia o formulário (fazendo uma solicitação POST
) usando o nome da tecla que está exibindo como o valor que está enviando com o formulário.
Também precisaremos de um pouco mais de CSS em nosso arquivo de folha de estilo principal:
.remote_button { float: left; width: calc(100%/3); height: calc(100%/3); font-size: 12vh; }
Anteriormente, quando configuramos o endpoint, terminamos de processar a solicitação com:
return res.redirect(req.originalUrl);
Isso significa efetivamente que, quando o navegador envia o formulário, o back-end do Node.js responde dizendo ao navegador para voltar à página da qual o formulário foi enviado, ou seja, o layout principal do controle remoto. Seria mais elegante sem trocar de página; no entanto, queremos compatibilidade máxima com o mundo estranho e maravilhoso dos navegadores móveis decrépitos. Dessa forma, mesmo sem nenhum JavaScript de front-end funcionando, nosso projeto de back-end Node.js ainda deve funcionar.
Uma pitada de JavaScript front-end
A desvantagem de usar um formulário para enviar pressionamentos de tecla é que o navegador precisa esperar e, em seguida, executar uma viagem de ida e volta extra: a página e suas dependências precisam ser solicitadas do nosso back-end Node.js e entregues. Em seguida, eles precisam ser renderizados novamente pelo navegador.
Os leitores podem se perguntar quanto efeito isso pode ter. Afinal, a página é pequena, suas dependências são extremamente mínimas, e nosso projeto final do Node.js será executado em uma conexão wifi local. Deve ser uma configuração de baixa latência, certo?
Como se vê - pelo menos ao testar em smartphones mais antigos com Windows Phone 8.1 e Android 4.4.2 - o efeito é, infelizmente, bastante perceptível no caso comum de tocar rapidamente para aumentar ou diminuir o volume de reprodução em alguns pontos. Aqui é onde o JavaScript pode ajudar, sem tirar o nosso retorno gracioso de POST
s manuais via formulários HTML.
Neste ponto, nosso JavaScript cliente final (para ser colocado em public/javascript/group-client.js
) precisa ser compatível com navegadores móveis antigos e sem suporte. Mas não precisamos muito disso:
(function () { function form_submit(event) { var request = new XMLHttpRequest(); request.open('POST', window.location.pathname + window.location.search, true); request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); request.send('keystroke_name=' + encodeURIComponent(event.target.value)); event.preventDefault(); } window.addEventListener("DOMContentLoaded", function() { var inputs = document.querySelectorAll("input"); for (var i = 0; i < inputs.length; i++) { inputs[i].addEventListener("click", form_submit); } }); })();
Aqui, a função form_submit
apenas envia os dados por meio de uma chamada assíncrona, e a última linha impede o comportamento de envio normal dos navegadores, em que uma nova página é carregada com base na resposta do servidor. A segunda metade deste snippet simplesmente espera até que a página seja carregada e, em seguida, conecta todos os botões de envio para usar form_submit
. A coisa toda está embrulhada em um IIFE.
Toques finais
Há várias alterações nos snippets acima na versão final do código do tutorial de back-end do Node.js, principalmente para fins de melhor tratamento de erros:
- O back-end do Node.js agora verifica os nomes dos grupos e os pressionamentos de tecla enviados a ele para garantir que eles existam. Esse código está em uma função que é reutilizada para as funções
GET
ePOST
deroutes/group.js
. - Fazemos uso do modelo de
error
Pug se não o fizerem. - O JavaScript e CSS de front-end agora fazem botões temporariamente delineados em cinza enquanto aguardam uma resposta do servidor, verde assim que o sinal passa pelo
xdotool
e volta sem problemas, e vermelho se algo não funciona como esperado . - O back-end do Node.js imprimirá um rastreamento de pilha se ele morrer, o que será menos provável devido ao que foi dito acima.
Os leitores são bem-vindos para examinar (e/ou clonar) o projeto completo do Node.js no GitHub.
Tutorial de back-end do Node.js, Etapa 5: um teste do mundo real
É hora de experimentá-lo em um telefone real conectado à mesma rede wifi que o host que está executando o npm start
e um player de filme ou música. É apenas uma questão de apontar o navegador da web de um smartphone para o endereço IP local do host (com o sufixo :3000
), o que provavelmente é mais fácil de encontrar executando hostname -I | awk '{print $1}'
hostname -I | awk '{print $1}'
em um terminal no host.
Um problema que os usuários do Windows Phone 8.1 podem notar é que tentar navegar para algo como 192.168.2.5:3000
resultará em um pop-up de erro:
Felizmente, não há necessidade de desanimar: basta prefixar com http://
ou adicionar um /
à direita para buscar o endereço sem mais reclamações.
Escolher uma opção deve nos levar a um controle remoto funcional.
Para maior conveniência, os usuários podem querer ajustar as configurações de DHCP do roteador para sempre atribuir o mesmo endereço IP ao host e marcar a tela de seleção de layout e/ou qualquer layout favorito.
Solicitações de pull bem-vindas
É provável que nem todos gostem deste projeto exatamente como ele é. Aqui estão algumas ideias de melhorias, para aqueles que desejam se aprofundar no código:
- Deve ser simples ajustar os layouts ou adicionar novos para outros serviços, como o Disney Plus.
- Talvez alguns prefiram um layout de “modo de luz” e a opção de alternar entre eles.
- Sair da Netflix, uma vez que não é reversível, poderia realmente usar um "você tem certeza?" confirmação de algum tipo.
- O projeto certamente se beneficiaria do suporte do Windows.
- A documentação do
xdotool
menciona o OSX - esse projeto (ou poderia) funcionar em um Mac moderno? - Para descanso avançado, uma maneira de pesquisar e navegar pelos filmes, em vez de ter que escolher um único filme do Netflix/Amazon Prime Video ou criar uma lista de reprodução do YouTube no computador.
- Um conjunto de testes automatizado, caso alguma das alterações sugeridas quebre a funcionalidade original.
Espero que você tenha gostado deste tutorial de back-end do Node.js e uma experiência de mídia aprimorada como resultado. Boa transmissão — e codificação!