Технические заметки
19 subscribers
57 photos
15 videos
30 links
Здесь я делюсь техническим опытом, который приобретаю каждый день на работе и дома.

Сетка: https://set.ki/Nheuhoc
LinkedIn: linkedin.com/in/magrega
Download Telegram
This media is not supported in your browser
VIEW IN TELEGRAM
Пользуюсь ИИ-чатом на Arena AI.

Очень нравится как визуально выполнена кнопка копирования.

Вроде бы ничего особенного, но именно это сочетание цветов, переходящих друг в друга, скорость и вид анимации вместе создают очень плавное ощущение. Хочется постоянно нажимать на эту кнопку.

#frontend #webdev #ux
🔥2👍1👏1
Какая математическая вероятность выпадения этого кода?

#telegram
🔥2
Сейчас будет задротская тема.

Я питаю определенную любовь к нишевым инди-играм. В них зачастую есть та душа творчества, которая сегодня уже полностью вымылась системой подписок и лутбоксов от ААА-компаний. Одна из таких игр The Textorcist: The Story of Ray Bibbia.

Прикольный буллет-хелл, который завязан на том, чтобы печатать слова на клавиатуре для нанесения атак по врагу. Очень сильно точит слепую печать, а еще вызывает взрывы пукана невероятной мощности.

Короч. Суть в чем, я хотел как-то переустановить ОС (бог пойми зачем опять 🤦🏻‍♂️), но прежде, чем это делать, решил проверить, что сейвы с этой игры перенесутся, так как я очень не хотел терять прогресс, доставшийся мне неоправданно тяжелым для видеоигры трудом. Накатил игру на ноуте. Запустил. Steam Cloud нет, перетащил папку сейвов с компа на ноут. На старте игры сейвов нет, закрываю игру, смотрю сейвы - они потерты, созданы новые.

Простите, что?


Может быть я дурак, копирую опять. Смотрю, файлы изменились только что, захожу опять, на запуске файлы трутся на новые.

ПРОСТИТЕ, ЧТО?

Чтобы я не делал, игра при запуске затирает мои сейвы и создает свои. Первым делом я пошел на форумы стима, пообщался с ребятами, никто ничего толком не сказал. Нашел тред с разрабами, написал им. Игнор. Прошел год. Написал им на почту. Прошел год. На форуме стиме тоже никаких комментов на тему. Оставил негативный отзыв. Пошел ковырять файлы. Обнаружил, что содержимое файлов сохранений выглядит интересно:

f27624b7b4c469c2d33b38c6bb833e4ab9b93dc0ef4073705b26c98017d32a5cc4c31c74be1a924eeyAiaWQxMGJldGEiOiAtMS4wLCAic2NvcmU1IjogOTg3MzQ1LjAsICJzY29yZTViZXRhIjogMC4wLCAic2NvcmU5YmV0YSI6IDAuMCwgImlkZGxjMSI6IC0xLjAsICJzY29yZXJ1c2giOiAwLjAsICJpZDdiZXRhIjogLTEuMCwgInNjb3JlZGxjMiI6IDAuMCwgInNjb3JlMmJldGEiOiAwLjAsICJzY29yZTEiOiA1OTE1ODcuMCwgImlkZGxjMyI6IC0xLjAsICJzY29yZTciOiAxNDU4Nzk5LjAsICJzY29yZTkiOiAxNjg1MDczLjAsICJzY29yZTMiOiAxMDUxNjE0LjAsICJpZGJldGEiOiAtMS4wLCAic2NvcmVrcmFtcHVzIjogMC4wLCAiaWR0ZXN0IjogLTEuMCwgImlkNCI6IDEwMDk2My4wLCAiaWQyIjogMjkwOTM4LjAsICJpZDJiZXRhIjogLTEuMCwgImlkcnVzaGJldGEiOiAtMS4wLCAic2NvcmU3YmV0YSI6IDAuMCwgImlkNWJldGEiOiAtMS4wLCAiaWQ5YmV0YSI6IC0xLjAsICJpZDYiOiA0MTU5MS4wLCAiaWQ4IjogMjMzNDguMCwgInNjb3JlZGxjMyI6IDAuMCwgInNjb3JlcnVzaGJldGEiOiAwLjAsICJpZDEwIjogMTMyMzkuMCwgInVzZXJuYW1lIjogIkFJVyIsICJzY29yZTQiOiAxNzA2MjkyLjAsICJzY29yZTEwIjogNTY5NTM2LjAsICJpZDZiZXRhIjogLTEuMCwgInNjb3JlM2JldGEiOiAwLjAsICJzY29yZTIiOiAxMDk5NjMyLjAsICJzY29yZWRsYzEiOiAwLjAsICJpZCI6IDM3NjkzNy4wLCAiaWRydXNoIjogLTEuMCwgInNjb3JlNGJldGEiOiAwLjAsICJpZGRsYzIiOiAtMS4wLCAic2NvcmU4YmV0YSI6IDAuMCwgInNjb3JlOCI6IDEzMTI5MjUuMCwgInNjb3JlMTBiZXRhIjogMC4wLCAic2NvcmU2IjogMjIwMDQ3MC4wLCAiaWQ1IjogNjI0MjguMCwgImlkNGJldGEiOiAtMS4wLCAiaWQ4YmV0YSI6IC0xLjAsICJzY29yZTFiZXRhIjogMC4wLCAic2NvcmVoYyI6IDAuMCwgImlkM2JldGEiOiAtMS4wLCAic2NvcmU2YmV0YSI6IDAuMCwgImlkOSI6IDE0NDIxLjAsICJpZDciOiAyNzA4NS4wLCAic2NvcmV0ZXN0IjogMTA1MTYxNC4wLCAiaWQzIjogMTc1ODg2LjAgfQA=


У меня была мысль, что это какая-то кодировка, но на тот момент я не сообразил, потому пошел искать форумы в инете, где люди занимаются взломом игровых процессов и прочего и набрел сюда.
🤯2
В общем, мне подсказали, что это base64. И после расшифровки данные выглядят так:
nۆo86w}:m7 o4y4~彺s4׷wٮ\s7mZn { "id10beta": -1.0, "score5": 987345.0, "score5beta": 0.0, "score9beta": 0.0, "iddlc1": -1.0, "scorerush": 0.0, "id7beta": -1.0, "scoredlc2": 0.0, "score2beta": 0.0, "score1": 591587.0, "iddlc3": -1.0, "score7": 1458799.0, "score9": 1685073.0, "score3": 1051614.0, "idbeta": -1.0, "scorekrampus": 0.0, "idtest": -1.0, "id4": 100963.0, "id2": 290938.0, "id2beta": -1.0, "idrushbeta": -1.0, "score7beta": 0.0, "id5beta": -1.0, "id9beta": -1.0, "id6": 41591.0, "id8": 23348.0, "scoredlc3": 0.0, "scorerushbeta": 0.0, "id10": 13239.0, "username": "AIW", "score4": 1706292.0, "score10": 569536.0, "id6beta": -1.0, "score3beta": 0.0, "score2": 1099632.0, "scoredlc1": 0.0, "id": 376937.0, "idrush": -1.0, "score4beta": 0.0, "iddlc2": -1.0, "score8beta": 0.0, "score8": 1312925.0, "score10beta": 0.0, "score6": 2200470.0, "id5": 62428.0, "id4beta": -1.0, "id8beta": -1.0, "score1beta": 0.0, "scorehc": 0.0, "id3beta": -1.0, "score6beta": 0.0, "id9": 14421.0, "id7": 27085.0, "scoretest": 1051614.0, "id3": 175886.0 }�

Как мы видим, это таблица рекордов из моего сохранения. Логика такая:
В начале всей строки есть рандомный кусочек, назовем его ID — nۆo86w}:m7 o4y4~彺s4׷wٮ\s7mZn . Затем идет JSON с данными сохранения, в данном случае количество очков. Все это шифруется в base64.

На старте игры проверяется ID. Если он совпадает - игра загружает сейвы, если нет, то всё трётся, делаются новые файлы. Таким образом мы можем взять старые сейвы, в них после расшифровки заменить ID на новый и добавить в игру, не теряя, таким образом, свой прогресс.

Понравилось ли мне тратить время на то, чтобы в этом разобраться? Безусловно. Хотел ли бы я этим заняться опять? Конечно, нет.

Сказать честно, я не понял для чего разрабы так запарились и не позволили просто менять файлы сейвов, как в любой другой человеческой игре, но было прикольно узнать, что такое бывает. Я даже запилил гайд в стим.

#steam #textorcist #guide #base64
👍1🔥1😁1
Был у меня давным-давно красивый инстаграм. Я в него заливал только самые эстетичные, на мой взгляд, фотки. Вел я его довольно давно, лет с 20, там была целая хронология становления моей персоны. К сожалению, с наступлением 2022 года то ли активизировались боты в инсте, то ли еще какой-то блудняк произошел, и в инсте стали блочить аккаунты, особенно с РФ, направо и налево.

Мой аккаунт не стал исключением. Я отправил, наверное, десяток фоток своей хари с листиком с написанным от руки 6-значным кодом, чтобы меня пустили в мой же аккаунт. Всё было бестолку и в течение года я наблюдал за своим аккаунтом со стороны учеток друзей, неспособный дотронуться до того, что было раньше моим...

По истечению года мой аккаунт окончательно удалили с серверов. Я зарегался под своей почтой, будто в первый раз, но больше никогда туда ничего не постил.

Потому я решил, что буду, может, по воскресеньям какие-то фотки тут выкидывать, чтобы показать миру как классно я живу.

#instagram
4👏2🥰1
К лучшему или нет, но зима заканчивается, а рабочие обороты увеличиваются. Времени на посты становится меньше, но я приготовил кое-что прикольное.

Я купил себе как-то часы Mi Band 8 на смену своим пожилым Mi Band 2, доставшимся мне от жены. Хотелось просто добавить цвета с кастомными циферблатами к подсчету шагов и калорий. На них нашелся циферблат, который мне невероятно сильно понравился. От картинки чувствовался такой осенний, немного хеллоуинский вайб с примесью какой-то диснеевской сказки. Но был у них баг один, дни недели отставали на один. В понедельник показывали воскресенье.

У восьмой версии часов оказался какой-то совершенно непонятный алгоритм счета калорий, который насчитывал мне по сожженных 400 ккал пока я просто сижу за компом. Решил вернуть. Взял 7 версию, на которой старый алгоритм подсчета, меня это устроило, но того циферблата на них не было. Ну что ж, полез курить форумы.

Накурился и узнал такую вещь:
Файлы циферблатов идут в формате .bin, но по сути они открываются обычным ZIP-архиватором. А внутри...
🎉 JavaScript 🎉

Вы не ослышались, ребята! Внутри .json-файлы манифестов, куча ассетов в видео картинок и javascript-код.

Потому, смачно похрустев пальцами, я полез исправлять циферблат и пересобирать его для 7 версии Mi Band. Обратно всё собирается программой ZMake, точнее, адаптируются картинки под ZeppOS.

Такие вот дела.

#javascript #zeppos #miband
🔥4
Про баланс работа-жизнь.

В 23 года мне ударила в голову идея, что мне кровь из носу нужно заработать гору бабок. В момент, когда мне пришло это осознание, я устроился на вторую работу и еще, когда успевал, на Авито чинил компы и чистил аквариумы.

Все последующие 10 лет, в ходе которых трансформировался мой профессиональный путь, я смотрел на жизнь, по большей части, через призму работы и количества зарабатываемых денег.

Так случилось, во-первых, потому что кроме работы я ничем не занимался, а, во-вторых, потому что интенсивность такой рабочей жизни свела на нет почти всё беззаботное дружеское взаимодействие и стало своеобразным фильтром общения, оставив меня наедине с моими амбициями.

К 30 годам я сделал что хотел, но очень быстро понял, что самый главный урок, который был упущен, заключался в том, что главное - это не конечная цель, а путешествие к ней. Положив весь свой праймтайм на работу, я только последние пару лет стал обращать внимание на смену сезонов. На то, как приятно чувствовать ослабевающую хватку зимы, и на то, с каким ожиданием я жду пожелтение листвы осенью. В 2024 году я ездил в Выборг посмотреть на осенний Монрепо, и мне очень жаль, что я не замечал всего этого раньше.

Всё хорошо в меру, ребятки.

#personal
2
Я придумал аудиосообщения в телеграм

Когда я учился в 7-8 классе школы, я оооочень активно переписывался смсками. Тогда у Теле2 был классный тариф, в котором смски стоили то ли 5, то ли 10 копеек, что позволяло продолжительно чатиться.

Одни вечером я гостил у соседа, общался и пил чай. Телефон без конца вибрировал сообщениями, отвечая на которые можно было бы потратить кучу времени (все помнят клавиатуры с Т9?).

И мне тогда пришла в голову мысль, которую я озвучил вслух. Мне показалось, что было бы классно иметь возможность записать голос и отправить его как сообщение. Сосед посмотрел на меня как дебила и сказал: "Так просто позвони..."

И где мы сейчас находимся? 😲
Не знаю, как Паша выудил у меня из ума эту мысль, но жаль, что я не запатентовал ее тогда.

А лет 5 назад, кстати, я подумал о том, как меня достали проводные мышки, клавы и наушники. Как было бы круто сделать все беспроводным, но при этом не иметь необходимости это всё заряжать. Например, мышки и клавиатуры могли бы заряжаться от коврика (типа MagSafe). То есть, пока периферия на коврике - она заряжается, не надо пихать в нее никакие провода. В любой момент можно ее взять с коврика и работать уже от аккумулятора.

Подобным образом с наушниками. Представим, что на мониторе закреплен специальный крюк, на который они вешаются и начинают заряжаться.

Прикольно было увидеть, что идею с ковриком реализовали. Про наушники не знаю, но раз я об этом подумал, значит скоро появится. 😎

А вас посещали передовые идеи?

#telegram #wireless #idea
❤‍🔥1🔥1🤯1
Важность понимания сложности алгоритмов

Сейчас на проекте поддерживаем ужасное легаси, доставшееся от бывшего подрядчика. Баги валятся со всех сторон, а в контексте расширения бизнеса и появления новых требований к продукту зачастую валится и базовый функционал. На выходных столкнулись с хрестоматийной проблемой неэффективности алгоритма.

В какой-то момент стали поступать жалобы от пользователей на то, что приложение вылетает на андроид-устройствах. Сами устройства довольно старые: RFID-считыватели на Android 6 и Android 14. После диагностики поняли, что суть в том, что тупо кончается оперативная память и приложение падает. Сразу оговорюсь, что к падению приложения причастен был и ряд других проблем, но в рамках темы рассмотрим только алгоритм.

Суть: в приложении есть функционал выгрузки части базы данных на устройство для оффлайн работы. При выгрузке из БД (у нас couchdb 1.7.2 😢) тянется 130000 документов. Пару секунд приложение пыжится, потом ломается. Предлагаю рассмотреть изначальный код:
obj_arr.forEach(obj => {
if (obj.m_type === OT_BAG) {
obj.art_count = obj_arr
.filter(x => (x.m_type === OT_ART) && (x.container === obj.id))
.length;
}
});

Как мы видим, внутри цикла forEach на каждой итерации вызывается filter, который проходит по тому же массиву. У нас квадратичная сложность вычисления O(n²).

Для тех, кто не в теме, приведу пример: представим, что у нас есть массив, в котором 1000 элементов. Проходя по нему одним циклом мы имеем линейную сложность вычисления O(n), потому что мы пройдем по всем элементам без исключения один раз. В данном случае n - количество элементов в массиве (1000). Количество операций равно количеству элементов.

А теперь представим, что проходя по одному элементу, мы вызываем еще один цикл, который проходится по тому же массиву (цикл в цикле). Сколько будет операций? На каждую операцию будет тоже O(n) вычислений. То есть, 1000 * 1000 = 1000000 операций. То есть, O(n * n) = O(n²).

Однако, если мы не будем вызывать цикл в цикле, а найдем способ вытащить один цикл из другого (а еще лучше, воспользуемся хеш-таблицами 😉), то наша сложность снова станет линейной. И вместо миллиона операций на массив мы вернемся к 1000, что является тысячекратным упрощением.

В общем, решили мы это так:
let bag_art_count = {};

rows.forEach(r => {
const value = r.value || {};
if (value.m_type === OT_ART && value.container) {
bag_art_count[value.container] =
(bag_art_count[value.container] || 0) + 1;
}
});

let obj_arr = rows.map(r => {
const obj = Object.assign(
{ _id: r.value.tag.substr(0, 24), id: r.id },
r.value
);

if (obj.m_type === OT_BAG) {
obj.art_count = bag_art_count[obj.id] || 0;
obj.is_suited = (obj.art_count === 0);
}

return obj;
});

По сути, разбили одну операции на две, но в сумме своей они дают более эффективную работу. Мы в одном цикле проходимся по данным, делаем подсчет, к примеру, а потом в новом цикле уже с данными что-то делаем. В результате даже хиленький девайс на андроиде 6 справляется с повторяемой выгрузкой данных, ничего не падает, пользователи довольны. Конечно, скорее всего можно сделать это еще более эффективно, как мы знаем, предел редко виден в причесывании кода, но самое главное, что мы решили проблему.

#javascript #algo #android
🔥31
У нас довольно непростая история с доступами во внешнюю сеть на работе. Если еще в рамках ежедневной работы что-то можно погуглить, но если нужно посветить виртуалкой в инет для простоты работы, то тут начинаются разнообразные этапы согласований, которые могут отнять время и желание вообще что-либо делать.

Очередная такая ситуация возникла, когда мы запустили виртуальную машину, чтобы развернуть на ней копию прода для тестов. У нас очень много чего завязано на SSL-сертификатах, но в нашей ситуации получать домен и светить айпишником в инет не было реализуемым вариантом. Я до этого с подобным не сталкивался, потому начал думать, как решить, чем сейчас и поделюсь.

Если кратко, то мы выпустили самописный сертификат на айпи адрес в локальной сети. По сути, работает точно так же, как сертификат от Let's Encrypt. Отличие лишь в том, что нужно вручную устанавливать этот сертификат в операционную систему. Вот процедура:

1) Создаем чистую папку для работы, чтобы не запутаться в файлах.
mkdir -p /root/certs
cd /root/certs


2) Создаем «Главный ключ» и «Главный сертификат». Этим сертификатом мы будем подписывать все остальные.
openssl genrsa -aes256 -out rootCA.key 4096
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt

Итог: Появятся rootCA.key (секретный, никому не давать) и rootCA.crt (публичный, его будем ставить на устройства, которые будут ходить на наш сервер).

3) Создается файл настройки с расширением .conf примерно такого содержания:
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = req_distinguished_name
req_extensions = req_ext
x509_extensions = v3_req

[req_distinguished_name]
C = RU
ST = Moscow
L = Moscow
O = CompanyGroup
CN = 101.148.223.60

[req_ext]
subjectAltName = @alt_names

[v3_req]
subjectAltName = @alt_names

[alt_names]
# List all DNS names and IPs here
DNS.1 = app.mycompany.ru
IP.1 = 101.148.223.60
IP.2 = 190.118.127.12
IP.3 = 127.0.0.1

В [alt_names] мы прописываем адреса, по которым будем ходить на сервер, для которого выпускается сертификат. Нас интересует IP.1 - адрес сервера в локальной сети.

4) Генерим ключ сервера и запрос на подпись:
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/server.key \
-out /etc/nginx/ssl/server.crt \
-config cert_config.cnf

Появятся файлы server.key (секретный ключ) и server.csr (запрос).

5) Подписываем запрос нашим корневым сертификатом:
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial \
-out server.crt -days 3650 -sha256 -extfile server.conf -extensions req_ext

Появится server.crt.

6) На этом этапе я добавил созданные сертификаты server.crt и server.key в nginx и перезапустил службу.

По сути с сертификатами всё. Теперь просто берем rootCA.crt и устанавливаем его на устройства, с которых планируется работа с сервером. Я устанавливал на Android и на Windows 11.

#ssl #dev #debian
👏3🔥2❤‍🔥1💯1
Сегодня расскажу про проброс портов в VScode. Удобная фишка, когда разрабатываешь сайт и его нужно показать кому-то. Позволяет не делать никакой деплой, все делается в рамках среды разработки.

Через сервера Microsoft Azure делается туннель, который ведет на вашу машину и ваша разработка становится доступны из внешней сети.

Делается просто:
Например, допустим, у нас просто вёрстка HTML-файлов с помощью встроенного Live Server на порту 5500.

В VScode в терминале жмем Порты. Если терминал закрыт, можно открыть с помощью Ctrl + `, либо Вид - Терминал.

Жмем Перенаправить порт. Вводим порт 5500. Расширение попросит залогиниться - делаем.

После того, как служба отработает, вы увидите ссылку на вашу разработку, которой можно делиться.

Видимость проброса может быть частной и общедоступной. Частная - каждый, кто заходит, должен будет проходить авторизацию, а общедоступный - ссылка напрямую проксирует на вашу машину, потому держим в уме безопасность.

На скринах можно посмотреть как это все выглядит.

#vscode #portforwarding
2
Когда я был молод, 10 лет назад, я уехал работать преподом английского в Таиланд. Такой поворот судьбы стал результатом хронической усталости от работы, на которой количество получаемого не совпадает с количеством отданного. То был, пожалуй, один из первых важных уроков, предоставленных мне жизнью.

Урок заключался в том, что изменения, которые мы ждем увидеть, находятся гораздо ближе, чем нам кажется, и вся сложность лишь в том, чтобы договориться с самим с собой оставить проигрышные условия и начать заново. В своем отчаянии я бросился в гугл искать все частные школы Таиланда и закидывать их имейлами со своим резюме. Через месяц я нашел школу. Через 2 месяца я уже был там. Настолько сильно я хотел поменять что-то, лишь бы было не так, как сейчас.

Из Таиланда я вернулся полным решимости вносить изменения дальше и, как только получил диплом, собрал вещи и уехал в Москву, откуда начался весь мой профессиональный рост.

#personal #thailand
3