Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni
🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!Пости з тегом #OmniWope
11.09.2022
Бот для відправлення постів у Telegram
🤖🤝😁 Сьогодні метапост: я запостив цей пост за допомогою бота, який теж написав сьогодні. До цього пости публікував вручну через додаток Telegram.
Навіщо це потрібно? Є декілька важливих переваг:
- при публікації з бота Телеграм підтримує Markdown; натомість в додатку треба все форматування додавати вручну.
- я хочу в майбутньому дублювати пости на свій сайт
Телеграм-бота створити досить нескладно — для Go є бібліотека, а реєстрація проходить через телеграм-бот @BotFather та займає хвилину.
До того ж для публікації постів потрібен не бот, а просто скрипт. Ідея скрипту проста: для телеграм-каналу створив окремий тип постів (а блог в мене на Hugo); також тримаю файл з відповідністю постів до ідентифікаторів у Telegram. Перед публікацією в Hugo запускаю скрипт, який запостить або оновить пост в Телеграмі, дивлячись на файл відповідностей.
Все б було ідеально, якби Телеграм не вимагав суворого екранування символів для Markdown (наприклад, треба екранувати дефіси та крапки.) Спробую для наступного поста HTML.
16.04.2023
Заголовки постів — тепер в Телеграмі!
Зробив сьогодні маленьку фічу, яку давно хотів. У моїх постів досить давно є заголовки, але вони зʼявляються тільки на сайті та RSS. В Телеграмі заголовків не було, бо мій скрипт їх не підтримував. Нарешті, коли заголовки реалізовані, поділюся проблемами та нюансами.
Заголовки постів, як вони є, містяться в front matter до поста. Вони не є частиною тексту. Так Hugo може показати заголовки на сторінці переліку постів, в RSS, в тезі <title>
і так далі. Але для мого скрипту для постінгу в Telegram це проблема, бо він генерує текст посту в Телеграмі шляхом перетворення вхідного тексту в Markdown у HTML; при цьому титул, як і вся інша передня частина, ігнорується. Ну, точніше, не ігнорується, а виноситься в окрему змінну за допомогою плагіну goldmark-meta; але потім титул треба приєднати до поста… або рудиментарним шаблоном, або — як я поки роблю — просто склейкою. Та при цьому не забути про санітаризацію.
Друга проблема — як уникнути масової зміни постів. Річ у тім, що мій скрипт дозволяє не тільки постити, а й редагувати пости. Це практично корисно тільки для виправлень сьогоднішнього посту, але можливість редагувати залишається для будь-якого з всієї історії. Для цього я зберігаю співвідношення між постами в Hugo та в Телеграмі. Щоб не перепощувати всі пости кожний раз, я зберігаю також контрольну суму тексту; якщо контрольна сума не змінюється, то запит до API Телеграму не відбувається. (Взагалі, контрольних сум дві; одна на весь зміст вхідного файлу, а інша — на результативний текст посту.)
Але. Якщо додати в скрипт титули, то текст абсолютно всіх постів зміниться, та всі вони будуть оновлені в Телеграмі. Мені це не подобається — краще, якщо старі пости залишаться як є. Тому додав також версіювання до моєї таблиці співвідношення. Всі пости отримують версію 0
, а нові пости, починаючи з цього — версію 1
. Скрипт має також поточну версію; пости з версією, що старіше поточної, просто ігноруються. Таким чином, зберігається можливість редагувати, але додавати функції. Тобто відкриває двері для майбутніх покращень.
22.05.2024
Mastodon: публікація через API
Хотілося б ретранслювати цей канал в Mastodon, але ж в концепції WOPE (Write once - publish everywhere.) В Телеграм я давно публікую ботом, також весь зміст є на сайті, доведеться й для Mastodon зробити автоматичну публікацію.
В цілому це набагато простіше, ніж з Telegram або з Twitter. У GoToSocial є API, з ним все зрозуміло, працює без сюрпризів. На власному сервері жодних обмежень на довжину поста немає. Навіть в Markdown можна писати.
Але ж мої пости пишуться не просто в Markdown, а містять метадані. Також я використовую особливу розмітку для посилань на пости з каналу. Можна або, як то кажуть, обробити регулярками по-простому, або все ж доведеться розібрати Markdown, перетворити та зібрати назад. Знайшов рендерер саме для цього.
Та друге питання — що робити з ілюстраціями. Наразі вони публікуються в Телеграм окремим постом через особливості відображення постів зі світлиною, які мені не подобаються. В Mastodon можна або робити так само, або обʼєднати в один пост; треба ще поекспериментувати та визначитись. Якщо окремим, можна робити мінітред з двох постів.
25.05.2024
Труднощі з автопостингом з CI
Отже, як я писав, хотів би загорнути своє рішення для автопостингу в гарний пакет та опублікувати. Частиною того є дійсно автоматичний запуск: наразі це скрипти, які я мушу згадати запустити сам, до того ж на власному компʼютері.
Модель така: є статичний сайт на Hugo; коли в вхідному коді зʼявляється новий пост, публікуємо його в Telegram, Mastodon та ще кудись. Логічним було б поєднати публікацію з розгортуванням самого сайту: для цього на CI вже запускається hugo
. Втім, просто “взяти та запустити” мої скрипти на CI не вийде. Чому?
У скриптів є стан. Наприклад, це відображення постів на ID повідомлень в Telegram. З нього скрипт знає, які пости залишилось опублікувати. Поки стан зберігається в JSON прямо в репозиторії. Це просто в реалізації та уможливлює виправлення його вручну.
Чи можна зберігати JSON в репозиторії з CI? Технічно, можна; GitHub Actions дозволяють робити git push
. Але з CI ми потрапляємо на територію рівночасності та мусимо виконувати всі її вимоги. Що буде, коли я зроблю git push
два рази поспіль — може, щоб виправити помилку? По-перше, git push
з CI може вже не спрацювати, бо репозиторій “поїхав далі”. По-друге, два різних запуски можуть так само налізти один на одного (хоча цього легше уникнути.) А нам життєво необхідно, щоб кожна публікація була занотована в стані!
Виходить, CI влаштовує тільки ідемпотентні операції, тобто такі, де останній запуск завжди приводить до актуального результату. А для мого випадку потрібний зовнішній стан — куди точно можна записати результат операції. Ба більше, так само необхідно записувати, що операція почалася, щоб уникнути подвійної публікації через помилку.
PS: для простіших випадків знайшов цікаву дію github/lock - тут замки вбудовані у GitHub.
26.05.2024
Автопостинг з CI: зовнішнє сховище
Я зʼясував, що без зовнішнього сховища з CI постити не вийде. Займався пошуком такого сховища.
В мене тут є дві потреби: запобігання конфліктам та власне зберігання стану. Краще першу відразу відокремити: запобігання конфліктам вимагає синхронності, що відразу звужує можливий список. Думаю, краще для того покладатись на вбудований функціонал CI - щоб просто не запускав дві збірки одночасно. Принаймні, для першої версії цього достатньо. (Наприклад: Terraform з S3 може використовувати DynamoDB для замикання стану.)
Як щодо зберігання, то в мене спочатку думки пішли в бік баз даних, а саме Firebase та вище згаданої DynamoDB. Цих двох, бо їх достатньо легко розгорнути. Проте будь-яка база зробить рішення практично непереносним, а підтримка різних баз вимагатиме відносно складних адаптерів.
Тому вирішив залишитись в парадигмі “єдиний великий JSON”: всі дані зберігаються в один файл JSON, а далі ми цей файл зберігаємо… на S3. Це очевидний вибір, оскільки API S3 став стандартом де-факто для хмарного сховища. Плюс, користувачі Hugo вже можуть мати налаштований S3. Плюс, JSON-“базу” можна взагалі нікуди не відвантажувати, а тримати локально, якщо CI вам не потрібний.
08.01.2025
Генерація JSON на Hugo
Мій бот скрипт для каналу — який бере зміст з файлів блогу на Hugo - був з самого початку збудований як повністю окреме рішення: він сам знаходить пости, сам їх парсить, сам їх надсилає в Telegram. З Hugo в нього спільні тільки домовленості про структуру тек та формат файлів.
Нещодавно мені спало на думку, що взагалі-то перші два кроки Hugo теж робить, та незрівнянно вправніше. Тому гарно було б якось включитися в Hugo та залишити на власний скрипт тільки публікацію. Зокрема, це розблокувало б інші напрямки публікації, бо наразі скрипт дуже завʼязаний на Telegram та розділити його мені поки не вдалося.
Механізму плагінів в Hugo немає, тож що я придумав: Hugo під час збірки буде генерувати мені JSON з всіма даними, що потрібні для публікації, а скрипту залишиться зчитати JSON та, так би мовити, “синхронізувати” його в канал.
…Механізму “згенерувати JSON” в Hugo теж немає. Зате ми можемо створити окрему сторінку, а в її шаблоні назбирати даних, викликати для них jsonify
та надрукувати — от і буде JSON. На щастя, принаймні з кожної сторінки є необмежений доступ до даних сайту.
Величезний мінус — збирати дані доведеться всередині шаблону. Hugo використовує звичайні шаблони Go, але ніколи в Go мені не спадало на думку, скажімо, перетворювати в шаблоні масиви даних. Бо мова шаблонів незручна, не має підказок IDE, та ще й документація Hugo неповноцінна (в документації функції називаються, як в Go, а в шаблонах пишуться за псевдонімами, яких у переліках функцій та навіть в пошуці не видно).
Але, попри все це, мені вдалося! Найбільше часу витратив на спроби функціонального перетворення масивів — бо там наче є функція apply - аналог map
- але виявилося, що дуже обмежена. А мова шаблонів — все ж імперативна. Тому, наприклад, щоб перетворити масив тегів-обʼєктів у масив назв, я оголошую новий масив та циклом складаю в нього назви. Коли це збагнеш, решта вже не так складно.
Так що чекайте в наступні тижні покращень по скрипту публікації, а може й публікації самого скрипту.
08.02.2025
Новини по HugoWope / OmniWope
Доробляю потрохи бот/скрипт/застосунок для ретрансляції блогу куди тільки можна.
Мій оригінальний скрипт займався “всім відразу” - читав пости з диска, розбирав метадані, будував дані для Telegram. Такий скрипт дуже важко зробити загально доступним, бо там навіть на назву розділу на сайті є завʼязка.
Це той випадок, коли “трохи узагальнити” не можна. Наприклад, параметризувати назву розділу — нічого не дасть, бо сама наявність окремого розділу для каналу є специфічним для мене.
Тому, коли я зробив прототип вивантаження змісту з Hugo у JSON, то це дійсно все спростило. Тепер навіть немає завʼязки на Hugo - бо дані можна підготувати звідки завгодно. (До речі, я вже думав про те, що цей JSON міг би бути натомість RSS. Але поки я хочу глибшої інтеграції ніж просто автопостинг RSS, та не хочу обтяжувати себе привʼязкою до конкретного формату з власною специфікою.)
Отже, тепер проєкт називається OmniWope (Write Once Publish Everywhere). Але почнеться з модулів для підтримки Hugo та Telegram; до якого невдовзі додасться ActivityPub, заради якого я все це роблю.
Сховище теж буде модульне, має влаштувати будь-яке сховище Key-Value. Але починаю я з єдиного файлу JSON. Та, до речі, поки відмовився від публікації з CI принаймні для себе, бо надто важко і це відразу охопити.
Я майже опублікував сьогодні весь проєкт, залишилося тільки зробити читання конфігурації. Але ж хіба то легко? Скоріше за все, долучу cobra - це практично фреймворк для застосунків командного рядка — його використовує Hugo та багато інших продуктів.
17.03.2025
OmniWope - перший пост!
🤞 Нарешті розвʼязав розбіжності в рендерингу постів, про які писав минулого разу. Тож цей пост буду публікувати через OmniWope, а якщо все буде гарно — то наступного дня й сам проєкт OmniWope теж. (Нагадаю, що OmniWope - то мій проєкт для публікації змісту блогу на всілякі платформи.)
Минулого разу також писав про труднощі в оцінюванні часу. Знаєте, тут помилятися можна в обидва боки. Того разу — недооцінив, зате потім переоцінив оті розбіжності та місяць з гаком відкладав. Коли нарешті взяв себе в руки, виправив за 20 хвилин.
Для того потрібно було: зробити так, щоб старий та новий скрипти писали у файли зміст всіх постів. Та порівняти в редакторі ті пости, де є розбіжності. Все! Виявилося, що Hugo автоматично робить першу літеру тегу великою, а значить, в новому скрипті деякі теги змінилися — бо він не вичитує файли самотужки, а бере експорт з Hugo.
Далі знайшов обговорення цього на GitHub, поміняв як там написано та — все! Розбіжності зникли.
Сподіваюся, з публікацією буде все гарно, а там вже й за Mastodon можна взятись (невже майже цілий рік минув?)
18.03.2025
OmniWOPE + Mastodon
Спочатку гарні новини: OmniWOPE вже опублікований! Якщо вам раптом потрібно публікувати блог у канал Telegram, то це вже можна зробити. А далі будемо працювати.
Як тільки я розвʼязав проблеми з телеграмом та перейшов на використання OmniWOPE для цього каналу, наступним чином захотів додати публікацію в Mastodon, про яку так давно думав. Власне, сама публікація — справа нескладна, бо вже є каркас, в який можна додавати більше виходів. (Чого і вам пропоную спробувати, якщо є охота.)
Але ж проблеми зʼявляються із відображенням постів. Якщо брати сам Mastodon, то там все дуже просто, бо він підтримує тільки текстовий зміст. Просто, бо багато не зробиш. Але той сервер що в мене — GoToSocial — хоч і сумісний з Mastodon, але дозволяє публікувати й у Markdown. Та якщо подивитися (зверху) на пост в самому GoToSocial, то наче все більш-менш правильно виглядає.
Проте головна відмінність федиверсу від того ж Telegram - це нескінченне різноманіття клієнтів. Та ще й серверів. Але як я розумію, задача відображення статусу перекладається саме на клієнта — сервер тільки поширює зміст.
Та якщо подивитися на популярні клієнти, то там той самий пост виглядає від недооформленого до повністю понівеченого. Тобто “просто публікувати Markdown” не вийде. Доведеться конвертувати його в такий обрізаний вигляд, який приймуть клієнти. Тобто приблизно так само, як було й з Telegram, але ще й із ретельною перевіркою у різних клієнтах.
22.03.2025
Стендап Сьогодні — тепер і в Федиверсі
Нарешті я на це наважився та публікую цей пост на @stendap_sogodni@shevtsov.me. Перфекціонізм мене тут зупиняв вже надто довго, треба з чогось почати. Запрошую підписатися!
Також із сьогодні OmniWOPE хоч трохи виправдовує префікс “омні” та вміє публікувати в Мастодон. Нагадаю, що публікація ведеться паралельно у всі налаштовані канали.
До речі, вирішив, що федиверсізація блогу (додавання ActivityPub прямо для його сторінок) - це проєкт, хоч безумовно цікавий, але завеликий, а також надто спеціалізований. Та я опинюся з тією ж проблемою невірного відображення HTML різними клієнтами. Тому все ж вважаю доцільною ретрансляцію змісту в Федиверс через проміжний сервер.
З проблемами HTML я поки нічого не робив, оскільки… треба з чогось почати. Поки зрозумів, що процес такий: мій сервер робить з Markdown HTML, а потім цей HTML вже обрізається клієнтами. Тож мені доведеться підбирати такий Markdown, який буде перетворений у прийнятний HTML. Тобто це ще окрема непроста задача.
23.03.2025
Редагування статусів у Mastodon та інше
Інкрементальні покращення для OmniWope:
Вчора помітив несподівану та неприємну особливість: якщо додати вихід (як я Mastodon), коли інший вже був живий (як в мене Telegram), то редагування старих постів викидують їх у новий вихід. Впровадив можливість вказати start_date
для виходу, щоб цього обминути. Допоможе це й тим, в кого вже був блог та публікація вестиметься не з першого поста.
Тут треба розповісти, як я відстежую, що публікувати. Я зберігаю для кожного поста контрольну суму його вхідного змісту. Коли вона змінюється, пост надходить на публікацію до кожного виходу. Вихід будує вихідний зміст та обчислює другу контрольну суму, яка теж зберігається, для кожного виходу окремо. Та якщо вже вона зміниться, то тоді вже пост йде на зовнішній сервіс.
Пощастило, що поки я збирався публікувати в Федиверс, в GoToSocial - мого сервера — зʼявилася можливість редагування. Тож сьогодні додав потрібний виклик та все запрацювало. Цікаво, що в ActivityPub редагування статусів є федерованою операцією, тобто не можна бути впевненим, що воно доїде до кожного отримувача.
Також зробив свій перший PR до GoToSocial, який, звісно, міняє два рядки в документації, але досить важливих. Бо я спочатку сам не міг зрозуміти, як воно працює. Втім, переміг здоровий глузд та експериментація.
25.03.2025
Підтримка relref для Mastodon
Приблизно два роки в мене в ПЗ для каналу є спосіб вказати посилання так, щоб на сайті воно вело на сайт, а в каналі Telegram - на пост в каналі. Сьогодні доробив цей функціонал й для публікації в Mastodon.
До речі. Mastodon досі не вміє публікувати Markdown. Тому технічно, наразі я підтримую не Mastodon, а GoToSocial, та, здається, ще Akkoma. Але — я використовую API Mastodon, звідси й назва.
Тут цікавого те, що вихідний текст поста в мене в Markdown. Та на виході теж отримую Markdown. Тому, певно, можна було б підставити посилання магією регулярних виразів, але навіщо, коли в мене є готове рішення для синтаксичного дерева Markdown? Залишилось тільки з нього побудувати назад Markdown - для того в Goldmark є спеціальний рендерер — та справа зроблена.
Мені такий підхід згодиться й в майбутньому, бо він допоможе позбавитись від тих конструкцій, які підтримуються неповноцінно.