Cabin Fever Coding: Учебное пособие по серверной части Node.js
Опубликовано: 2022-03-11Блокировка COVID-19 заставила многих из нас застрять дома, возможно, надеясь, что простая лихорадка в салоне — это худший вид лихорадки, с которым мы столкнемся. Многие из нас потребляют больше видеоконтента, чем когда-либо прежде. Хотя физические упражнения сейчас особенно важны, иногда возникает ностальгия по роскоши хорошего старомодного пульта дистанционного управления, когда ноутбук находится вне пределов досягаемости.
Вот тут-то и появляется этот проект: возможность превратить любой смартфон — даже старый, который иначе бесполезен из-за отсутствия обновлений — в удобный пульт для следующего Netflix/YouTube/Amazon Prime Video/и т. д. выпивка смотреть. Это также учебник по серверной части Node.js: возможность изучить основы серверной части JavaScript с использованием среды Express и механизма шаблонов Pug (ранее Jade).
Если это звучит пугающе, в конце будет представлен полный проект Node.js; читатели должны узнать ровно столько, сколько им интересно, и по пути будет изрядное количество более мягких объяснений некоторых основ, которые более опытные читатели могут пропустить.
Почему бы не просто...?
Читатели могут задаться вопросом: «Зачем заниматься кодированием серверной части Node.js?» (Помимо возможности обучения, конечно.) «Разве для этого уже нет приложения?»
Конечно — их много. Но есть две основные причины, по которым это может быть нежелательно:
- Для тех, кто пытается перепрофилировать старый телефон, это может быть просто не вариант , как в случае с устройством Windows Phone 8.1, которое я хотел использовать. (Магазин приложений был официально закрыт в конце 2019 года.)
- Доверие (или его отсутствие). Как и многие приложения, которые можно найти на любой мобильной платформе, они часто требуют, чтобы пользователи предоставляли гораздо больше разрешений, чем требуется приложению для того, что оно должно делать. Но даже если этот аспект надлежащим образом ограничен, природа приложения для удаленного управления означает, что пользователи все равно должны быть уверены, что разработчики приложений не злоупотребляют своими привилегиями в отношении настольной части решения, включая шпионское или другое вредоносное ПО.
Эти проблемы существуют уже давно и даже послужили мотивом для аналогичного проекта 2014 года, найденного на GitHub. nvm
упрощает установку старых версий Node.js, и даже если несколько зависимостей требовали обновления, Node.js имел прекрасную репутацию благодаря обратной совместимости.
К сожалению, битрот победил. Упрямый подход и совместимость с серверной частью Node.js не могли сравниться с бесконечными устареваниями и невозможными петлями зависимостей среди старых версий Grunt, Bower и десятков других компонентов. Спустя несколько часов стало совершенно ясно, что начать с нуля будет намного проще, несмотря на собственный совет автора не изобретать велосипед.
Новые штуковины из старых: перепрофилирование телефонов в качестве пультов дистанционного управления с использованием серверной части Node.js
Прежде всего, обратите внимание, что этот проект Node.js в настоящее время специфичен для Linux — в частности, он разработан и протестирован для Linux Mint 19 и Linux Mint 19.3, — но, безусловно, может быть добавлена поддержка других платформ. Это может уже работать на Mac.
Предполагая, что установлена современная версия Node.js и командная строка открыта в новом каталоге, который будет служить корнем проекта, мы готовы начать работу с Express:
npx express-generator --view=pug
Примечание. Здесь npx
— это удобный инструмент, который поставляется с npm
, диспетчером пакетов Node.js, который поставляется с Node.js. Мы используем его для запуска генератора скелета приложения Express. На момент написания этой статьи генератор создает проект Express/Node.js, который по умолчанию по-прежнему использует механизм шаблонов под названием Jade, даже несмотря на то, что проект Jade переименовал себя в «Pug», начиная с версии 2.0. Таким образом, чтобы быть в курсе и сразу же использовать Pug — плюс избежать предупреждений об устаревании — мы --view=pug
, параметр командной строки для скрипта express-generator
, запускаемого npx
.
Как только это будет сделано, нам нужно установить некоторые пакеты из недавно заполненного списка зависимостей нашего проекта Node.js в package.json
. Традиционный способ сделать это — запустить npm i
( i
означает «установить»). Но некоторые по-прежнему предпочитают скорость Yarn, поэтому, если он у вас установлен, просто запустите yarn
без параметров.
В этом случае должно быть безопасно игнорировать (надеюсь, скоро будет исправлено) предупреждение об устаревании от одной из подзависимостей Pug, пока доступ сохраняется по мере необходимости в локальной сети.
Быстрый yarn start
yarn или npm start
с последующим переходом на localhost:3000
в браузере показывает, что наша базовая серверная часть Node.js на базе Express работает. Мы можем убить его с помощью Ctrl+C
.
Учебное пособие по серверной части Node.js, шаг 2. Как отправлять нажатия клавиш на хост-компьютере
Когда удаленная часть уже наполовину сделана, давайте обратим внимание на часть управления . Нам нужно что-то, что могло бы программно управлять машиной, на которой мы будем запускать нашу серверную часть Node.js, делая вид, что она нажимает клавиши на клавиатуре.
Для этого мы установим xdotool
используя его официальные инструкции. Быстрый тест их примера команды в терминале:
xdotool search "Mozilla Firefox" windowactivate --sync key --clearmodifiers ctrl+l
… должен делать именно то, что он говорит, предполагая, что Mozilla Firefox открыт в это время. Это хорошо! Наш проект Node.js легко заставить вызывать инструменты командной строки, такие как xdotool
, как мы скоро увидим.
Учебное пособие по серверной части Node.js, шаг 3: дизайн функций
Это может быть не так для всех, но лично я считаю, что многие современные физические пульты дистанционного управления имеют примерно в пять раз больше кнопок, чем я когда-либо использовал. Итак, для этого проекта мы рассмотрим полноэкранный макет с сеткой три на три красивых, больших, удобных для нажатия кнопок. Какими могут быть эти девять кнопок, зависит от личных предпочтений.
Оказывается, сочетания клавиш даже для самых простых функций не идентичны в Netflix, YouTube и Amazon Prime Video. Эти сервисы также не работают с общими мультимедийными ключами, как, вероятно, родное приложение музыкального проигрывателя. Кроме того, некоторые функции могут быть недоступны для всех служб.
Итак, что нам нужно сделать, это определить разные схемы удаленного управления для каждой службы и предоставить способ переключения между ними.
Определение макетов пульта дистанционного управления и их сопоставление с сочетаниями клавиш
Давайте быстро создадим прототип, работающий с несколькими пресетами. Мы поместим их в common/preset_commands.js
— «common», потому что мы будем включать эти данные из более чем одного файла:
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', }, }, };
Значения кодов клавиш можно найти с помощью xev
. (Для меня «отключение звука» и «воспроизведение звука» не были обнаружены с помощью этого метода, поэтому я также сверился со списком мультимедийных клавиш.)
Читатели могут заметить разницу в регистре между space
и xdotool
Return
правильно. В связи с этим у нас есть пара определений, написанных явно — например, shift+p
, хотя P
тоже будет работать — просто для того, чтобы наши намерения были ясными.
Учебное пособие по серверной части Node.js, шаг 4: «Ключевая» конечная точка нашего API (простите за каламбур)
Нам понадобится конечная точка для POST
, которая, в свою очередь, будет имитировать нажатия клавиш с помощью xdotool
. Поскольку у нас будут разные группы ключей, которые мы можем отправлять (по одной для каждой службы), мы будем вызывать конечную точку для конкретной group
. Мы переназначим сгенерированную конечную точку users
, переименовав routes/users.js
в route routes/group.js
и внеся соответствующие изменения в app.js
:
// ... var indexRouter = require('./routes/index'); var groupRouter = require('./routes/group'); // ... app.use('/', indexRouter); app.use('/group', groupRouter); // ...
Ключевая функциональность заключается в использовании xdotool
через вызов системной оболочки в routes/group.js
. На данный момент мы жестко закодируем YouTube
в качестве меню выбора, просто для целей тестирования.
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;
Здесь мы получаем запрошенный ключ «имя» из тела запроса POST
( req.body
) в параметре с именем keystroke_name
. Это будет что-то вроде ️
. Затем мы используем это для поиска соответствующего кода в объекте commands
preset_commands['YouTube']
.
Последняя команда находится более чем в одной строке, поэтому \
s в конце каждой строки объединяет все части в одну команду:
-
search "YouTube"
выводит первое окно со словом «YouTube» в заголовке. -
windowactivate --sync
активирует выбранное окно и ждет, пока оно не будет готово к приему нажатия клавиши. -
key --clearmodifiers ${keystroke_code}
отправляет нажатие клавиши, обязательно временно очищая клавиши-модификаторы, такие как Caps Lock, которые могут мешать тому, что мы отправляем.
На этом этапе код предполагает, что мы передаем ему допустимые входные данные — к этому мы будем более внимательны позже.
Для простоты в коде также будет предполагаться, что открыто только одно окно приложения с «YouTube» в заголовке — если есть более одного совпадения, нет гарантии, что мы отправим нажатия клавиш в нужное окно. Если это проблема, может помочь изменение заголовков окон простым переключением вкладок браузера во всех окнах, кроме того, которым нужно управлять удаленно.
С этим готовым мы можем снова запустить наш сервер, но на этот раз с включенной отладкой, чтобы мы могли видеть вывод наших вызовов debug
. Для этого просто запустите DEBUG=old-fashioned-remote:* yarn start
или DEBUG=old-fashioned-remote:* npm start
. Как только он запустится, воспроизведите видео на YouTube, откройте другое окно терминала и попробуйте вызов cURL:
curl --data "keystroke_name=️" http://localhost:3000/group
Это отправляет запрос POST
с запрошенным именем нажатия клавиши в его теле на нашу локальную машину через порт 3000
, порт, который прослушивает наш сервер. Запуск этой команды должен выводить заметки о Executing
и Executed
в окне npm
и, что более важно, запускать браузер и приостанавливать его видео. Повторное выполнение этой команды должно дать тот же результат и отменить паузу.
Учебное пособие по серверной части Node.js, шаг 5: несколько макетов удаленного управления
Наша задняя часть не совсем готова. Нам также потребуется, чтобы он мог:
- Создайте список макетов удаленного управления из
preset_commands
. - Создайте список «названий» клавиш после того, как мы выбрали конкретную раскладку пульта дистанционного управления. (Мы также могли бы использовать
common/preset_commands.js
непосредственно во внешнем интерфейсе, поскольку это уже JavaScript, и отфильтровать его там. Это одно из потенциальных преимуществ серверной части Node.js, мы просто не используем его здесь. .)
Обе эти функции — это место, где наше руководство по серверной части Node.js пересекается с интерфейсом на основе Pug, который мы будем создавать.
Использование шаблонов Pug для представления списка пультов дистанционного управления
Внутренняя часть уравнения означает изменение routes/index.js
, чтобы оно выглядело следующим образом:
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;
Здесь мы получаем имена наших макетов удаленного управления ( group_names
), вызывая Object.keys
в нашем файле preset_commands
. Затем мы отправляем их и некоторые другие данные, которые нам понадобятся, в механизм шаблонов Pug, который автоматически вызывается через res.render()
.
Будьте осторожны, чтобы не перепутать значение keys
здесь с нажатиями клавиш , которые мы отправляем: Object.keys
дает нам массив (упорядоченный список), содержащий все ключи пар ключ-значение , которые составляют объект в JavaScript:

const my_object = { 'a key' : 'its corresponding value' , 'another key' : 'its separate corresponding value' , };
Если мы посмотрим на common/preset_commands.js
, мы увидим приведенный выше шаблон, а наши ключи (в объектном смысле) — это имена наших групп: 'Netflix'
, 'YouTube'
и т. д. Их соответствующие значения не простые строки, как my_object
выше — они сами являются целыми объектами со своими собственными ключами, то есть commands
и, возможно, window_name_override
.
Передаваемый здесь пользовательский CSS, по общему признанию, является чем-то вроде хака. Причина, по которой он нам вообще нужен вместо использования современного решения на основе flexbox, заключается в лучшей совместимости с прекрасным миром мобильных браузеров в их еще более замечательных старых воплощениях. В этом случае главное отметить, что в ландшафтном режиме мы сохраняем кнопки большими, показывая не более двух вариантов на весь экран; в портретном режиме четыре.
Но где это на самом деле превращается в HTML для отправки в браузер? Вот тут-то и вступает в views/index.pug
, который должен выглядеть следующим образом:
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}
Самая первая строка важна: extends layout
означает, что Pug будет использовать этот файл в контексте views/layout.pug
, который является своего рода родительским шаблоном, который мы будем повторно использовать здесь, а также в другом представлении. Нам нужно добавить пару строк после строки link
, чтобы окончательный файл выглядел так:
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
Мы не будем вдаваться в основы HTML здесь, но для читателей, незнакомых с ними, этот код Pug отражает стандартный HTML-код, который можно найти практически везде. Аспект шаблонизации начинается с title= title
, который устанавливает для заголовка HTML любое значение, соответствующее ключу title
объекта, который мы передаем Pug через res.render
.
Мы можем увидеть другой аспект создания шаблона двумя строками позже с block
, который мы назвали header_injection
. Подобные блоки — это заполнители, которые можно заменить шаблонами, расширяющими текущий. (Независимо от этого, meta
— это просто быстрый обходной путь для мобильных браузеров, поэтому, когда пользователи нажимают на элементы управления громкостью несколько раз подряд, телефон не увеличивает или уменьшает масштаб.)
Вернемся к нашим block
: вот почему views/index.pug
определяет свои собственные block
с теми же именами, что и в views/layout.pug
. В случае с header_injection
это позволяет нам использовать CSS для портретной или альбомной ориентации телефона.
content
— это место, где мы помещаем основную видимую часть веб-страницы, в данном случае:
- Перебирает массив
group_names
, который мы передаем, - создает элемент
<span>
для каждого из них с примененным к нему классом CSSgroup_bar
, и - создает ссылку внутри каждого
<span>
на основеgroup_name
.
Класс CSS group_bar
, который мы можем определить в файле, полученном через views/layout.pug
, а именно, 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; }
На данный момент, если npm start
все еще запущен, при переходе по http://localhost:3000/
в настольном браузере должны отображаться две очень большие кнопки для Netflix и YouTube, а остальные доступны при прокрутке вниз.
Но если мы нажмем на них в этот момент, они не будут работать, потому что мы еще не определили маршрут, на который они ссылаются ( GET
ting для /group
.)
Отображение выбранного макета пульта дистанционного управления
Для этого мы добавим это в routes/group.js
непосредственно перед последней 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` }); });
Это позволит получить имя группы, отправленное в конечную точку (например, поместив ?group_name=Netflix
в конец /group/
), и использовать его для получения значения commands
из соответствующей группы. Это значение ( group.commands
) является объектом, а ключи этого объекта — это имена ( keystroke_names
), которые мы будем отображать в макете удаленного управления.
Примечание. Неопытным разработчикам не нужно вдаваться в подробности того, как это работает, но значение для title
использует немного регулярных выражений, чтобы превратить названия наших групп/макетов в аббревиатуры — например, наш пульт YouTube будет иметь название браузера. YT-Remote
. Таким образом, если мы проводим отладку на нашем хост-компьютере, прежде чем пробовать что-то на телефоне, у нас не будет xdotool
, захватившего само окно браузера удаленного управления, а не то, которым мы пытаемся управлять. Между тем, на наших телефонах заголовок будет красивым и коротким, если мы захотим добавить пульт дистанционного управления в закладки.
Как и в нашем предыдущем случае с res.render
, этот отправляет свои данные для смешивания с шаблоном views/group.pug
. Мы создадим этот файл и заполним его следующим образом:
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")
Как и в случае с views/index.pug
, мы переопределяем два блога из views/layout.pug
. На этот раз мы помещаем в заголовок не CSS, а некоторый клиентский JavaScript, к которому мы вскоре перейдем. (И да, в момент придирчивости я переименовал неправильно во множественном числе javascripts
…)
Основным content
здесь является HTML-форма, состоящая из множества разных кнопок отправки, по одной для каждого keystroke_name
. Каждая кнопка отправляет форму (делает запрос POST
), используя имя нажатия клавиши, которое она отображает в качестве значения, которое она отправляет с формой.
Нам также понадобится немного больше CSS в нашем основном файле таблицы стилей:
.remote_button { float: left; width: calc(100%/3); height: calc(100%/3); font-size: 12vh; }
Ранее, когда мы настроили конечную точку, мы закончили обработку запроса с помощью:
return res.redirect(req.originalUrl);
Фактически это означает, что когда браузер отправляет форму, серверная часть Node.js отвечает, говоря браузеру вернуться на страницу, с которой была отправлена форма, т. е. на основной макет удаленного управления. Без переключения страниц было бы элегантнее; однако мы хотим максимальной совместимости со странным и прекрасным миром дряхлых мобильных браузеров. Таким образом, даже без работы внешнего интерфейса JavaScript наш внутренний проект Node.js должен работать.
Немного внешнего интерфейса JavaScript
Недостатком использования формы для отправки нажатий клавиш является то, что браузер должен ждать, а затем выполнять дополнительный круговой обход: страница и ее зависимости должны быть затем запрошены из нашей серверной части Node.js и доставлены. Затем они должны быть снова отображены браузером.
Читатели могут задаться вопросом, какой эффект это может иметь. В конце концов, страница крошечная, ее зависимости чрезвычайно минимальны, и наш последний проект Node.js будет работать через локальное соединение Wi-Fi. Должна быть установка с малой задержкой, верно?
Как оказалось — по крайней мере, при тестировании на старых смартфонах под управлением Windows Phone 8.1 и Android 4.4.2 — эффект, к сожалению, довольно заметен в обычном случае быстрого нажатия, чтобы увеличить или уменьшить громкость воспроизведения на несколько делений. Вот где JavaScript может помочь, не отказываясь от нашего изящного запасного варианта ручного POST
через HTML-формы.
На данный момент наш окончательный клиентский JavaScript (который должен быть помещен в public/javascript/group-client.js
) должен быть совместим со старыми мобильными браузерами, которые больше не поддерживаются. Но нам этого много не надо:
(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); } }); })();
Здесь функция form_submit
просто отправляет данные через асинхронный вызов, а последняя строка предотвращает нормальное поведение браузеров при отправке, при котором новая страница загружается на основе ответа сервера. Вторая половина этого фрагмента просто ждет, пока страница загрузится, а затем подключает каждую кнопку отправки для использования form_submit
. Все это завернуто в IIFE.
Последние штрихи
В приведенные выше фрагменты кода в финальной версии нашего внутреннего учебного кода Node.js внесен ряд изменений, в основном для улучшения обработки ошибок:
- Серверная часть Node.js теперь проверяет имена групп и нажатия клавиш, отправленные ему, чтобы убедиться, что они существуют. Этот код находится в функции, которая повторно используется для функций
GET
иPOST
вroutes/group.js
. - Мы используем шаблон
error
Pug, если они этого не делают. - Внешний JavaScript и CSS теперь делают кнопки временно выделенными серым цветом в ожидании ответа от сервера, зеленым, как только сигнал прошел через
xdotool
и обратно без проблем, и красным, если что-то не работает должным образом. . - Серверная часть Node.js напечатает трассировку стека, если он умрет, что будет менее вероятно, учитывая вышеизложенное.
Читатели могут просмотреть (и/или клонировать) полный проект Node.js на GitHub.
Учебное пособие по серверной части Node.js, шаг 5: реальный тест
Пришло время попробовать это на реальном телефоне, подключенном к той же сети Wi-Fi, что и хост, на котором запущен npm start
и кино- или музыкальный проигрыватель. Это просто вопрос указания веб-браузеру смартфона локального IP-адреса хоста (с суффиксом :3000
), который, вероятно, проще всего найти, выполнив hostname -I | awk '{print $1}'
hostname -I | awk '{print $1}'
в терминале на хосте.
Одна проблема, которую могут заметить пользователи Windows Phone 8.1, заключается в том, что при попытке перейти к чему-то вроде 192.168.2.5:3000
появляется всплывающее окно с ошибкой:
К счастью, не нужно отчаиваться: просто префикс http://
или добавление завершающего /
позволяет получить адрес без дальнейших жалоб.
Выбор опции должен привести нас к работающему пульту дистанционного управления.
Для дополнительного удобства пользователи могут настроить параметры DHCP своего маршрутизатора, чтобы всегда назначать хосту один и тот же IP-адрес, и добавить в закладки экран выбора макета и/или любые избранные макеты.
Пулл-реквесты приветствуются
Вполне вероятно, что не всем этот проект понравится именно таким, какой он есть. Вот несколько идей по улучшению для тех, кто хочет углубиться в код:
- Должно быть легко настроить макеты или добавить новые для других сервисов, таких как Disney Plus.
- Возможно, некоторые предпочтут раскладку «облегченного режима» и возможность переключения между ними.
- Отказ от Netflix, поскольку он необратим, действительно может использовать вопрос «Вы уверены?» подтверждение какое-то.
- Проект, безусловно, выиграет от поддержки Windows.
- В документации
xdotool
упоминается OSX — работает ли этот (или может ли этот) проект на современном Mac? - Для расширенного безделья, способ поиска и просмотра фильмов вместо того, чтобы выбирать один фильм Netflix / Amazon Prime Video или создавать список воспроизведения YouTube на компьютере.
- Набор автоматизированных тестов на случай, если какие-либо из предложенных изменений нарушат исходную функциональность.
Я надеюсь, вам понравилось это руководство по серверной части Node.js, и в результате вы улучшили работу с мультимедиа. Удачной потоковой передачи и кодирования!