Конфигурация goaccess на nginx-сервере.

Итак, стоит задача настроить анализатор логов nginx-сервера goaccess таким образом, чтобы оно отображало статистику в реальном времени (для этого будем использовать веб-сокеты). Статистика должна быть доступна по адресу https://website1.example.com/goaccess

Далее – создать systemd-юниты для автоматизации процесса. Дополнительные задачи: настроить goaccess для второго сайта на том же сервере (https://website2.example.com/goaccess). Два процесса goaccess должны работать параллельно (для этого будем использовать встроенные опции именованных каналов межпроцессного взаимодействия, указание на путь хранения базы данных программой goaccess, а также укажем другой порт). Также установить ограничений по ip-адресам для доступа к панели статистики второго сайта (с помощью конфигурации nginx).

Предположим, что goaccess уже установлен. Нам нужно просматривать статистику по логам /var/log/nginx/website1/access.log по адресу https://website1.example.com/goaccess/main.html. HTML-страница со статистикой для https://website1.example.com будет храниться здесь: /var/www/goaccess/website1. Далее нам потребуется настроить nginx. Для этого в конфиге сайта (/etc/nginx/sites-available/website1) нужно добавить две локации: stats и ws:

location /stats/ {
alias /var/www/goaccess/website1/;
try_files $uri $uri/ =404;
}

вкратце, этот блок конфигурации Nginx настроен на обработку запросов, начинающихся с “/stats/”. Он сопоставляет эти запросы с локальным каталогом на сервере и пытается предоставить запрашиваемый файл или каталог, возвращаясь к ошибке 404, если таковой не существует.

Далее нужно настроить веб-сокет:

location /ws/ {

# порт не должен блокироваться фаерволом
proxy_pass http://localhost:7890/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}

Релоадим конфигурацию nginx:

sudo systemctl reload nginx

этот блок перенаправляет http-реквесты с https://website1.example.com/ws на http://localhost:7890 и устанавливает необходимые заголовки для работы с веб-сокетами. 

Запуск команды для проверки что все работает: 

goaccess /var/log/nginx/website1/access.log -o /var/www/goaccess/website1/main.html --real-time-html --port=7890 --log-format=COMBINED --ws-url=wss://eiensora.bernd32.xyz/ws/:443

Порт 443 предполагает, что у нас настроено https.

Заходим https://website1.example.com/goaccess/main.html и смотрим. Если все ок, то добавим systemd-юнит: 

vim /etc/systemd/system/goaccess_website1.service:

[Unit]
Description=GoAccess Live Log Analyzer for website1


[Service]
Type=simple
ExecStart=goaccess /var/log/nginx/website1/access.log \
-o /var/www/goaccess/website1/main.html \
--real-time-html \
--port=7890 \
--log-format=COMBINED \
--ws-url=wss://website1.example.com/ws/:443
ExecStop=/bin/kill ${MAINPID}
PrivateTmp=false
RestartSec=1800
User=root
Group=root
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start goaccess_website1
sudo systemctl enable goaccess_website1

Для одного сайта всё должно работать ок. Если нужно мониторить несколько разных логов (скажем, от разных сайтов, которые находятся на одном сервере) одновременно на разных адресах. То есть нам нужно запустить каким-то образом запустить два параллельных процесса goaccess. Сделать это можно, если

– запускать каждый процессор goaccess на разных портах –port.

– Различные каналы (FIFO) –fifo-in=/path/in.1 –fifo-out=/path/out.1.

– Если вы используете хранилище на диске, вам понадобится другой путь, по которому хранятся файлы БД –db-path=/path/instance1/.

Нужно изменить предыдущий юнит и добавить новые опции

–fifo-in=/tmp/es.in \
–fifo-out=/tmp/es.out \
–db-path=/var/lib/goaccess/

[Unit]
Description=GoAccess Live Log Analyzer for website1


[Service]
Type=simple
ExecStart=goaccess /var/log/nginx/website1/access.log \
-o /var/www/goaccess/website1/main.html \
--real-time-html \
--port=7890 \
--log-format=COMBINED \
--ws-url=wss://website1.example.com/ws/:443 \

--fifo-in=/tmp/site1.in \
--fifo-out=/tmp/site1.out \
--db-path=/var/lib/goaccess/website1
ExecStop=/bin/kill ${MAINPID}
PrivateTmp=false
RestartSec=1800
User=root
Group=root
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start goaccess_website1
sudo systemctl enable goaccess_website1

Значения fifo и db-path можно выбрать какие-угодно.

Далее, добавить второй юнит для статистики второго сайта, в котором будет другой порт, другое значение параметров –db-path, –fifo-in и –fifo-out:

vim /etc/systemd/system/goaccess_website2.service:

[Unit]
Description=GoAccess Live Log Analyzer for website2

[Service]
Type=simple
ExecStart=goaccess /var/log/nginx/website2/access.log \
-o /var/www/goaccess/website2/main.html \
--real-time-html --port=7891 \
--log-format=COMBINED \
--ws-url=wss://website1.example.com/ws/:443 \
--fifo-in=/tmp/site2.in \
--fifo-out=/tmp/site2.out \
--db-path=/var/lib/goaccess/website2/
ExecStop=/bin/kill ${MAINPID}
PrivateTmp=false
RestartSec=1800
User=root
Group=root
Restart=always

[Install]
WantedBy=multi-user.target

Также не забыть добавить в конфиг /etc/nginx/sites-available/website2:

location /stats/ {
alias /var/www/goaccess/website2/;
try_files $uri $uri/ =404;

#даем доступ к статистике только локальной подсетке и одному внешнему айпи
allow 44.51.220.3;
allow 192.168.1.0/24;
deny all;
}
location /ws/ {

# порт должен отличаться от созданного первого юнита
proxy_pass http://localhost:7891/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
sudo systemctl daemon-reload
sudo systemctl start goaccess_website2
sudo systemctl enable goaccess_website2

Теперь панели просмотра goaccess разных сайтов будут доступны одновременно по двум адресам:

https://website1.example.com/goaccess/main.html

https://website2.example.com/goaccess/main.html

How to upload a file directly from Dolphin file manager

How to upload an image file to 0x0.st file hosting:

Just create 0x0upload.desktop file with the following content:

[Desktop Entry] 
Type=Service 
MimeType=image/*; 
Actions=UploadTo0x0st; 
X-KDE-Priority=TopLevel 
X-KDE-StartupNotify=false 

[Desktop Action UploadTo0x0st] 
Icon=cloud-upload 
Name=Upload to 0x0.st 
Exec=konsole -e bash -c "curl -F'file=@%u' http://0x0.st; read -p 'Press Enter to close'"


Save the file in
/usr/share/kio/servicemenus, restart Dolphin (killall dolphin) and it’s done.

Result:

How to upload a file to remote server via scp:

Create a desktop file:

[Desktop Entry]
Type=Service
MimeType=all/allfiles;
Actions=UploadToVPS;
X-KDE-Priority=TopLevel
X-KDE-StartupNotify=true

[Desktop Action UploadToVPS]
Icon=cloud-upload
Name=Upload to VPS
Exec=konsole -e bash -c "scp -P <port> -i /home/user/.ssh/id_rsa '%u' user@<server-ip>:/home/user/
Uploads && echo 'File %u was uploaded to /home/user/Uploads' || echo 'Upload failed'; read -p 'Pre
ss Enter to close';"

 

Создание своего стримингового сервиса музыки Navidrome

Недавно мне в руки, почти бесплатно, попал вот такой почти что карманный бесшумный маленький терабайтный сервер, Lenovo ThinkCentre.

Думая, что с ним сделать такого интересного, решил, что неплохого было бы создать свой self-hosted аналог сервиса для потокового вещания музыки (что-то типа Spotify или Apple Music).  Первым делом я, конечно же, накатил Linux Debian в качестве ОС для будущего сервера.  В качестве бекенда для музыкального сервера выбор пал на проект Navidrome, из-за его простоты. Процесс установки Navidrome действительно настолько простой, насколько это вообще возможно.

Задача: создать на локальном сервере личный музыкальный стриминговый сервис по типу spotify, который был бы доступен как из локальной сети, так и из внешней, при этом учитывая то, что у нас динамический ip от провайдера.

План:  в качестве бекенда для стримингового сервиса будем использовать navidrome из-за его простоты и надежности. Установим navidrome на локальный сервер с debian linux, протестируем, что все нормально работает в локальной сети. Далее настраиваем возможность работы сервера за пределами локальной сети. Для этого будем использовать VPS с nginx в качестве реверс-прокси. Установим ssh-туннель с VPS и настроим nginx чтобы он обратно проксировал данные на наш локальный сервер с Navidrome.

Что потребуется: локальный сервер с linux-дистрибутивом, vps-сервер на linux со статическим ip-адресом, и, опционально, с доменом.

Реализация:

    ставим ffmpeg и удостоверяемся, что система обновлена:

    sudo apt update
    sudo apt upgrade
    sudo apt install ffmpeg

    создаем нужные директории:

    sudo install -d -o <юзер> -g <группа> /opt/navidrome
    sudo install -d -o <юзер> -g <группа> /var/lib/navidrome

    Заходим на страницу релизов (https://github.com/navidrome/navidrome/releases) navidrome и скачиваем и устанавливаем последнюю версию:

    wget https://github.com/navidrome/navidrome/releases/download/v0.XX.0/navidrome_0.XX.0_Linux_x86_64.tar.gz -O Navidrome.tar.gz
    sudo tar -xvzf Navidrome.tar.gz -C /opt/navidrome/
    sudo chown -R <юзер>:<группа> /opt/navidrome

    Создаем конфигурационный файл navidrome.toml в рабочей директории /var/lib/navidrome и вбиваем туда путь до директории музыкальной библиотеки, которая будет стримиться:

    MusicFolder = "/home/MusicLib"

    Создаем юнит systemd. Для этого создаем файл navidrome.service в директории /etc/systemd/system/с таким содержанием:

    [Unit]
    Description=Navidrome Music Server and Streamer compatible with Subsonic/Airsonic
    After=remote-fs.target network.target
    AssertPathExists=/var/lib/navidrome
    
    [Install]
    WantedBy=multi-user.target
    
    [Service]
    User=<user>
    Group=<group>
    Type=simple
    ExecStart=/opt/navidrome/navidrome --configfile "/var/lib/navidrome/navidrome.toml"
    WorkingDirectory=/var/lib/navidrome
    TimeoutStopSec=20
    KillMode=process
    Restart=on-failure
    
    # See https://www.freedesktop.org/software/systemd/man/systemd.exec.html
    DevicePolicy=closed
    NoNewPrivileges=yes
    PrivateTmp=yes
    PrivateUsers=yes
    ProtectControlGroups=yes
    ProtectKernelModules=yes
    ProtectKernelTunables=yes
    RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
    RestrictNamespaces=yes
    RestrictRealtime=yes
    SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap
    ReadWritePaths=/var/lib/navidrome

     

    Запускаем только что созданный сервис:

    sudo systemctl daemon-reload
    sudo systemctl start navidrome.service
    sudo systemctl status navidrome.service
    
    sudo systemctl enable navidrome.service

     

    Заходим на http://localhost:4533 и проверяем, что все нормально работает. Если с сервера зайти посмотреть не удается, то это можно сделать с другого компьютера, если он в одной локальной сети, но localhost нужно будет поменять локальный ip сервера.

    Готово! Теперь у нас есть собственный стриминговый сервис with blackjack and hookers, но есть одно большое НО: работать все будет только в рамках локальной сети. Итак, теперь наша задача сделать так, чтобы сервис был доступен из Интернета. Сразу скажу, что у меня динамический IP и исходить я буду из этого. Вариантов решения этой задачи несколько, но учитывая то, что у меня есть работающая VPS-ка с веб-сервером nginx, статическим IP и доменом, то самым идеальным вариантом для меня стало бы пробросить порты посредством ssh, создав безопасный туннель передачи данных между удаленным VPS и нашим локальным сервером. Дальше нужно будет настроить nginx на удаленном vps чтобы он обратно проксировал данные на наш локальный сервер с Navidrome. Отмечу, что это самый секьюрный вариант, а так же самый удобный в пользовании: в конечном итоге нам остается только зайти на наш домен (например, music.example.com) и пользоваться, с поддержкой SSL. Если нет VPS-ки, то этот вариант будет не самым быстрым. Отмечу вкратце, что нужно сделать:

    1. Приобретаем VPS-ку.
    2. Приобретаем домен.
    3. Настраиваем nginx и ssh доступ через ключ
    4. Регистрируемся в Cloudflare и привязываем туда домен.
    5. Получаем бесплатный SSL-сертификат

    Как все это сделать можно посмотреть в других гайдах (например, здесь).

    Итак, если VPS с настроенным nginx есть, то можем продолжать. В качестве примера будут следующие вводные параметры (взятые “от балды”):

    Внешний IP-адрес VPS: 237.37.96.248

    Порт ssh: 44633

    Домен (а так же URL), на котором будет доступен Navidrome: music.mydomain.com

    Итак, устанавливаем на локальном сервере с Navidrome ssh-туннель из порта 16059 на нашем vps до порта 4533 локального сервера:

     ssh -p 44633 -R 16059:localhost:4533 user@237.37.96.248 -N

    Создаем отдельный конфигурационный файл для music.mydomain.com в nginx такого содержания:

    server {
    listen 80;
    server_name music.mydomain.com;
    location / {
    proxy_pass http://localhost:16059;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    }
    }

    Здесь мы говорим nginx принимать запросы по адресу music.mydomain.com и переводить их на порт 16059 локалхоста нашего vps, который в свою очередь через туннель передается на наш локальный сервер с установленным Navidrome.

    Теперь, если мы зайдем по адресу music.mydomain.com, то все должно работать. Остался добавить небольшой штрих – подключить ssl-сертификаты. Я предпочитаю это делать через удобнейшую утилиту certbot:

    certbot --nginx -d music.mydomain.com

    Итоговый результат будет выглядеть как-то так на компьютере:

    Navidrome

    На смартфоне можно использовать любое приложение поддерживающее Subsonic API, я предпочитаю Ultrasonic.

    Есть еще один важный момент. Дело в том, что ssh-туннель, установленный выше способом, очень просто, но в то же время неудобен: соединение будет время от времени падать, и нужно будет переподключиться самостоятельно. Можно решить эту проблему несколькими способами (например утилитой autossh), но на мой взгляд лучше всего создать службу systemd, которая будет всегда поддерживать туннельное соединение. Способ авторизации в данном случае только через ключи безопасности.

    Для этого нужно создать файл службы

    sudo vim /etc/systemd/system/ssh-tunnel-persistent.service
    Примерно такого содержания:
    [Unit]
    Description=Persistent SSH Tunnel to from port 9092 on this server to port 9090 on external server (for encrypted traffic)
    After=network.target
    
    [Service]
    Restart=on-failure
    RestartSec=5
    ExecStart=/usr/bin/ssh -i <путь/до/приватного/ключа/ssh> -p 44633-R 16059:localhost:4533 user@237.37.96.248 -NTC -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes
    
    [Install]
    WantedBy=multi-user.target
    Данная конфигурация службы systemd используется для настройки постоянного SSH-туннеля. Этот туннель предназначен для передачи зашифрованного трафика и обеспечивает сохранение активности туннеля и его автоматический перезапуск в случае сбоя. Далее, нужно ввести в терминале:
    sudo /usr/bin/ssh -i <путь/до/приватного/ключа/ssh> -p 44633-R 16059:localhost:4533 user@237.37.96.248 -NTC -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes
    После этого подключение можно будет сразу прервать. Дело в том, что SSH хранит известные ключи хоста и связанные с ними отпечатки пальцев в конфигурационном файле для каждого пользователя, а так как эта служба systemd выполняет команду из-под пользователя root, то у системы для него нет записей в known_hosts.
    Осталось лишь включить и запустить созданную службу:
    sudo systemctl daemon-reload
    sudo systemctl enable ssh-tunnel-persistent.service
    sudo systemctl start ssh-tunnel-persistent.service

    Zankyou no Terror

    Zankyou no Terror is a glaring example of an anime gone wrong. The plot is mediocre, pretentious, and at times, so ridiculous that it’s unintentionally humorous. It embodies the essence of cringe and juvenile pathos in a manner that is hard to digest.

    While the graphical execution is commendable, offering stellar animations and visuals, the OST is awful (at least to my taste).

    The story and its characters don’t warrant any deep analysis, much like assessing a child’s gibberish (which it is). Injecting mythological references and ridiculous attempts to make the plot look “deep” – all this leaves only an impression of puzzlement and Spanish shame.

    By comparison, Code Geass, another show designed for the edgy schoolboy demographic, reigns supreme. It manages to sustain a level of storytelling elements that Zankyou no Terror lacks sorely.

    One might speculate that the creators intended to pander to Western audiences in a bid for popularity or increased viewership. Unfortunately, their efforts fell dreadfully short.

    Knights of Sidonia

    Здесь есть хороший задел для увлекательного сюжета, но в итоге получилось как-то скучновато. Здесь очень крутой сеттинг – гигантский космический корабль с говорящим названием Сидония (здесь очевидная отсылка на Библейские мифы про Сидон), красивый город в стиле стим-кибер-панка. Персонажи немного уродливы внешне (из-за низкобюджетной 3D анимации с закосом под 2D-арт), к тому же они слабо раскрыты (кроме ГГ). Главный герой – типичная зажатая стеснительная тряпка и спаситель всего человечества в одном лице – от таких клише тошнит больше всего. Такие главные герои не вызывают никакого сочувствия и эмпатии во время просмотра, во всяком случае у меня. Впрочем, остальные персонажи не лучше, в том смысле, что всех их можно охарактеризовать одним словом – серость.
    По сюжету тоже очень много вопросов есть. Сценаристам можно было бы придумать разного рода кулстори, например, с объяснениями, что за существа такие эти “гауны” и почему они стремятся уничтожить “человеков”, каким образом они напали на Землю, и, самое интересное – почему из всего человечества спаслись только японцы? Либо может есть еще какие-то гигантские космические корабли помимо Сидонии, бороздящие бескрайние просторы Вселенной? Раскрытие подобных вопросов увеличила бы интерес от просмотра в разы, но увы – большая часть сюжета – это драки больших антропоморфных роботов с НЁХ (с гаунами т.е.). Аниме это сделано по одноименной манге, в которой 15 томов. Полагаю, что в этих 15 томах есть ответы на хотя бы часть вопросов и что персонажи там раскрыты в полной мере. Но аниме само по себе получилось скучноватым.

    Shingeki no Kyojin

    В общем-то, понятно, почему это аниме стало самым популярным в мире, затмив собой даже легендарную Тетрадку Смерти. Визуальная составляющая здесь и правда впечатляет, анимация и графон здесь объективно хороши, хоть и много бездушной 3DCG.
    Тайтл можно оценивать с разных сторон: с одних он будет шедевром, с других – пустышка ни о чем. Если оценивать с точки зрения популярности и финансового успеха, то это действительно шедевр. Создателям удалось создать качественный продукт, который зашел большинству подростков и детей в мире, тем самым принеся тонны нефти студии. Да, это аниме нацелено исключительно на детскую и подростковую аудиторию, падких на красивую обертку и дешевый развод на эмоции.
    Если же оценивать с моей личной точки зрения, то Титаны – ни о чем. Буквально.
    Сюжет… ну, его практически тут нет. Поэтому и обсуждать тут нечего. Сюжет всех 25 серий можно описать в двух коротких предложениях. 95% экранного времени представляют собой драки с титанами, истошные крики, оры, кривляния, кровь, кишки, и прочие дешевые попытки вызвать у зрителя эмоции на пустом месте. Сюжетная концепция (спасти человечество от НЁХ) тоже не блещет оригинальностью, мягко сказать. Про тупость и нелогичность в некоторых моментах даже говорить не хочется, для подростков тут никто и не старался сделать происходящее логичным – и так сойдет. Сюжетной концовки тут просто нет, НИЧЕГО не объяснено, более-менее раскрыто только два персонажа. Да, это он самый – очевидный байт на просмотр последующих сиквелов. Справедливости ради стоит отметить, что несмотря на все вышесказанное, смотрятся серии довольно легко, без желания немедленно дропнуть.
    Кстати, о персонажах. Никакого выдающегося, интересного и хоть чуточку уникального персонажа тут нет. Все они неплохи, но шаблонны донельзя. Просто не интересны. Обсуждать их не хочется, ведь все это видено уже сотни раз.
    Что можно еще добавить? Очень качественный коммерческий тайтл, в который вложена куча денег и ноль души, ноль идеи, ноль оригинальности, ноль смысла, с очень дешевыми попытками развести зрителя на эмоции. Возможно, если бы я смотрел Титанов лет в 12, то они бы мне зашли. Но после 20-ти такие пустышки в красивой обертке уже не впечатляют, увы.

    3-gatsu no Lion

    Начал смотреть этот тайтл из-за сёги – игры, которая мне интересна. Тайтл показался несколько противоречивым: есть минусы и плюсы.
    Из минусов в сюжете – абсолютно ничем не объяснимая приторная доброта семьи Акари по отношению к ГГ – немного раздражала своей неестественностью. Ладно бы был какой-то один добрый поступок с их стороны. Ладно бы если ГГ был хотя бы отдаленным родственником их семьи, но нет. Акари просто подобрала нашего главного школьника на улице пьяным (лолшто?) и с тех пор она, все ее сестры и даже их дед относились к нему в миллиард раз лучше, чем если бы это их был самый близкий родственник. Авторы, видимо поняв, что это какая-то неправдоподобная лажа, вскользь объясняют такой поступок тем, что Акари “любит спасать бездомных кошек” (лолшто??), а через несколько серий спустя объяснение Акари дополняется – “потому что там было бы одиноко без тебя” (лолшто???). Спрашивается – а почему эта добренькая Акари выбрала именно нашего ГГ? В мире, а особенно в Японии, десятки миллионов одиноких и несчастных. В общем, да, бредятина еще та, что тут обсуждать. Вообще, все эти три персонажа (Хину, Акари и Момо) выглядят лишними в сюжете – убери их и ничего бы не поменялось. Эпизоды с ними приходилось даже скипать (не могу слушать 10-минутные бессмысленные скороговорки про еду, извините).
    Из плюсов – красивое поэтичное повествование от главного героя. Интересная сюжетная линия с Симадой (побольше бы такого, но увы). Ну и так по мелочи.
    Второй сезон обязательно посмотрю, тем более что там есть намек на раскрытие загадочного персонажа – сёгиста Сои.

    6/10

    Generating random words in JS

    Russian version is here.

    Let’s say we had an idea to create a JS script that would generate random words (nicknames).

    Let’s start with the simplest approach first. If we just generate random letters and make words using these letters, they will look unnatural and unsightly. Here are some examples:

    • srjxdq
    • moyssj
    • ywtckmw
    • wjvzw
    • xtwey

    etc.

    As you can see, this approach does not allow us to generate words that even remotely resemble natural ones – at the output, we simply generate a set of meaningless letters that looks more like passwords than words. Here are two points how to make the generated random words look more natural (in my opinion):

    1. Avoid occurrences of more than two vowels/consonants when generating a word. This problem is trivial and I will not consider it.
    2. Pick random letters for a word based on their weight. Weights in this case will be the frequency of letters in English. This way we have to reduce/increase the chance that a certain letter will end up in our generated word, and rarely used letters such as Q, Z and X will appear in our words much less often than E, T, A, O , I, which are statistically the most frequent in English words.

    Using just these two approaches, we generate much more “natural” words. Here are some examples:

    screenshot_0.png

    Now it’s much better. Let’s analyze the 2nd point in more detail.

    Algorithm for selecting random array elements based on weights in JS

    A relatively simple implementation of such an algorithm is the transformation of a series of rational numbers s1 (array), which are weights for elements, into a series of numbers s2, which is obtained by cumulative addition of numbers:

    equation

    const items = [ 'a', 'b', 'c' ]; 
    const weights = [ 3, 7, 1 ];

    Prepare an array of weights via cumulative addition (i.e. a cumulativeWeights list that will have the same number of elements as the original weights list of weights). In our case, such an array will look like this:

    cumulativeWeights = [3, 3 + 7, 3 + 7 + 1] = [3, 10, 11]

    We now generate a randomNumber from 0 to the highest cumulative weight value. In our case, the random number will be in the range [0..11]. Let’s say randomNumber = 8.

    Loop through the cumulativeWeights array from left to right and select the first element that is greater than or equal to randomNumber. We will use the index of such an element to select an element from an array of elements.

    The idea behind this approach is that higher weights will “occupy” more numerical space. Therefore, there is a higher probability that the random number will end up in the “number bucket” with a higher weight.

    I’ll try to demonstrate this using the example of my script:

    const weights = [3, 7, 1 ]; 
    const cumulativeWeights = [3, 10, 11]; 
    // In a pseudo-view, we can represent cumulativeWeights like this:
    const pseudoCumulativeWeights = [ 1, 2, 3, // <-- [3] numbers
    4, 5, 6, 7, 8, 9, 10, // <-- [7] numbers
    11, // <-- [1] number 
    ];

    As you can see, heavier weights occupy a higher numerical space and therefore have a higher chance of being randomly selected. The percentage of selection chance for the weights elements will be as follows:

    Element 3: ≈ 27%,

    Element 7: ≈ 64%,

    Element 1: ≈ 9%

    In general, the function looks something like this:

    function weightedRandom(items, weights) {
    if (items.length !== weights.length) {
    throw new Error('Arrays of elements and weights must be the same size');
    }
    
    if (!items.length) {
    throw new Error('Array elements must not be empty');
    }
    const cumulativeWeights = [];
    for (let i = 0; i < weights.length; i += 1) {
    cumulativeWeights[i] = weights[i] + (cumulativeWeights[i - 1] || 0);
    }
    
    const maxCumulativeWeight = cumulativeWeights[cumulativeWeights.length - 1];
    
    const randomNumber = maxCumulativeWeight * Math.random();
    
    for (let itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
    if (cumulativeWeights[itemIndex] >= randomNumber) {
    return items[itemIndex];
    }
    }
    }

    How can the word generation algorithm be even better?

    This script is more of an example of using an algorithm for selecting a random element of an array based on their weight, so I did not go deep into linguistics and artificial intelligence algorithms. But offhand, unsightly combinations of some vowel and consonant pairs that do not occur in real words and looks unnatural. Examples:

    • satlenl
    • tohhi
    • tiowh
    • aahepw

    etc.

    The simplest solution to this issue is to limit the alternation of more than two vowels / consonants in a row:

    if (vowelCounter >= maxVowelsInRow) { i -= 1; continue;}

    and

    if (consonantCounter >= maxConsonantsInRow) { i -= 1; continue;}

    Let the values maxConsonantsInRow = 1 and maxVowelsInRow = 1, then the generated words will look something like this:

    screenshot_1.png

    Note here that “th” and “ae” are digrams, and count as one letter.

    The obvious disadvantage of this approach is that the generated words are more of the same type and with much less variative potential. Therefore, in this problem there is a huge scope for improving the algorithm.

    The full version of the script can be found here: https://github.com/bernd32/nickname-generator