[{"data":1,"prerenderedAt":9548},["ShallowReactive",2],{"header-services-en":3,"services-list-en":866,"site-data-en":1154,"cases-all-en":1419,"glossary-en":8872},[4,196,372,540,714],{"id":5,"title":6,"audience":7,"body":17,"description":31,"emoji":32,"extension":33,"faq":34,"includes":68,"lead":77,"meta":78,"navigation":79,"order":80,"path":81,"pricing":82,"seo":112,"serviceType":113,"stack":114,"stackNotes":122,"stem":135,"steps":136,"works":155,"__hash__":195},"services_en\u002Fen\u002Fuslugi\u002Fsajty.md","Websites and online stores",[8,11,14],{"title":9,"text":10},"We sell by phone and chat","We build an online store the owner runs alone: products, prices, stock and orders are edited in an admin panel, and every new order arrives in Telegram.",{"title":12,"text":13},"An old site on an outdated CMS","We move the content to WordPress from a database dump, point old URLs to the new ones with permanent 301 redirects, and fill in titles and descriptions for every page.",{"title":15,"text":16},"You need a page instead of a long chat thread","We build a business-card site or a storefront: who you are, what you do, how the process works, how to get in touch. The page is light, so it opens fast on a phone from an ad link.",{"type":18,"value":19,"toc":27},"minimark",[20,24],[21,22,23],"p",{},"We build websites from scratch, with no site builders or off-the-shelf templates.\nThe stack follows the task: Nuxt and FastAPI with a custom admin panel for stores\nand services, or WordPress with our own theme and no third-party plugins when the\nclient wants a familiar admin. SEO markup, mobile\nlayout and a clear admin panel come standard, so the client changes prices and\ntexts themselves.",[21,25,26],{},"We also handle migrations: moving sites off outdated CMSs while keeping URLs and\nsearch rankings — old links redirect to the new pages.",{"title":28,"searchDepth":29,"depth":29,"links":30},"",2,[],"Turnkey websites on Nuxt with a custom admin panel or on WordPress with our own theme: online stores, storefronts, business cards, CMS migrations.","🌐","md",[35,38,41,44,47,50,53,56,59,62,65],{"q":36,"a":37},"Can I order a website from you?","Yes. We build turnkey websites: landing pages, business cards, catalogs, storefronts and online stores. The stack follows the task: Nuxt with a custom admin panel, or WordPress with our own theme. We are based in Moscow and Chisinau and work with clients remotely.",{"q":39,"a":40},"Do you build on a custom stack or on WordPress?","Both. A store with stock, orders and its own logic is built on Nuxt and FastAPI: it loads fast and the admin panel covers exactly what the owner changes. When a client wants the familiar WordPress admin, we write our own theme with no third-party plugins or page builders, which means fewer updates, fewer vulnerabilities and light pages.",{"q":42,"a":43},"Can you migrate an old site without losing search rankings?","Yes. We move sites off legacy CMSs, for example from Joomla to WordPress: we parse the database dump, carry over texts, the catalog and forms, and map old URLs to new ones with permanent 301 redirects.",{"q":45,"a":46},"How much does SEO and getting into AI assistants' answers cost?","If the site is sound and does not need a rebuild, we set up indexing and visibility turnkey from $160: sitemap, schema.org markup, llms.txt, Yandex Webmaster, Google Search Console and Bing accounts, IndexNow, recrawl requests. The result is a site that appears in Google and Bing and in the answers of ChatGPT, Gemini and Copilot. It takes 2–8 weeks, almost all of it waiting: search engines and assistants refresh their data slowly and nothing speeds that up. If the site needs major rework, we quote it separately.",{"q":48,"a":49},"How much does a website cost?","A specialist's business card site starts at $80, with SEO markup for search and AI assistants at $160. A multi-page Nuxt site or a WordPress showcase starts at $1,000, a site with an admin panel or an online store at $1,400. Migration from an old CMS starts at $250, each extra language adds 5 % to the estimate. The exact figure comes in the proposal after a free audit of the task, the current site and competitors. No hourly billing: the price is fixed for the result.",{"q":51,"a":52},"Will I be able to edit texts and prices myself?","Yes. We build a clear admin panel for the client's tasks from the start: prices and stock are edited in one table, texts with a live preview — no developer needed.",{"q":54,"a":55},"What is better for an online store: Nuxt or WordPress?","It depends on what the store must do. We built a store with sizes, colors, stock and wholesale prices on Nuxt and FastAPI: pages are served as ready HTML, and the admin panel covers exactly what the owner changes. We built a storefront without online payment on WordPress with our own theme, because the client wanted the familiar admin. We make the choice after the audit.",{"q":57,"a":58},"Can a site work without online payment, with orders going through a messenger or VKontakte?","Yes. In the project for an essential-oil brand there is no payment on the site by design: the cart collects the order and passes it to the community bot on VKontakte with a one-time code, and the buyer lands in a chat with the order ready. If the site did not respond or the bot is off, the cart falls back to copying the order text.",{"q":60,"a":61},"How do I make a site that search engines and AI assistants can find?","Pages are served as ready HTML, each has its own title and description, and JSON-LD markup tells robots what is on the page: Product for an item, FAQPage for questions and answers, Person for a specialist's business card. We generate the sitemap and robots.txt from the content and add hreflang for two languages. Our own studio site and a business-card site are built this way.",{"q":63,"a":64},"Who owns the site, and what do I receive?","The code and data stay with the client. At handover you get the site, access, a README written as a technical specification and a CHANGELOG, plus a guide to the admin panel for the owner. Support is free for a month after handover, after that maintenance is by agreement.",{"q":66,"a":67},"How fast can a site be built?","The time depends on the scope: a store with stock and wholesale, a storefront with a cart and a one-page business card are different amounts of work. We state the time after a short free audit, together with the estimate.",[69,70,71,72,73,74,75,76],"An admin panel built around the client's tasks: prices, stock and texts are edited without a developer","SEO markup: page titles and descriptions, JSON-LD, sitemap, robots.txt","Layout for phone and desktop","Permanent 301 redirects from old URLs when moving to a new site","Docker deployment with HTTPS on the client's server or ours","A guide for the site owner, built into the admin panel","README written as a technical specification, and a CHANGELOG","A free month of support after handover; the code and data stay with the client","Turnkey websites: business cards, landing pages, catalogs and multi-page sites",{},true,10,"\u002Fen\u002Fuslugi\u002Fsajty",{"visible":79,"note":83,"items":84},"The audit and the proposal are free. No hourly billing: the price is fixed for the result, the exact figure comes in the proposal after the audit. Support after delivery starts at $100 a month, the first month is free. Timelines are individual and set once the specification is approved.",[85,95,104],{"title":86,"price":87,"unit":88,"description":89,"includes":90,"featured":94},"Business card site","from $80","one page","A simple page for a specialist: who you are, services, contacts.",[91,92,93],"A card with SEO markup for search and AI assistants — from $160","Mobile layout, QR code and vCard","Turnkey SEO and AI-assistant visibility — from $160, 2–8 weeks",false,{"title":96,"price":97,"unit":98,"description":99,"includes":100,"featured":79},"Nuxt or WordPress site","from $1,000","per project","A multi-page Nuxt site without a database or a WordPress showcase with a custom theme.",[101,102,103],"With an admin panel — from $1,400","Each extra language +5 % to the estimate, machine translation","Migration from an old CMS with redirects — from $250",{"title":105,"price":106,"unit":98,"description":107,"includes":108,"featured":94},"Online store","from $1,400","Your own admin panel: the owner manages products, prices, stock and orders; orders arrive in Telegram.",[109,110,111],"Web service with a Telegram cabinet — from $650","Code and data stay on your server","README and CHANGELOG included",{"title":6,"description":31},"Website and online store development",[115,116,117,118,119,120,121],"Nuxt \u002F Vue 3","TypeScript","FastAPI","WordPress","nginx","Docker","SEO",[123,125,127,129,131,133],{"name":115,"text":124},"Pages are built on the server and delivered as ready HTML, so a search robot sees products and texts right away.",{"name":117,"text":126},"A server program that serves the catalog, accepts orders and stores the site settings.",{"name":118,"text":128},"A familiar admin panel and editor for the client, with a theme written for the project.",{"name":119,"text":130},"Serves the site, sets up caching and redirects, and limits the number of requests to the login page.",{"name":120,"text":132},"The site starts the same way on a developer's computer and on the server, and moving to another server does not break the setup.",{"name":121,"text":134},"Titles, descriptions and structured data help search engines and AI assistants understand what a page is about.","en\u002Fuslugi\u002Fsajty",[137,140,143,146,149,152],{"title":138,"text":139},"Request and free audit","You briefly describe the task. We go through it, the current site and competitors. The audit is free, and it gives us the scope of work.",{"title":141,"text":142},"Stack and estimate","Depending on the task we pick Nuxt with a custom admin panel or WordPress with our own theme, then state the scope and the price.",{"title":144,"text":145},"Design and layout","We build the pages for phone and desktop, following the client's brand book if there is one. The client reviews the result on a test address.",{"title":147,"text":148},"Admin panel, content and SEO","We set up management around what the owner changes, fill in the catalog and texts, and add titles, descriptions and structured data. In a migration we move the content and build the redirect map.",{"title":150,"text":151},"Launch","We deploy the site in Docker with HTTPS and check it on a phone and against the acceptance criteria from the README.",{"title":153,"text":154},"Handover and support","We hand over the code, access, documentation and the owner's guide. The first month of support is free, after that maintenance is by agreement.",[156,161,166,171,176,181,186,191],{"id":157,"title":158,"text":159,"term":28,"price":28,"case":160},"sajty-01","Online store with an admin panel","A store on Nuxt and FastAPI: a catalog with colors and sizes, stock per size, a cart and wholesale prices. The owner manages products, prices and orders alone, and the server recalculates the order total at checkout. It ends with a working store on a server and order notifications in Telegram.","internet-magazin-rubashek",{"id":162,"title":163,"text":164,"term":28,"price":28,"case":165},"sajty-02","WordPress storefront with ordering through VKontakte","A catalog with pack sizes, retail and wholesale prices, and a cart kept on the buyer's device. The order goes to the community chat on VKontakte with the contents already filled in, and the community bot reads the same catalog from the site. The client edits prices, stock and texts in the admin panel, and the site runs in Russian and English.","dyhanie-povolzhya",{"id":167,"title":168,"text":169,"term":28,"price":28,"case":170},"sajty-03","Migration from Joomla to WordPress","We parse the database dump with scripts and carry over texts, categories, dates, URLs and attachments. We build a map of old URLs and set up 301 redirects, and the import can be re-run without creating duplicates. It ends with a WordPress site and a settings panel the client runs alone.","joomla-to-wordpress",{"id":172,"title":173,"text":174,"term":28,"price":28,"case":175},"sajty-04","Business-card site for a specialist","One page: profile, work process, services, FAQ and contacts. It includes a QR code and a vCard file for saving the contact to a phone, Person and FAQPage markup for search engines, and self-hosted fonts. The specialist's client needs just one link.","valera",{"id":177,"title":178,"text":179,"term":28,"price":28,"case":180},"sajty-05","Multilingual Nuxt site without a database","Pages, case studies and the glossary live in markdown files in git, and a schema validates their structure. Russian and English versions come with hreflang, and the sitemap and structured data are generated from the content. Editing the site means editing a file and rebuilding, and it runs in a single container.","sajt-studii",{"id":182,"title":183,"text":184,"term":28,"price":28,"case":185},"sajty-06","Site and hosting audit with a demo stand of the new site","A technical audit of the site from a backup, a check of hosting and domain, and a set of documents for management. Alongside them we build a working demo stand of the new site, closed from search indexing. The client sees the result before signing a contract.","sajt-muzeya-demo",{"id":187,"title":188,"text":189,"term":28,"price":28,"case":190},"sajty-07","Web service with a Telegram admin","Not just a page but a service around it: a card from a single HTML file, a Python API, PostgreSQL, an admin bot and a web cabinet inside Telegram. It has plans, client roles and invitation codes. The owner creates and edits pages from a phone, without a developer.","unlock",{"id":192,"title":193,"text":194,"term":28,"price":28,"case":180},"sajty-08","SEO and visibility in AI assistants","Turnkey indexing setup: sitemap, schema.org markup, llms.txt files for AI, Yandex Webmaster, Google Search Console and Bing accounts, IndexNow. Pages get into Google and Bing and into the answers of ChatGPT, Gemini and Copilot. Timeline 2–8 weeks: that is how long search engines and assistants take to refresh their data, and it is out of our hands. If the site needs major rework, we quote it separately.","ZJLm1eY2JZRQEzJd6hkdBXGSUmvLwMFIE0FOpUdYVBw",{"id":197,"title":198,"audience":199,"body":209,"description":219,"emoji":220,"extension":33,"faq":221,"includes":252,"lead":261,"meta":262,"navigation":79,"order":263,"path":264,"pricing":265,"seo":290,"serviceType":291,"stack":292,"stackNotes":298,"stem":316,"steps":317,"works":335,"__hash__":371},"services_en\u002Fen\u002Fuslugi\u002Fboty.md","Telegram and VK bots",[200,203,206],{"title":201,"text":202},"Requests and payments get lost in chats","We move request intake, approval and records into a bot with a database. In a ticketing system a request moves through statuses and a resolution deadline; in subscription sales the payment receipt goes to the admins with 'Confirm' and 'Reject' buttons.",{"title":204,"text":205},"Documents and records are processed by hand","We build a bot that only needs a file forwarded to it: a PDF policy, a bank statement, a voice message or a photo of a receipt. It returns a finished file or text, and the result stays in the database.",{"title":207,"text":208},"You need a loyalty card, a queue or visit tracking","We replace paper cards and sign-up lists: a QR card in Telegram with a reward for visits, an electronic queue with reminders, and a list export to Excel.",{"type":18,"value":210,"toc":217},[211,214],[21,212,213],{},"We build Telegram and VK bots for sales, support and accounting — with their\nown database and integration into the client's systems. At 3 a.m. the bot\nreplies just as fast as at noon.",[21,215,216],{},"These are not business-card bots but production with transactions, migrations\nand live users: billing with payment confirmation, ticketing systems, document\nprocessing, accounting. Every bot comes with its own admin panel, logs and\nbackups.",{"title":28,"searchDepth":29,"depth":29,"links":218},[],"Telegram and VK bots with real business logic: sales, support, accounting, billing — with their own database and integration into the client's systems.","🤖",[222,225,228,231,234,237,240,243,246,249],{"q":223,"a":224},"How much does a Telegram or VK bot cost?","A simple bot — broadcasts, FAQ answers, bookings and request intake — starts at $80. A loyalty program or digital queues start at $160. PDF processing, media download and an AI bot via API start at $250. A bot with payments and account management starts at $400, a ticket system with roles and SLA at $800. Support starts at $100 a month, the first month is free. We quote the exact price after a short free review of the task; there is no hourly billing.",{"q":226,"a":227},"Can you build a bot that tracks finances from bank statements?","Yes, we already run one: it accepts PDF bank statements, detects the bank and account itself, never double-counts overlapping periods and separates transfers between your own accounts from income and expenses. See the “Finance Assistant” case.",{"q":229,"a":230},"Will the bot run on your server or ours?","Whichever suits you. Usually we deploy on the client's server — code and data stay with you. We can also take it under our maintenance with monitoring and backups.",{"q":232,"a":233},"What if Telegram gets blocked?","For critical scenarios we build a backup channel on VK: the same bot, the same data. That's how access delivery works in our VPN service — if Telegram is unreachable, the config arrives via VK.",{"q":235,"a":236},"Can the bot be connected to our system over API?","Yes, a bot talks to other systems over API, that is, through a set of addresses by which one program asks another to perform an action. In our projects this is how the Marzban panel (creating users and extending subscriptions), a chat service's internal API and Google Drive for backups are connected. What to connect to your system, we work out during the free audit.",{"q":238,"a":239},"How can a bot take payments if there is no card acquiring?","The bot shows bank-transfer details, the customer sends a receipt screenshot, and the admins receive it with 'Confirm' and 'Reject' buttons. A safeguard stops two admins from confirming the same payment twice. If you need an automatic payment system, we discuss connecting it separately during the audit.",{"q":241,"a":242},"How do I build an AI bot that transcribes voice messages?","The audio goes to the Google Gemini model together with an instruction in a single request, with no separate speech-recognition step. Before sending, FFmpeg checks the recording and recompresses a heavy file. The audio is deleted right after processing, and if there is no speech in the recording, the bot says so outright.",{"q":244,"a":245},"How can employee and manager rights be separated in a bot?","The role is stored in the database and injected into every request, so a person never sees another role's menu. The ticketing system has four roles: employee, administrator, manager and owner. The manager gets reports and a dashboard but does not see the requests themselves.",{"q":247,"a":248},"Who owns the bot's code and data?","The code and data stay with the client. We deliver the project with documentation: a README written as a technical specification and a CHANGELOG. After delivery there is a month of free support, then maintenance by agreement.",{"q":250,"a":251},"How long does it take to build a bot?","It depends on the scope: the number of roles, integrations and scenarios. We name the timeline after a short free audit, once it is clear what exactly has to be built.",[253,254,255,256,257,258,259,260],"A Telegram bot with button menus, roles and access rights","A database suited to the task: PostgreSQL, MariaDB or SQLite, tables created on start","An admin panel inside the bot: statistics, users, broadcasts, logs","Integrations with external systems over API: a panel, a service, an AI model, cloud storage","Docker Compose deployment on your server or ours; the bot version is written to the log on start","Database backups where the database holds the client's data","Documentation: a README written as a technical specification and a CHANGELOG","One month of free support after delivery, then maintenance by agreement","Sales and support that never sleep",{},20,"\u002Fen\u002Fuslugi\u002Fboty",{"visible":79,"note":83,"items":266},[267,274,281],{"title":268,"price":87,"unit":98,"description":269,"includes":270,"featured":94},"Simple bot","Broadcasts, FAQ answers, bookings and request intake.",[271,272,273],"Loyalty program and digital queues — from $160","PDF processing, media download, AI bot via API — from $250","Backup channel in VK",{"title":275,"price":276,"unit":98,"description":277,"includes":278,"featured":79},"Bot with payments and roles","from $400","Subscription sales with payments, account management, button-based admin panel.",[279,280,110],"Ticket system and service desk — from $800","Database and integrations with CRM and payment providers",{"title":282,"price":283,"unit":284,"description":285,"includes":286,"featured":94},"Support","from $100","per month","Monitoring, backups, small fixes and library updates.",[287,288,289],"First month after delivery is free","Feature requests are quoted separately","Bot activity report on request",{"title":198,"description":219},"Telegram and VK bot development",[293,294,295,296,297,120],"Python","aiogram 3","VK API","PostgreSQL","MariaDB",[299,302,305,308,311,314],{"name":300,"text":301},"Python and aiogram 3","An asynchronous bot answers many people at once while other requests are still being processed.",{"name":303,"text":304},"PostgreSQL, MariaDB, SQLite","A database sized to the task: a ticketing system and record-keeping need a separate database server, while a small club is fine with a single SQLite file.",{"name":306,"text":307},"APScheduler","A scheduler sends reminders, escalations and digests and makes backups on a timetable.",{"name":309,"text":310},"Google Gemini","A neural network transcribes voice messages, reads receipt photos and parses complex text without separate recognition programs.",{"name":312,"text":313},"FFmpeg","It checks a recording's length, compresses large files and splits video into parts so they fit Telegram's limit.",{"name":120,"text":315},"The bot and its database start with one command on any server and do not depend on the machine's settings.","en\u002Fuslugi\u002Fboty",[318,320,323,326,329,332],{"title":138,"text":319},"We go through the task: who uses the bot, which actions it must perform and where the data comes from. You get an estimate based on the scope of work.",{"title":321,"text":322},"Scenarios and roles","We describe the bot's dialogues and the rights of each role: what a customer, an employee, an administrator and a manager see. You get a description against which the finished bot can be checked.",{"title":324,"text":325},"Database and integrations","We design how the data is stored and connect external systems over API. You know where your data will live and what the bot takes from other services.",{"title":327,"text":328},"Bot and admin panel development","We build the bot, a button-based admin panel, broadcasts and logs. You try the working scenarios before the launch on the production server.",{"title":330,"text":331},"Launch in Docker","We deploy the bot on your server or ours and set up the time zone, logs and backups. You get a running service and a README with instructions.",{"title":333,"text":334},"Delivery and support","We hand over the project with documentation and a CHANGELOG and fix any issues free of charge for a month. After that, maintenance by agreement.",[336,341,346,351,356,361,366],{"id":337,"title":338,"text":339,"term":28,"price":28,"case":340},"boty-01","Subscription sales bot with bank-transfer payment","The bot takes a customer from invite-code registration and a trial access to payment. It shows the bank-transfer details (Russian SBP system) and accepts a receipt screenshot, and the admins confirm the payment with a button. The subscription is then extended in the Marzban VPN panel, and the bot reminds the customer 3 days and 1 day before expiry. A person verifies the payment; this project has no automatic card acquiring.","bot-prodazhi-podpisok",{"id":342,"title":343,"text":344,"term":28,"price":28,"case":345},"boty-02","Ticketing and service desk in Telegram","An employee files a request in four steps, and the card appears in the IT team's chat and changes status in place. It includes priorities and an SLA (a target time to resolve), closing only after the employee confirms, and a 1–10 rating. Alongside we build equipment tracking, an access registry with password encryption, a knowledge base and a monthly report for management.","help-desk",{"id":347,"title":348,"text":349,"term":28,"price":28,"case":350},"boty-03","PDF document processing by a bot","The bot accepts a PDF, analyses the page and returns a modified file. In our project it places an emergency commissioner's contacts onto an electronic motor-insurance policy while the policy number, personal data and QR code stay untouched, and a separate command checks the result. The document is processed in memory and never saved to disk.","strahovye-pdf",{"id":352,"title":353,"text":354,"term":28,"price":28,"case":355},"boty-04","Loyalty programme and electronic queues","A customer shows a QR code from the bot, an administrator adds a visit with one tap, and on the sixth night the bot unlocks a reward. A pause between accruals and a staff action log protect against abuse. The same approach works for an electronic queue: sign-up with one button, check-in, a notice when the turn is near, and a list export to Excel.","kompyuternyi-klub",{"id":357,"title":358,"text":359,"term":28,"price":28,"case":360},"boty-05","AI bot: speech transcription, statement and receipt parsing","Voice messages, receipt photos and complex text go to the multimodal Google Gemini model, which works with audio and images directly. This is how a voice-to-text bot with three modes and a finance assistant work: the assistant parses PDF statements from four banks and takes expenses as a plain sentence. Gemini keys run as a pool: when one key's quota runs out, the request goes through the next.","finansovyi-assistent",{"id":362,"title":363,"text":364,"term":28,"price":28,"case":365},"boty-06","Media download and processing bot","A user sends a link and the bot returns the file in the same chat: yt-dlp fetches the video, FFmpeg compresses and splits it. Our project covers 12 platforms, quality choice up to 4K, MP3 with cover art, repeat requests from a cache and files up to 2 GB through a local Bot API. A separate bot downloads Hi-Res music and checks by spectrum that the FLAC is genuine.","savebot",{"id":367,"title":368,"text":369,"term":28,"price":28,"case":370},"boty-07","Account sign-in and management bot for a service","The bot links a service account to a person in Telegram and issues a one-time code for passwordless sign-in. The owner approves registration requests with buttons, and changing a name or password is done from the chat. The bot has no database of its own: it calls the service's internal API, so the data lives in one place.","stream-bot","8C0j4_KQ1nm8LTzs5unip2KhyfqXq2gcpewJEo3he-A",{"id":373,"title":374,"audience":375,"body":385,"description":395,"emoji":396,"extension":33,"faq":397,"includes":428,"lead":436,"meta":437,"navigation":79,"order":438,"path":439,"pricing":440,"seo":465,"serviceType":466,"stack":467,"stackNotes":473,"stem":488,"steps":489,"works":506,"__hash__":539},"services_en\u002Fen\u002Fuslugi\u002Favtomatizaciya.md","Automation and document processing",[376,379,382],{"title":377,"text":378},"Leads are copied from a messenger into the CRM by hand","Requests arrive in a Telegram bot, and then someone copies them into the CRM and a spreadsheet. We build an n8n chain where the request is split into fields and lands in Bitrix24 and Google Sheets on its own.",{"title":380,"text":381},"You need to know about an event in another service right away","Around holidays, Wildberries delivery slots are taken within minutes, and a manager notices a deal status change only when they open the CRM. We poll the API or receive webhooks and send a Telegram message immediately.",{"title":383,"text":384},"Calculations and documents are done by hand","Spreadsheet formulas produce errors that surface only at the cutting stage, and documents get edited in several versions. We move the rules into code: a library calculates by formula, and documents are built from a source file with one command.",{"type":18,"value":386,"toc":393},[387,390],[21,388,389],{},"We set up n8n: linking ERP, marketplaces and CRM into one chain. We sync stock,\nupload orders and catch warehouse delivery slots via the Wildberries and Ozon\nAPIs. Tasks a person used to do by hand now run themselves.",[21,391,392],{},"Where ready-made integrations don't exist, we write our own: monitoring,\nticketing systems, document processing — and embed them into the overall\nscheme.",{"title":28,"searchDepth":29,"depth":29,"links":394},[],"Automation on n8n and custom services: ERP, marketplaces, CRM and Telegram in one chain. Stock sync, orders, warehouse slots on Wildberries and Ozon.","🔄",[398,401,404,407,410,413,416,419,422,425],{"q":399,"a":400},"Can you set up n8n to integrate with Wildberries and Ozon?","Yes, we specialize in integrations with Russian marketplaces: automatic stock sync, order uploads and price updates through the WB and Ozon APIs, running reliably 24\u002F7.",{"q":402,"a":403},"Why n8n instead of Zapier or Make?","n8n lives on your server: no operation limits, data doesn't leave for someone else's cloud, and the service won't switch off due to regional restrictions. Same scenarios — webhooks, schedules, branching — without a monthly fee for every step.",{"q":405,"a":406},"What if n8n has no integration we need?","We write our own node or a separate Python service and plug it into the chain. That's how the Wildberries warehouse slot monitor works — n8n isn't needed there, a custom bot polls the API every 30 seconds.",{"q":408,"a":409},"How do I pass a Telegram bot request into a CRM and a spreadsheet without manual work?","The bot hands the message to n8n. The scenario splits it into fields, creates a record in Bitrix24 and appends a row to Google Sheets. That is how the small-business request automation we built works: no manual copying.",{"q":411,"a":412},"How can a manager learn about a deal status change immediately?","When the status changes, the CRM calls an n8n address, which is called a webhook. The scenario finds the responsible manager and sends them a Telegram message, so they don't need to open the CRM.",{"q":414,"a":415},"Where are n8n scenarios stored and who owns them?","A scenario is exported to a JSON file that can be imported on another server and kept in git. n8n runs on the client's server, keys and tokens are stored in it rather than in the file. Code and data stay with the client.",{"q":417,"a":418},"How do I avoid missing free delivery slots on Wildberries?","The bot polls the API every 30 seconds and sends slots for the chosen warehouses, dates and acceptance coefficients. The message has a direct booking link: from the alert to booking takes about 10 seconds. A person makes the decision.",{"q":420,"a":421},"Can production calculations be automated, for example a cardboard box blank?","Yes. For a packaging manufacturer we built a C++17 library: from inner dimensions and board thickness it calculates the blank for FEFCO 0200, 0201 and 0203 and the layout on a sheet. The formulas are covered by tests. The scoring allowance should be checked against a specific plant's tooling.",{"q":423,"a":424},"Can document preparation for management be automated?","Yes. The source is kept in Markdown in git, and a script builds the .docx for signing in the agreed layout. That is how we built the survey package for a museum complex network: five internal memos and a commercial proposal. Edits go into the source, because a change made in Word is overwritten by the next build.",{"q":426,"a":427},"How much does automation cost and how fast is it done?","Linking two services, webhooks and notifications, self-hosted n8n start at $80 per workflow. Leads from a bot or website into CRM and Google Sheets start at $400. Marketplace slot monitoring starts at $160, engineering calculations for production at $500. The total depends on how many systems must be linked and whether they have an API. We name the price and timeline after a short free audit; there is no hourly billing.",[429,430,431,432,433,434,75,435],"n8n scenarios as JSON files: they can be moved to another server and kept in git","n8n on your own server rather than a cloud plan: no cap on the number of runs","An execution log: for every run you can see whether the request went through","A Telegram bot or alerts with filters tuned to your task","Tests where the project includes calculations: formula and input validation checks","Code and data stay with the client","One month of free support after handover","Less manual routine for your staff",{},30,"\u002Fen\u002Fuslugi\u002Favtomatizaciya",{"visible":79,"note":83,"items":441},[442,450,457],{"title":443,"price":87,"unit":444,"description":445,"includes":446,"featured":94},"Service connections","per workflow","Webhooks and manager notifications, self-hosted n8n, document assembly from Markdown to Word.",[447,448,449],"Wildberries slot monitoring — from $160","Workflows are delivered as files and can live in git","Keys and tokens are stored in n8n, not in the file",{"title":451,"price":276,"unit":98,"description":452,"includes":453,"featured":79},"Leads into CRM","Bot or website → CRM → Google Sheets, no manual copying.",[454,455,456],"Every run is visible in the execution log","n8n on your server: no limits on runs","Scheme agreed before the build",{"title":458,"price":459,"unit":98,"description":460,"includes":461,"featured":94},"Engineering calculations","from $500","Calculation services for production: sheet layouts, nesting, calculators.",[462,463,464],"Result as a service or a spreadsheet","README as a technical specification and CHANGELOG","Free audit before the proposal",{"title":374,"description":395},"Business process automation and integrations",[468,469,470,471,472,293],"n8n","Webhooks","WB API","Ozon API","1C",[474,476,478,480,482,485],{"name":468,"text":475},"An automation builder: steps are assembled from ready-made blocks and triggered by an event, so no separate program is needed for each link.",{"name":469,"text":477},"An address to which a system sends an event by itself: this is how the CRM reports a deal status change without waiting for someone to look.",{"name":470,"text":479},"The Wildberries interface through which the bot gets delivery slots without opening the seller account.",{"name":293,"text":481},"The language for separate services where n8n has no ready node: the slot bot and document building.",{"name":483,"text":484},"Google Sheets","A familiar tracking spreadsheet that also serves as a fallback record of requests if the CRM is unavailable.",{"name":486,"text":487},"C++17","A fast calculation library that embeds into production software with no external dependencies.","en\u002Fuslugi\u002Favtomatizaciya",[490,492,495,498,501,504],{"title":138,"text":491},"We look at where staff copy data by hand and which systems are involved. Before any estimate, you get a list of what can be automated.",{"title":493,"text":494},"Integration check","We find out whether each system has an API or webhooks so it can join the chain. If there is no ready-made node, we plan a separate Python service.",{"title":496,"text":497},"Chain design","We describe what triggers the scenario, which steps follow and where the data ends up. You approve the design before we build.",{"title":499,"text":500},"Build and launch on the server","We build the scenarios in n8n or write a separate service and install it on the client's server. Keys and tokens are stored inside n8n.",{"title":502,"text":503},"Check against real data","We run the chain and verify the result in the execution log: the request arrived, the status changed, the row appeared in the spreadsheet.",{"title":153,"text":505},"We hand over the scenarios as files, a README written as a technical specification and a CHANGELOG. Support is free for a month after handover.",[507,512,516,520,525,530,535],{"id":508,"title":509,"text":510,"term":28,"price":28,"case":511},"avtomatizaciya-01","Telegram bot requests into CRM and Google Sheets","An n8n scenario takes a message from the bot, splits it into fields, creates a record in Bitrix24 and appends a row to a spreadsheet. Every run is visible in the execution log. The result is a request with no manual copying.","n8n-crm",{"id":513,"title":514,"text":515,"term":28,"price":28,"case":511},"avtomatizaciya-02","Manager alerts and payment system webhooks","When a deal status changes, the CRM calls an n8n address by itself, and the scenario finds the responsible manager and messages them in Telegram. Payment system events arrive at the same node and are recorded without manual reconciliation.",{"id":517,"title":518,"text":519,"term":28,"price":28,"case":511},"avtomatizaciya-03","Self-hosted n8n on the client's server","We install n8n on your server instead of a cloud plan: there is no cap on the number of runs, only the server's capacity. Scenarios are exported as JSON files, moved between servers and kept in git. Keys and tokens live inside n8n, not in the scenario file.",{"id":521,"title":522,"text":523,"term":28,"price":28,"case":524},"avtomatizaciya-04","Wildberries delivery slot monitoring","A Telegram bot polls the Wildberries API every 30 seconds and sends free slots for the chosen warehouses, dates and acceptance coefficients. The message contains a direct booking link. The bot runs on a server around the clock.","wb-monitoring",{"id":526,"title":527,"text":528,"term":28,"price":28,"case":529},"avtomatizaciya-05","Box blank and sheet layout calculation (FEFCO)","We move formulas out of spreadsheets into code: a C++17 library calculates a cardboard box blank for FEFCO styles, volumes and the layout of blanks on a corrugated sheet. Invalid input is rejected with a message, and the formulas are covered by tests. The library has no dependencies and embeds into production software.","box-calc",{"id":531,"title":532,"text":533,"term":28,"price":28,"case":534},"avtomatizaciya-06","LAN and CCTV survey with a document package","We describe the local network, video surveillance and remote access: every fact has a stated source, and anything unconfirmed goes into separate lists for on-site checking. The output is a report, internal memos and a commercial proposal with work stages.","obsledovanie-seti-muzeya",{"id":536,"title":537,"text":538,"term":28,"price":28,"case":534},"avtomatizaciya-07","Building Word documents from Markdown","The document source lives in Markdown in git, and a Python script builds the .docx for signing in the agreed layout: A4, Times New Roman 12. Edits go into the source, so the change history is preserved. Passwords and keys never reach the repository.","ADtMdNRk957oLiov6fIjkcLVB5C38Ow4GS5wc2z7f0E",{"id":541,"title":542,"audience":543,"body":553,"description":563,"emoji":564,"extension":33,"faq":565,"includes":599,"lead":608,"meta":609,"navigation":79,"order":610,"path":611,"pricing":612,"seo":639,"serviceType":640,"stack":641,"stackNotes":648,"stem":665,"steps":666,"works":683,"__hash__":713},"services_en\u002Fen\u002Fuslugi\u002Finfrastruktura.md","Infrastructure and networks",[544,547,550],{"title":545,"text":546},"The team lost access to work services","Because of regional restrictions Canva, Notion, Jira and AI tools stopped opening, and public workarounds break the security policy. We deploy a corporate gateway on the company's own servers: traffic is encrypted and access is split by role.",{"title":548,"text":549},"Websites and services share one server and get in each other's way","Editing one site can take down a neighboring service, and certificates are renewed by hand. We separate them into containers and docker networks behind a single entry nginx.",{"title":551,"text":552},"A VPN gets cut on the path between countries","A direct connection to an exit server abroad is filtered at the moment it is established. We build an entry node in Russia, a permanent tunnel between nodes and an exit node abroad, as in our own VPN service.",{"type":18,"value":554,"toc":561},[555,558],[21,556,557],{},"We move Notion, Zapier and Google Workspace onto the client's own servers — the\ncompany never loses access to its tools overnight, and data never leaves the\nperimeter. We build private VPN networks for teams and services. We set up\nauto-deploy, monitoring and backups — if something breaks, you find out from an\nalert within minutes, not from a customer complaint.",[21,559,560],{},"A separate line of work is censorship circumvention for business: an entry node\nin Russia, an exit node abroad, SNI-based routing and a custom client for\nWindows and Android that picks a live entry point by itself.",{"title":28,"searchDepth":29,"depth":29,"links":562},[],"Self-hosted infrastructure, private VPN networks for the team, auto-deploy, monitoring and backups. Data never leaves the company perimeter.","🐳",[566,569,572,575,578,581,584,587,590,593,596],{"q":567,"a":568},"How much does a server or VPN setup cost?","A corporate gateway or your own mail on the domain starts at $80, connection guides for staff at $50, a VPN route with entry and exit in different countries at $150. A server for websites on Docker and nginx with backups starts at $160, support at $100 a month. Your own voice, chat and streaming server starts at $650, a service built for load with its own client at $2,500. The exact figure comes in the proposal after a free audit; there is no hourly billing.",{"q":570,"a":571},"What is self-hosted and why does it matter?","It's a private cloud server owned only by the client: nobody can restrict access, shut down the service or peek at the data. Full control instead of someone else's “cloud” — but also full responsibility for setup and maintenance, which is usually what we do.",{"q":573,"a":574},"Do I need my own server for self-hosted solutions?","Not necessarily physical hardware — a VPS from a Russian or foreign provider works fine. We'll help you pick the right option for your budget and task, and set everything up turnkey.",{"q":576,"a":577},"How long does it take to set up a VPN network for a team?","A basic WireGuard setup for a team of up to 10 takes 1-2 days. Complex configurations (several sites, censorship circumvention, a custom client) take up to 5 business days, including instructions and team training.",{"q":579,"a":580},"Do you provide support after the project is delivered?","Yes. After handover — a month of free support. Then you can subscribe to ongoing maintenance: monitoring, updates, backups, fast response to issues.",{"q":582,"a":583},"How do I host several websites on one server so they don't interfere with each other?","One entry nginx accepts all requests, terminates TLS and routes them to the sites. Each site lives in its own container and its own docker network, so the sites cannot see each other. Databases are attached only to the internal networks of their own projects.",{"q":585,"a":586},"How do I restore a team's access to Canva, Notion, Jira and AI tools without public VPNs?","We deploy a corporate gateway on the company's servers: Xray and nginx run on Linux, traffic is encrypted and access is split by role. An employee connects in one click. Corporate data stays inside the perimeter instead of passing through public services.",{"q":588,"a":589},"Can I run mail on my own domain instead of at the host?","Yes. docker-mailserver with Postfix, Dovecot, Rspamd and fail2ban handles receiving and sending, and you read mail in Roundcube or any client. DNS records are required: MX, SPF, DKIM, DMARC and a PTR reverse record. On our own server the host blocks outgoing mail ports: receiving works, while outgoing messages wait in the queue until the route is opened.",{"q":591,"a":592},"What can be done if a VPN is blocked on the path from Russia abroad?","Measurements on 20 August 2026 showed 35–60% of TCP handshakes to our exit server were lost while ping had no loss. We replaced a connection per client with one permanent frp tunnel and split the domains into traffic, subscriptions and admin. The limit of the scheme: if all domains point to one IP address, a block by address takes them all down at once.",{"q":594,"a":595},"Can we run work calls and chats on our own server instead of a cloud messenger?","Yes. We built such a platform: three shared voice channels, calls, groups, screen sharing and chats with a 90-day history. Voice goes directly between participants and is encrypted between browsers, and the stream is delivered over SRT. It all works in the browser with no installation.",{"q":597,"a":598},"How do I avoid losing data on a server failure or being left without HTTPS?","certbot renews Let's Encrypt certificates on a timer, and a hook after renewal makes nginx reload the certificate without a restart. Nightly snapshots cover mail (kept for two weeks) and the database and files of some hosted sites. For the streaming service a separate machine pulls database snapshots and checks that they restore, and the administrator learns about a failure from a Telegram message.",[600,601,602,603,604,605,606,607],"Infrastructure deployed in Docker: an entry nginx, containers and a separate docker network for each service","HTTPS certificates with automatic renewal and expiry checks","Nightly data backups","Container health checks and a Telegram message to the administrator on failure","Role-based access and traffic encryption","Step-by-step connection guides for staff, by platform","Configuration in a git repository; code and data stay with the client","A README written as a technical specification, a CHANGELOG and one month of free support after handover","Self-hosted, private VPN networks, DevOps and monitoring",{},40,"\u002Fen\u002Fuslugi\u002Finfrastruktura",{"visible":79,"note":83,"items":613},[614,622,631],{"title":615,"price":87,"unit":616,"description":617,"includes":618,"featured":94},"Access and mail","per setup","A corporate gateway for staff, your own mail on the domain with DKIM and SPF.",[619,620,621],"Connection guides for staff — from $50","VPN route with entry and exit in different countries — from $150","Settings are documented",{"title":623,"price":624,"unit":625,"description":626,"includes":627,"featured":79},"Server for websites","from $160","per server","Docker, nginx, certificates, backups and monitoring: several sites on one server.",[628,629,630],"Support — from $100 a month","Migration from another host without downtime","Checked after every change",{"title":632,"price":633,"unit":98,"description":634,"includes":635,"featured":94},"Voice, chat, streaming","from $650","A communication server instead of Discord: voice rooms, chats, screen streaming.",[636,637,638],"A service built for load with its own client — from $2,500","Data does not leave for a third-party cloud","Keeps working when public services are blocked",{"title":542,"description":563},"Server, VPN and self-hosted infrastructure setup",[120,642,119,643,644,645,646,647],"Kubernetes","WireGuard","Xray \u002F VLESS","Marzban","Prometheus","Grafana",[649,652,654,657,660,662],{"name":650,"text":651},"Docker Compose","Each service and site runs in its own container and network, so one failure doesn't drag down its neighbors.",{"name":119,"text":653},"A single entry point: it accepts HTTPS, checks access and passes the request to the right service.",{"name":655,"text":656},"certbot","It issues free Let's Encrypt certificates and renews them by itself, with no site downtime.",{"name":658,"text":659},"Xray","A proxy protocol core: it routes and encrypts the traffic of the corporate gateway and the VPN.",{"name":645,"text":661},"A panel where VPN users and subscriptions are created and traffic is tracked.",{"name":663,"text":664},"frp","It keeps a permanent tunnel between nodes where a direct cross-border connection gets cut.","en\u002Fuslugi\u002Finfrastruktura",[667,669,672,675,678,681],{"title":138,"text":668},"We look at what runs now and where, which services are unavailable or get in each other's way, and which data must stay inside the perimeter. Before any estimate, you get a description of the task.",{"title":670,"text":671},"Infrastructure design","We decide which services go on which servers, how to split them into containers and networks, and who gets access with which role.",{"title":673,"text":674},"Deployment in Docker","We install the entry nginx, the service containers and HTTPS certificates with automatic renewal on the client's server or ours.",{"title":676,"text":677},"Access and protection","We set up access roles and traffic encryption, and for mail and SSH login we turn on brute-force protection (fail2ban).",{"title":679,"text":680},"Backups and monitoring","We enable nightly data snapshots, container health checks and alerts to the administrator about failures.",{"title":153,"text":682},"We hand over the configuration in a repository, a README written as a technical specification, a CHANGELOG and connection guides. Support is free for a month after handover.",[684,689,694,698,703,708],{"id":685,"title":686,"text":687,"term":28,"price":28,"case":688},"infrastruktura-01","Corporate gateway for access to work services","The gateway runs on the company's Linux servers: Xray handles routing and encryption, nginx sits at the entrance. Access is split by employee role, and connecting takes one click. Corporate data stays inside the perimeter.","korporativnyi-shlyuz",{"id":690,"title":691,"text":692,"term":28,"price":28,"case":693},"infrastruktura-02","A server for several websites on Docker and nginx","A single entry nginx accepts HTTP and HTTPS and routes requests to the sites. Each site lives in its own container and its own docker network, and Let's Encrypt certificates renew themselves. The configuration is kept in a git repository.","server-saitov",{"id":695,"title":696,"text":697,"term":28,"price":28,"case":693},"infrastruktura-03","Your own mail on your domain","A docker-mailserver container with Postfix, Dovecot, Rspamd and fail2ban receives and sends mail, and you read it in Roundcube or a mail client. We set up the DNS records: MX, SPF, DKIM, DMARC and PTR. Spam is not discarded but placed in the Junk folder.",{"id":699,"title":700,"text":701,"term":28,"price":28,"case":702},"infrastruktura-04","VPN route: an entry node in Russia and an exit abroad","nginx on the entry node splits websites, the panel and the VPN by SNI on one port, and a permanent frp tunnel carries all traffic to the exit server. A separate Marzban node with VLESS Reality runs autonomously. A domain change is one command with no dropped sessions.","vpn-trassa",{"id":704,"title":705,"text":706,"term":28,"price":28,"case":707},"infrastruktura-05","Voice, chat and streaming on your own server","A closed browser-based platform: SRT streaming, viewing over HLS and WebRTC, voice directly between participants (up to 10 people per channel), chats and access roles. Everything runs in Docker Compose, and a 90-day chat history sits in SQLite on your server.","toshastream",{"id":709,"title":710,"text":711,"term":28,"price":28,"case":712},"infrastruktura-06","VPN connection guides for staff and customers","Five static pages: Android, iPhone and iPad, Windows, macOS and Linux. They open in a Telegram Mini App from the bot, so a person with no experience doesn't have to write to support. No build step or server is needed for them.","instrukcii-vpn","JG5zZ3qD4DGXS1eBX08MYQpsX9LAnaRviTFYt-kq6q8",{"id":715,"title":716,"audience":717,"body":727,"description":737,"emoji":738,"extension":33,"faq":739,"includes":764,"lead":771,"meta":772,"navigation":79,"order":773,"path":774,"pricing":775,"seo":800,"serviceType":801,"stack":802,"stackNotes":809,"stem":823,"steps":824,"works":841,"__hash__":865},"services_en\u002Fen\u002Fuslugi\u002Fprilozheniya.md","Android and Windows apps",[718,721,724],{"title":719,"text":720},"Users should not have to deal with settings","We hide the complexity behind one button and sensible defaults: the subscription is entered once, and the app picks a working entry point and updates itself. Our VPN client works this way.",{"title":722,"text":723},"A fleet of Windows 7 machines","We build a separate client on .NET Framework 4.8 that runs on the old Windows and shares the logic with the main version.",{"title":725,"text":726},"You learn about crashes from users, not from the app","We build in over-the-air updates with signature checks and crash reports sent to your own server. The developer sees the crash and the log before a user writes to support.",{"type":18,"value":728,"toc":735},[729,732],[21,730,731],{},"Application development for Windows (including Windows 7 — for clients with an\nolder fleet) and Android. One codebase of logic for all platforms, a native UI\nfor each, over-the-air self-updates and crash reports to the developer — so we\nlearn about a problem before the user does.",[21,733,734],{},"Done so far: a VPN client with a swappable engine for three platforms and an\naudio player with direct sound output to a USB DAC, bypassing the system mixer.",{"title":28,"searchDepth":29,"depth":29,"links":736},[],"Application development for Windows (including Windows 7) and Android: from a VPN client with a swappable engine to an audio player with direct USB DAC output.","🖥️",[740,743,746,749,752,755,758,761],{"q":741,"a":742},"Why Windows 7 — it's been unsupported for years?","Some clients — checkouts, warehouses, manufacturing — run a fleet on Windows 7, and nobody will replace it for one program. We build a separate client on .NET Framework 4.8 that runs there, sharing the logic with the main version.",{"q":744,"a":745},"Do you publish to Google Play and app stores?","Both ways: through stores or bypassing them — APK distribution via a bot link with over-the-air self-updates and a signed release list. For internal corporate apps the second path is usually simpler.",{"q":747,"a":748},"Will the app update itself?","Yes: we build in self-updates with signature verification — on Android the install is silent, on Windows only changed files are downloaded, and on failure the update rolls back.",{"q":750,"a":751},"Can one app be built for Android and Windows at once?","The logic can be shared while the interface is built per platform. In our VPN client, Android is written in Kotlin, Windows 10\u002F11 in C# and WPF, and Windows 7 on .NET Framework 4.8, and the Windows clients share common logic. A common layer sits between the interface and the VPN engine, so the engine can be replaced without rewriting the app.",{"q":753,"a":754},"What happens to traffic if the VPN client or its engine crashes?","On Windows in TUN mode, WFP 'leak protection' filters are active: outgoing traffic outside the tunnel is blocked, and when the engine drops or restarts, programs see 'no internet' rather than the real address. On Android the tunnel stays up until 'Disconnect' is pressed, and the system 'always-on VPN' protects traffic before the app starts: the main screen reminds the user if it is off.",{"q":756,"a":757},"How can we collect crash reports without third-party services?","The app sends the log, version, network state and crash reason with a token to a receiver on your own server. Subscription links, keys, passwords and tokens are masked on the device when the log is written, before sending. If the receiver is unavailable, the VPN client queues reports on disk: up to 20 files and 50 MB.",{"q":759,"a":760},"Can Android play audio to a USB DAC without alteration?","Yes. The OshaPlay player takes over the DAC through the USB Host API and sends audio in isochronous transfers, bypassing the system mixer, which would otherwise resample every track to 48 kHz. Direct output works with UAC2 DACs using adaptive or synchronous transfer, while asynchronous DACs and UAC1 still play through the system.",{"q":762,"a":763},"How much does an app cost and how fast can it be built?","An Android app starts at $2,100. A VPN client for Android and Windows runs from $4,900 to $18,000 and above depending on requirements: server-driven updates, statistics, a Windows 7 client. Store publishing starts at $500. Over-the-air updates and a crash receiver as separate jobs are quoted in the proposal after a free audit. No hourly billing: the price is fixed for the result.",[765,766,767,768,769,770,76],"An app for Android, Windows 10\u002F11 or Windows 7 with a native UI for each platform","Over-the-air self-update with a signed release list","Crash reports and logs sent to your own receiver, with secrets masked on the device","A 'How to use' section and a version history inside the app","Installation from a file: an APK for Android, an archive with a built-in installer for Windows","A README written as a technical specification, with known limitations, and a CHANGELOG","Desktop and mobile development for the task at hand",{},50,"\u002Fen\u002Fuslugi\u002Fprilozheniya",{"visible":79,"note":83,"items":776},[777,785,793],{"title":778,"price":779,"unit":98,"description":780,"includes":781,"featured":94},"Android app","from $2,100","A native Kotlin app: hardware access, offline data, custom design.",[782,783,784],"Crash and log receiver — quoted after the audit","Over-the-air updates — quoted after the audit","Source code is handed to the client",{"title":786,"price":787,"unit":98,"description":788,"includes":789,"featured":79},"VPN client Android + Windows","$4,900 – $18,000","The price depends on requirements: server-driven updates, statistics, a Windows 7 client.",[790,791,792],"Two engines and endpoint switching without restart","Crash reports and logs to your server","Signed builds, over-the-air updates",{"title":794,"price":459,"unit":795,"description":796,"includes":797,"featured":94},"Store publishing","per app","RuStore and Google Play: signing, build, store page, moderation replies.",[798,799,628],"One-click CI build","Screenshots and descriptions prepared",{"title":716,"description":737},"Android and Windows application development",[803,804,805,806,807,808],"Kotlin","Jetpack Compose","C# \u002F .NET 10","WPF",".NET Framework 4.8","C++ (NDK)",[810,813,816,818,820],{"name":811,"text":812},"Kotlin and Jetpack Compose","The basis of our Android apps: an adaptive interface that follows the system theme.",{"name":814,"text":815},"C# \u002F .NET 10 and WPF","The basis of the Windows 10\u002F11 client: native windows and the engine launched as a separate process.",{"name":807,"text":817},"Lets us build a client that runs on Windows 7, where newer .NET versions do not start.",{"name":808,"text":819},"Where timing and precision matter, such as the audio stream to a USB DAC, the code is written in C++ for Android.",{"name":821,"text":822},"Xray and sing-box","Swappable VPN engines: the client talks to them through a thin layer, so one engine can be replaced with another.","en\u002Fuslugi\u002Fprilozheniya",[825,827,830,833,836,839],{"title":138,"text":826},"You describe the task: platforms, devices, what already exists. We go through it and state the scope of work.",{"title":828,"text":829},"Platforms and architecture","We decide which logic is shared and what is separate on each platform, and which constraints apply, for example an old Windows or a specific DAC.",{"title":831,"text":832},"Development on real devices","We build the app and check it beyond the emulator. Test builds and logs come from real phones and computers.",{"title":834,"text":835},"Testing at the client","Test builds are installed from a file. Tester mode sends crash and anomaly reports, and we fix bugs from them.",{"title":837,"text":838},"Release and distribution","We prepare the APK and the Windows installer, sign the release list and turn on self-update.",{"title":153,"text":840},"We hand over the code, documentation and access to the report receiver. The first month of support is free, after that maintenance is by agreement.",[842,847,852,856,860],{"id":843,"title":844,"text":845,"term":28,"price":28,"case":846},"prilozheniya-01","Android audio player with USB DAC output","A player for a personal library that talks to a USB DAC directly over USB Audio Class, bypassing the Android system mixer, and delivers the track bit-perfect. It has its own resampler, a Hi-Res fake check based on the spectrum, playlists, search across four sources and an audiobook section. It ends with an APK that then updates itself.","oshaplay",{"id":848,"title":849,"text":850,"term":28,"price":28,"case":851},"prilozheniya-02","VPN client for Android and Windows","An app with one 'Connect' button: the subscription is entered once, the entry point is chosen by a race, the client switches by itself when the connection drops, and Russian sites go direct. The Xray or sing-box engine is plugged in through a thin layer, so it can be replaced. The client is currently in testing with users.","vpn-klient",{"id":853,"title":854,"text":855,"term":28,"price":28,"case":851},"prilozheniya-03","Windows 7 client","A separate build on .NET Framework 4.8 and WinForms that shares its logic with the main client. Windows 7 has no TUN, so it works through the system proxy, with sing-box as the engine. It was built and run on Windows 11 and has not yet been tested on a real Windows 7.",{"id":857,"title":858,"text":859,"term":28,"price":28,"case":851},"prilozheniya-04","Over-the-air app self-update","The release list is signed with a key, and the public key is built into the app: a forged list is rejected. Android downloads the APK for its own architecture, Windows downloads only changed files and rolls the replacement back on failure. There is a tester channel and a regular one.",{"id":861,"title":862,"text":863,"term":28,"price":28,"case":864},"prilozheniya-05","Crash and log report receiver","A small Python service in Docker on your server, where the app sends the log, version, network state and crash reason with a token. Secrets are masked on the device before sending, and if the connection is lost the report waits in a queue on disk. It ends with a script the developer uses to read the reports.","oshaplay-diag","iwAyqAoo7sDhBBEBTEBvDLJ3CBhBs9Lz7AwUs7F_4XE",[867,927,985,1043,1101],{"id":5,"title":6,"audience":868,"body":872,"description":31,"emoji":32,"extension":33,"faq":880,"includes":892,"lead":77,"meta":893,"navigation":79,"order":80,"path":81,"pricing":894,"seo":902,"serviceType":113,"stack":903,"stackNotes":904,"stem":135,"steps":911,"works":918,"__hash__":195},[869,870,871],{"title":9,"text":10},{"title":12,"text":13},{"title":15,"text":16},{"type":18,"value":873,"toc":878},[874,876],[21,875,23],{},[21,877,26],{},{"title":28,"searchDepth":29,"depth":29,"links":879},[],[881,882,883,884,885,886,887,888,889,890,891],{"q":36,"a":37},{"q":39,"a":40},{"q":42,"a":43},{"q":45,"a":46},{"q":48,"a":49},{"q":51,"a":52},{"q":54,"a":55},{"q":57,"a":58},{"q":60,"a":61},{"q":63,"a":64},{"q":66,"a":67},[69,70,71,72,73,74,75,76],{},{"visible":79,"note":83,"items":895},[896,898,900],{"title":86,"price":87,"unit":88,"description":89,"includes":897,"featured":94},[91,92,93],{"title":96,"price":97,"unit":98,"description":99,"includes":899,"featured":79},[101,102,103],{"title":105,"price":106,"unit":98,"description":107,"includes":901,"featured":94},[109,110,111],{"title":6,"description":31},[115,116,117,118,119,120,121],[905,906,907,908,909,910],{"name":115,"text":124},{"name":117,"text":126},{"name":118,"text":128},{"name":119,"text":130},{"name":120,"text":132},{"name":121,"text":134},[912,913,914,915,916,917],{"title":138,"text":139},{"title":141,"text":142},{"title":144,"text":145},{"title":147,"text":148},{"title":150,"text":151},{"title":153,"text":154},[919,920,921,922,923,924,925,926],{"id":157,"title":158,"text":159,"term":28,"price":28,"case":160},{"id":162,"title":163,"text":164,"term":28,"price":28,"case":165},{"id":167,"title":168,"text":169,"term":28,"price":28,"case":170},{"id":172,"title":173,"text":174,"term":28,"price":28,"case":175},{"id":177,"title":178,"text":179,"term":28,"price":28,"case":180},{"id":182,"title":183,"text":184,"term":28,"price":28,"case":185},{"id":187,"title":188,"text":189,"term":28,"price":28,"case":190},{"id":192,"title":193,"text":194,"term":28,"price":28,"case":180},{"id":197,"title":198,"audience":928,"body":932,"description":219,"emoji":220,"extension":33,"faq":940,"includes":951,"lead":261,"meta":952,"navigation":79,"order":263,"path":264,"pricing":953,"seo":961,"serviceType":291,"stack":962,"stackNotes":963,"stem":316,"steps":970,"works":977,"__hash__":371},[929,930,931],{"title":201,"text":202},{"title":204,"text":205},{"title":207,"text":208},{"type":18,"value":933,"toc":938},[934,936],[21,935,213],{},[21,937,216],{},{"title":28,"searchDepth":29,"depth":29,"links":939},[],[941,942,943,944,945,946,947,948,949,950],{"q":223,"a":224},{"q":226,"a":227},{"q":229,"a":230},{"q":232,"a":233},{"q":235,"a":236},{"q":238,"a":239},{"q":241,"a":242},{"q":244,"a":245},{"q":247,"a":248},{"q":250,"a":251},[253,254,255,256,257,258,259,260],{},{"visible":79,"note":83,"items":954},[955,957,959],{"title":268,"price":87,"unit":98,"description":269,"includes":956,"featured":94},[271,272,273],{"title":275,"price":276,"unit":98,"description":277,"includes":958,"featured":79},[279,280,110],{"title":282,"price":283,"unit":284,"description":285,"includes":960,"featured":94},[287,288,289],{"title":198,"description":219},[293,294,295,296,297,120],[964,965,966,967,968,969],{"name":300,"text":301},{"name":303,"text":304},{"name":306,"text":307},{"name":309,"text":310},{"name":312,"text":313},{"name":120,"text":315},[971,972,973,974,975,976],{"title":138,"text":319},{"title":321,"text":322},{"title":324,"text":325},{"title":327,"text":328},{"title":330,"text":331},{"title":333,"text":334},[978,979,980,981,982,983,984],{"id":337,"title":338,"text":339,"term":28,"price":28,"case":340},{"id":342,"title":343,"text":344,"term":28,"price":28,"case":345},{"id":347,"title":348,"text":349,"term":28,"price":28,"case":350},{"id":352,"title":353,"text":354,"term":28,"price":28,"case":355},{"id":357,"title":358,"text":359,"term":28,"price":28,"case":360},{"id":362,"title":363,"text":364,"term":28,"price":28,"case":365},{"id":367,"title":368,"text":369,"term":28,"price":28,"case":370},{"id":373,"title":374,"audience":986,"body":990,"description":395,"emoji":396,"extension":33,"faq":998,"includes":1009,"lead":436,"meta":1010,"navigation":79,"order":438,"path":439,"pricing":1011,"seo":1019,"serviceType":466,"stack":1020,"stackNotes":1021,"stem":488,"steps":1028,"works":1035,"__hash__":539},[987,988,989],{"title":377,"text":378},{"title":380,"text":381},{"title":383,"text":384},{"type":18,"value":991,"toc":996},[992,994],[21,993,389],{},[21,995,392],{},{"title":28,"searchDepth":29,"depth":29,"links":997},[],[999,1000,1001,1002,1003,1004,1005,1006,1007,1008],{"q":399,"a":400},{"q":402,"a":403},{"q":405,"a":406},{"q":408,"a":409},{"q":411,"a":412},{"q":414,"a":415},{"q":417,"a":418},{"q":420,"a":421},{"q":423,"a":424},{"q":426,"a":427},[429,430,431,432,433,434,75,435],{},{"visible":79,"note":83,"items":1012},[1013,1015,1017],{"title":443,"price":87,"unit":444,"description":445,"includes":1014,"featured":94},[447,448,449],{"title":451,"price":276,"unit":98,"description":452,"includes":1016,"featured":79},[454,455,456],{"title":458,"price":459,"unit":98,"description":460,"includes":1018,"featured":94},[462,463,464],{"title":374,"description":395},[468,469,470,471,472,293],[1022,1023,1024,1025,1026,1027],{"name":468,"text":475},{"name":469,"text":477},{"name":470,"text":479},{"name":293,"text":481},{"name":483,"text":484},{"name":486,"text":487},[1029,1030,1031,1032,1033,1034],{"title":138,"text":491},{"title":493,"text":494},{"title":496,"text":497},{"title":499,"text":500},{"title":502,"text":503},{"title":153,"text":505},[1036,1037,1038,1039,1040,1041,1042],{"id":508,"title":509,"text":510,"term":28,"price":28,"case":511},{"id":513,"title":514,"text":515,"term":28,"price":28,"case":511},{"id":517,"title":518,"text":519,"term":28,"price":28,"case":511},{"id":521,"title":522,"text":523,"term":28,"price":28,"case":524},{"id":526,"title":527,"text":528,"term":28,"price":28,"case":529},{"id":531,"title":532,"text":533,"term":28,"price":28,"case":534},{"id":536,"title":537,"text":538,"term":28,"price":28,"case":534},{"id":541,"title":542,"audience":1044,"body":1048,"description":563,"emoji":564,"extension":33,"faq":1056,"includes":1068,"lead":608,"meta":1069,"navigation":79,"order":610,"path":611,"pricing":1070,"seo":1078,"serviceType":640,"stack":1079,"stackNotes":1080,"stem":665,"steps":1087,"works":1094,"__hash__":713},[1045,1046,1047],{"title":545,"text":546},{"title":548,"text":549},{"title":551,"text":552},{"type":18,"value":1049,"toc":1054},[1050,1052],[21,1051,557],{},[21,1053,560],{},{"title":28,"searchDepth":29,"depth":29,"links":1055},[],[1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067],{"q":567,"a":568},{"q":570,"a":571},{"q":573,"a":574},{"q":576,"a":577},{"q":579,"a":580},{"q":582,"a":583},{"q":585,"a":586},{"q":588,"a":589},{"q":591,"a":592},{"q":594,"a":595},{"q":597,"a":598},[600,601,602,603,604,605,606,607],{},{"visible":79,"note":83,"items":1071},[1072,1074,1076],{"title":615,"price":87,"unit":616,"description":617,"includes":1073,"featured":94},[619,620,621],{"title":623,"price":624,"unit":625,"description":626,"includes":1075,"featured":79},[628,629,630],{"title":632,"price":633,"unit":98,"description":634,"includes":1077,"featured":94},[636,637,638],{"title":542,"description":563},[120,642,119,643,644,645,646,647],[1081,1082,1083,1084,1085,1086],{"name":650,"text":651},{"name":119,"text":653},{"name":655,"text":656},{"name":658,"text":659},{"name":645,"text":661},{"name":663,"text":664},[1088,1089,1090,1091,1092,1093],{"title":138,"text":668},{"title":670,"text":671},{"title":673,"text":674},{"title":676,"text":677},{"title":679,"text":680},{"title":153,"text":682},[1095,1096,1097,1098,1099,1100],{"id":685,"title":686,"text":687,"term":28,"price":28,"case":688},{"id":690,"title":691,"text":692,"term":28,"price":28,"case":693},{"id":695,"title":696,"text":697,"term":28,"price":28,"case":693},{"id":699,"title":700,"text":701,"term":28,"price":28,"case":702},{"id":704,"title":705,"text":706,"term":28,"price":28,"case":707},{"id":709,"title":710,"text":711,"term":28,"price":28,"case":712},{"id":715,"title":716,"audience":1102,"body":1106,"description":737,"emoji":738,"extension":33,"faq":1114,"includes":1123,"lead":771,"meta":1124,"navigation":79,"order":773,"path":774,"pricing":1125,"seo":1133,"serviceType":801,"stack":1134,"stackNotes":1135,"stem":823,"steps":1141,"works":1148,"__hash__":865},[1103,1104,1105],{"title":719,"text":720},{"title":722,"text":723},{"title":725,"text":726},{"type":18,"value":1107,"toc":1112},[1108,1110],[21,1109,731],{},[21,1111,734],{},{"title":28,"searchDepth":29,"depth":29,"links":1113},[],[1115,1116,1117,1118,1119,1120,1121,1122],{"q":741,"a":742},{"q":744,"a":745},{"q":747,"a":748},{"q":750,"a":751},{"q":753,"a":754},{"q":756,"a":757},{"q":759,"a":760},{"q":762,"a":763},[765,766,767,768,769,770,76],{},{"visible":79,"note":83,"items":1126},[1127,1129,1131],{"title":778,"price":779,"unit":98,"description":780,"includes":1128,"featured":94},[782,783,784],{"title":786,"price":787,"unit":98,"description":788,"includes":1130,"featured":79},[790,791,792],{"title":794,"price":459,"unit":795,"description":796,"includes":1132,"featured":94},[798,799,628],{"title":716,"description":737},[803,804,805,806,807,808],[1136,1137,1138,1139,1140],{"name":811,"text":812},{"name":814,"text":815},{"name":807,"text":817},{"name":808,"text":819},{"name":821,"text":822},[1142,1143,1144,1145,1146,1147],{"title":138,"text":826},{"title":828,"text":829},{"title":831,"text":832},{"title":834,"text":835},{"title":837,"text":838},{"title":153,"text":840},[1149,1150,1151,1152,1153],{"id":843,"title":844,"text":845,"term":28,"price":28,"case":846},{"id":848,"title":849,"text":850,"term":28,"price":28,"case":851},{"id":853,"title":854,"text":855,"term":28,"price":28,"case":851},{"id":857,"title":858,"text":859,"term":28,"price":28,"case":851},{"id":861,"title":862,"text":863,"term":28,"price":28,"case":864},{"id":1155,"contacts":1156,"extension":1162,"faq":1163,"fits":1176,"meta":1234,"process":1235,"stack":1254,"stats":1398,"stem":1411,"why":1412,"__hash__":1418},"site_en\u002Fen\u002Fsite.yml",{"telegram":1157,"telegramUrl":1158,"email":1159,"github":1160,"githubUrl":1161},"@tosha_DevUnit","https:\u002F\u002Ft.me\u002Ftosha_DevUnit","anton@devunit-lab.ru","github.com\u002FToshaToshiba","https:\u002F\u002Fgithub.com\u002FToshaToshiba","yml",[1164,1166,1168,1170,1173],{"q":570,"a":1165},"It's your own private cloud server, and you're the only owner: nobody can restrict your access, shut down the service or peek at your data. Full control instead of someone else's “cloud” — but also full responsibility for setup and maintenance, which is usually what we do.",{"q":573,"a":1167},"Not necessarily physical hardware — a VPS from any provider you trust works fine. We'll help you pick the right option for your budget and task, and set everything up turnkey.",{"q":579,"a":1169},"Yes. After handover you get a month of free support: bug fixes and consultations. After that you can subscribe to ongoing maintenance — monitoring, updates, backups, fast response to issues.",{"q":1171,"a":1172},"How does the work go and what do you need from us?","A request on Telegram, a short audit call, a work plan with a price range — and only then development. From you we need access to your current systems and a person who makes decisions; we take care of the rest.",{"q":1174,"a":1175},"Can you migrate data from Notion to a self-hosted alternative?","Yes, we migrate to Outline — pages, knowledge bases and images, preserving structure and formatting. It usually takes 3-7 days depending on data volume.",[1177,1181,1184,1187,1190,1195,1198,1201,1204,1208,1211,1214,1218,1221,1224,1227,1231],{"who":1178,"text":1179,"direction":1180,"case":160},"Online stores and retail","You sell by phone and chat, and stock lives in notes. You need a store the owner runs from a phone.","sajty",{"who":1182,"text":1183,"direction":1180,"case":165},"Brands and product makers","Ads send people from the street to their phones. You need a light storefront with a catalog and ordering in a familiar messenger.",{"who":1185,"text":1186,"direction":1180,"case":170},"Companies with a legacy website","The site runs on an outdated CMS, updates are risky and search rankings must survive. You need a migration that keeps the URLs.",{"who":1188,"text":1189,"direction":1180,"case":175},"Independent professionals","You need one page instead of long explanations in chat: who you are, what you do and how to reach you.",{"who":1191,"text":1192,"direction":1193,"case":1194},"Subscription services","Payments are confirmed by hand and renewals live in a spreadsheet. You need a bot that takes payment, extends access and reconciles on its own.","boty","tech-poly-vpn",{"who":1196,"text":1197,"direction":1193,"case":345},"Internal IT departments","Requests arrive in DMs and hallways, and some get lost. You need ticket tracking, an asset register and a manager report without a separate system.",{"who":1199,"text":1200,"direction":1193,"case":355},"Clubs, salons and service businesses","You need a loyalty card with no plastic and no app: visits, rewards and records right in Telegram.",{"who":1202,"text":1203,"direction":1193,"case":350},"Agents who work with documents","The same document is edited by hand for every client. You need PDF processing that leaves the data and the signature intact.",{"who":1205,"text":1206,"direction":1207,"case":524},"Marketplace sellers","Delivery slots are gone in minutes, and stock and orders are checked by hand. You need monitoring and automatic alerts.","avtomatizaciya",{"who":1209,"text":1210,"direction":1207,"case":511},"Small businesses taking leads in a messenger","Leads from Telegram are copied into the CRM and a spreadsheet by hand. You need a pipeline where the data moves itself.",{"who":1212,"text":1213,"direction":1207,"case":529},"Manufacturing companies","Calculations are done by hand from tables and standards. You need software that always calculates the same way and is covered by tests.",{"who":1215,"text":1216,"direction":1217,"case":688},"IT companies and distributed teams","Work tools stopped opening and everyone has a different workaround. You need shared access on your own server.","infrastruktura",{"who":1219,"text":1220,"direction":1217,"case":693},"Companies with several websites","Sites share a server with other services and get in each other's way. You need a dedicated server with isolation, certificates and backups.",{"who":1222,"text":1223,"direction":1217,"case":851},"Gaming cafés and LAN centers","Games, launchers and Discord must open on every machine without lag. You need a VPN for the venue with split tunneling: games go direct, everything else through a secure channel.",{"who":1225,"text":1226,"direction":1217,"case":707},"Teams that need their own communications","You need voice, chat and streaming on your own server, with roles and closed access, and no third-party cloud.",{"who":1228,"text":1229,"direction":1230,"case":851},"Services that need their own app","A third-party client scares users with settings. You need an app under your own name with one button and self-updates.","prilozheniya",{"who":1232,"text":1233,"direction":1230,"case":851},"Companies with a fleet of old PCs","Some machines still run Windows 7 and modern software will not install. You need a separate client for the old system.",{},[1236,1239,1242,1245,1248,1251],{"title":1237,"text":1238},"Request","Briefly describe your task on Telegram or by email — 2-3 minutes, no unnecessary fields.",{"title":1240,"text":1241},"Audit","We hop on a call, review your current infrastructure and bottlenecks — usually 1-3 days.",{"title":1243,"text":1244,"optional":79},"Demo","For larger tasks we can build a working prototype or walk you through a similar case live, before pricing the estimate.",{"title":1246,"text":1247},"Proposal","We send a concrete work plan, timeline and price range — no “it depends”.",{"title":1249,"text":1250},"Implementation","We configure, test and battle-test the solution under your real load, not in theory.",{"title":1252,"text":1253},"Handover & support","We hand it over with documentation and training, then stay in touch under SLA — we don't disappear after payment.",[1255,1277,1289,1301,1316,1334,1351,1369,1385],{"group":1256,"icon":1257,"items":1258},"Backend & AI","🧠",[1259,1262,1266,1270,1274],{"name":293,"emoji":1260,"note":1261},"🐍","FastAPI, aiogram",{"name":1263,"emoji":1264,"note":1265},"Go","🐹","media services",{"name":1267,"emoji":1268,"note":1269},"C++","⚡","firmware, calculations",{"name":1271,"emoji":1272,"note":1273},"Node.js","🟢","Nuxt, integrations",{"name":309,"emoji":1275,"note":1276},"✨","AI in bots and automation",{"group":1278,"icon":32,"items":1279},"Web",[1280,1283,1286],{"name":115,"emoji":1281,"note":1282},"💚","server-rendered websites",{"name":116,"emoji":1284,"note":1285},"🔷","typed front and back end",{"name":121,"emoji":1287,"note":1288},"🔍","Schema.org, indexing",{"group":1290,"icon":738,"items":1291},"Apps",[1292,1294,1298],{"name":803,"emoji":220,"note":1293},"Android, Jetpack Compose",{"name":1295,"emoji":1296,"note":1297},"C# \u002F .NET","🟣","Windows 10\u002F11, WPF",{"name":807,"emoji":1299,"note":1300},"🪟","Windows 7",{"group":1302,"icon":564,"items":1303},"Servers & containers",[1304,1308,1310,1313],{"name":1305,"emoji":1306,"note":1307},"Ubuntu","🐧","server OS",{"name":120,"emoji":564,"note":1309},"Compose",{"name":119,"emoji":1311,"note":1312},"🧭","web server, proxy",{"name":642,"emoji":1314,"note":1315},"☸️","K3s, Helm",{"group":1317,"icon":1318,"items":1319},"Media & communications","🎥",[1320,1323,1327,1330],{"name":1321,"emoji":1318,"note":1322},"SRT \u002F HLS \u002F WHEP","low-latency video",{"name":1324,"emoji":1325,"note":1326},"WebRTC","🎙️","real-time voice",{"name":312,"emoji":1328,"note":1329},"🎞️","media processing",{"name":1331,"emoji":1332,"note":1333},"Asterisk","☎️","IP telephony",{"group":1335,"icon":396,"items":1336},"Automation & VPN",[1337,1339,1342,1345,1348],{"name":468,"emoji":396,"note":1338},"self-hosted",{"name":643,"emoji":1340,"note":1341},"🔒","fast VPN tunnel",{"name":644,"emoji":1343,"note":1344},"🥷","censorship circumvention",{"name":645,"emoji":1346,"note":1347},"🎛️","VPN panel",{"name":663,"emoji":1349,"note":1350},"🚇","tunnels, port forwarding",{"group":1352,"icon":1353,"items":1354},"Databases","🗄️",[1355,1358,1361,1365],{"name":296,"emoji":1356,"note":1357},"🐘","primary database",{"name":297,"emoji":1359,"note":1360},"🐬","billing, accounting",{"name":1362,"emoji":1363,"note":1364},"Redis","🔴","cache, queues",{"name":1366,"emoji":1367,"note":1368},"SQLite","🗃️","compact services",{"group":1370,"icon":1371,"items":1372},"Embedded & IoT","🔌",[1373,1377,1381],{"name":1374,"emoji":1375,"note":1376},"ESP32","📟","custom firmware",{"name":1378,"emoji":1379,"note":1380},"MQTT","📡","device messaging",{"name":1382,"emoji":1383,"note":1384},"LoRa","📶","links without Wi-Fi",{"group":1386,"icon":1387,"items":1388},"Monitoring & CI\u002FCD","📈",[1389,1391,1394],{"name":647,"emoji":1387,"note":1390},"dashboards, graphs",{"name":646,"emoji":1392,"note":1393},"🔥","metrics, alerts",{"name":1395,"emoji":1396,"note":1397},"GitHub Actions","🐙","auto-deploy",[1399,1402,1405,1408],{"value":1400,"label":1401},"30+","case studies — from loyalty programs to corporate billing",{"value":1403,"label":1404},"24\u002F7","automation runs without your involvement",{"value":1406,"label":1407},"1 mo","free support after project handover",{"value":1409,"label":1410},"100%","code and data stay with you, not the contractor","en\u002Fsite",[1413,1414,1415,1416,1417],"Engineers, not a lone freelancer who is “sometimes” available","Fast to react — a small team, zero bureaucracy and approvals","We hand over the project with documentation and a month of support","Remote-first: written async updates in English, EU-friendly time zone (GMT+3)","Infrastructure with no single point of failure","KT-UokRfkNAUSDo5Y4CK3e0sK7SX34kdtiLJXLkgP5s",[1420,1718,1958,2254,2525,2764,3009,3233,3480,3716,3916,4202,4421,4545,4733,4982,5228,5466,5598,5832,6032,6277,6537,6772,7034,7251,7504,7852,8116,8356,8646],{"id":1421,"title":1422,"body":1423,"client":28,"cover":28,"date":1672,"demoUrl":28,"description":28,"direction":1193,"emoji":1673,"extension":33,"faq":1674,"featured":79,"links":1693,"meta":1694,"navigation":79,"nda":94,"order":80,"own":79,"path":1695,"related":1696,"results":1697,"seo":1707,"shot":28,"shotAlt":28,"stack":1708,"status":1713,"stem":1714,"subtitle":1715,"summary":1716,"__hash__":1717},"cases_en\u002Fen\u002Fkeysy\u002Ffinansovyi-assistent.md","Finance Assistant: PDF bank statements to a Telegram budget",{"type":18,"value":1424,"toc":1654},[1425,1430,1433,1436,1440,1443,1514,1518,1523,1526,1530,1533,1537,1540,1544,1547,1551,1554,1558,1561,1565,1568,1572,1575,1579,1599,1603,1644,1648,1651],[1426,1427,1429],"h2",{"id":1428},"the-task","The task",[21,1431,1432],{},"Money flows through several banks, relatives' cards and cash. Each banking app shows only its own slice. Transfers between your own accounts look like income and expenses, and a hand-kept spreadsheet usually dies after a couple of weeks. On top of that, some spending belongs to work and your own projects and some to personal life, and in a bank app they are mixed together.",[21,1434,1435],{},"What was needed was one honest picture of the money: how much came in, where it went, which part is business and which is personal. And without typing in every operation by hand: a bank statement should simply be dropped into the chat, and a small expense told in an ordinary sentence.",[1426,1437,1439],{"id":1438},"the-solution","The solution",[21,1441,1442],{},"Finance Assistant is our own Telegram bot with a web dashboard on top of the same data. Implemented in version 1.14.0:",[1444,1445,1446,1454,1460,1466,1472,1478,1484,1490,1496,1502,1508],"ul",{},[1447,1448,1449,1453],"li",{},[1450,1451,1452],"strong",{},"Statements from four banks."," PDFs from VTB (credit card, credit card account, debit cards), Sberbank, T-Bank and Ozon Bank. The bank is detected automatically, and a universal CSV and XLSX parser is the fallback.",[1447,1455,1456,1459],{},[1450,1457,1458],{},"A register of accounts and cards."," Account number, contract, last four card digits, owner. The next statement lands on the right account by itself; for a VTB credit card the debt, limit, rate and payment date are shown.",[1447,1461,1462,1465],{},[1450,1463,1464],{},"Expenses in words."," \"Coffee 250 yesterday\", \"+65000 salary cash\", \"recurring expenses 2500 subscription\". One message can hold several operations, and one purchase can be split across categories.",[1447,1467,1468,1471],{},[1450,1469,1470],{},"Voice and receipt photos."," Gemini transcribes speech; a receipt becomes line items with categories that are matched to the card operation.",[1447,1473,1474,1477],{},[1450,1475,1476],{},"Account in words."," \"By credit card\", \"from Sber\", \"card 5515\": the record goes to the right account, and the bot asks rather than guessing when it is ambiguous.",[1447,1479,1480,1483],{},[1450,1481,1482],{},"Night run."," Unknown merchants go to the model in one request, categories are set by morning, and doubtful ones arrive as a list of buttons.",[1447,1485,1486,1489],{},[1450,1487,1488],{},"Questions about your money."," \"What do I spend the most on?\", \"compare with last month\": the answer is built from the bot's own data, and the bot gives no investment advice.",[1447,1491,1492,1495],{},[1450,1493,1494],{},"Debts, recurring payments and budgets."," A debt ledger per person, payments with a charge day, category budgets with warnings at 80 % and 100 %.",[1447,1497,1498,1501],{},[1450,1499,1500],{},"Directions and reinvestments."," Income, expenses and 12-month dynamics for each direction.",[1447,1503,1504,1507],{},[1450,1505,1506],{},"A dashboard as a Telegram Mini App."," Business, Purchases and Debts tabs, a spending calendar, comparison with the previous period, CSV and XLSX export.",[1447,1509,1510,1513],{},[1450,1511,1512],{},"Access for two people."," The owner and a close relative, each with their own accounts and summary.",[1426,1515,1517],{"id":1516},"how-it-works","How it works",[1519,1520,1522],"h3",{"id":1521},"detecting-the-bank-account-and-card-from-a-pdf","Detecting the bank, account and card from a PDF",[21,1524,1525],{},"Each bank has its own parser (a program that reads the structure of a file), and a separate module reads the statement header to detect the bank, owner, account, cards, period and balance. If the bank format is unknown, the fallback CSV and XLSX parser takes over. The parsers are built for each bank's current PDF layout: when a bank changes it, the sums stop matching the statement totals and the parser has to be fixed. A VTB debit card statement has no account number, so cards of one account are joined by an identical balance on the statement date, and a \"Same account\" button helps when that is not enough.",[1519,1527,1529],{"id":1528},"reconciling-overlapping-statements-without-duplicates","Reconciling overlapping statements without duplicates",[21,1531,1532],{},"A bank often issues a statement with spare dates, so the same day lands in two files. The bot compares the incoming operations with the saved ones and does not record them again; mismatches in the overlap are listed in the reply. Uploading the same file twice changes nothing.",[1519,1534,1536],{"id":1535},"transfers-between-your-own-accounts-are-not-income","Transfers between your own accounts are not income",[21,1538,1539],{},"A transfer from card to card looks like an expense in one place and income in another. The bot recognises such transfers by the banks' wording, by the owner's own name and by \"out, in\" pairs of the same amount, and they never reach income and expenses. Any account can be excluded from the overall totals with one button.",[1519,1541,1543],{"id":1542},"merging-a-manual-entry-with-the-statement-operation","Merging a manual entry with the statement operation",[21,1545,1546],{},"An expense you typed into the chat is not doubled when the statement arrives. The amount may differ slightly: the tolerance is 2 % or 100 currency units, plus a small date window. A place name from the message is searched in the bank's description with transliteration taken into account, so a Cyrillic name and its Latin spelling count as the same place. If several operations fit, the bot offers a choice with buttons, and a merge can be undone. The weak spot: two different expenses of the same amount on the same day may merge, and a button separates them.",[1519,1548,1550],{"id":1549},"cash-withdrawal-a-transfer-not-an-expense","Cash withdrawal: a transfer, not an expense",[21,1552,1553],{},"It used to be impossible to tell the bot about a withdrawal: from the statement it recognised the ATM operation, but from a message it did not, and the withdrawal either vanished or became an expense. Now a withdrawal is recorded as money moving from the card to the pocket, while expenses from the same message stay separate records. A withdrawal recorded in words merges with the ATM operation when the statement arrives, so the pocket is not counted twice. The same withdrawal is not recorded twice in one day; a genuine second withdrawal of the same amount is recorded with the word \"another\".",[1519,1555,1557],{"id":1556},"night-run-and-merchant-rules","Night run and merchant rules",[21,1559,1560],{},"A category is chosen in this order: learned merchant rules, then keywords (the longest match wins), then the category the bank supplied. Whatever stays unknown goes to the model in one request overnight. The clear cases are categorised and remembered as a rule, and in the morning a list of buttons arrives: the unclear ones on top, the sorted ones below for correction.",[1519,1562,1564],{"id":1563},"one-set-of-calculations-for-the-bot-and-the-dashboard","One set of calculations for the bot and the dashboard",[21,1566,1567],{},"All analytics live in one layer shared by the bot and the dashboard, so the numbers in the Telegram menu and on the charts match. The dashboard is a web page with no build step, written in HTML and Chart.js, and it opens as a Telegram Mini App. Login uses the Telegram signature or a personal link from the bot: the link works for an hour, after which the browser is issued a cookie for 30 days.",[1519,1569,1571],{"id":1570},"backups-and-liveness-monitoring","Backups and liveness monitoring",[21,1573,1574],{},"A daily database dump is checked for integrity and rotated, and a restore can be tried on a separate database without touching the live one. Every 5 minutes an external check looks at the containers, the database, the \"bot is alive\" mark (the bot sets it once a minute) and the dashboard address. On failure a Telegram message arrives. An error in a handler is never left silent: the person sees a reply, and the owner gets what failed and on which message.",[1426,1576,1578],{"id":1577},"results","Results",[1444,1580,1581,1584,1587,1590,1593,1596],{},[1447,1582,1583],{},"A one-message menu: the month at a glance (income, expenses, balance, today, recurring payments until month end, debts).",[1447,1585,1586],{},"Directions and \"who eats the most\": directions and personal spending in one row by descending expense, with a share and a shift against the previous month.",[1447,1588,1589],{},"The dashboard: daily spending, a spending calendar, \"what changed\", purchases by receipt line, export of operations to CSV and XLSX.",[1447,1591,1592],{},"Hints: rising expenses, exceeded budgets, outdated statements, an approaching credit card payment.",[1447,1594,1595],{},"A credit card payment reminder 3 days ahead, a weekly digest on Mondays, cash reconciliation.",[1447,1597,1598],{},"Automated tests (about 120 pytest functions) cover text parsing, statement matching, debts, bank detection, export and cash handling.",[1426,1600,1602],{"id":1601},"technologies-and-why","Technologies and why",[1444,1604,1605,1611,1617,1623,1629,1639],{},[1447,1606,1607,1610],{},[1450,1608,1609],{},"Python 3.13 and aiogram 3"," — the Telegram bot: menus, messages, files, the reminder scheduler.",[1447,1612,1613,1616],{},[1450,1614,1615],{},"FastAPI and uvicorn"," — the dashboard API; the frontend itself has no build step and uses HTML, CSS, JS and Chart.js.",[1447,1618,1619,1622],{},[1450,1620,1621],{},"PostgreSQL 17 and SQLAlchemy 2.0 (async)"," — storage of operations, accounts, rules, debts and budgets.",[1447,1624,1625,1628],{},[1450,1626,1627],{},"pdfplumber, pandas, openpyxl, chardet"," — reading PDF statements, the fallback CSV and XLSX, export.",[1447,1630,1631,1633,1634,1638],{},[1450,1632,309],{}," — complex text, voice, receipt photos, the night run and questions about money; keys and ordered model lists are set in ",[1635,1636,1637],"code",{},".env",".",[1447,1640,1641,1643],{},[1450,1642,650],{}," — three containers: the database, the bot and the web part; tables are created at startup.",[1426,1645,1647],{"id":1646},"status","Status",[21,1649,1650],{},"Our own product, in production. The current version, 1.14.0 of 22 September 2026, adds cash withdrawals and deposits from a message. Earlier the same day came spreading expenses across directions, questions about your own money, the spending calendar and naming the account in words.",[21,1652,1653],{},"Limitations: there are no migrations, so rolling back a version is done from a database copy; there is no multi-currency support; complex text and receipt photos go to Google. Transfers between your account and a relative's account in the \"All\" view count as income for one and an expense for the other.",{"title":28,"searchDepth":29,"depth":29,"links":1655},[1656,1657,1658,1669,1670,1671],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":1659},[1660,1662,1663,1664,1665,1666,1667,1668],{"id":1521,"depth":1661,"text":1522},3,{"id":1528,"depth":1661,"text":1529},{"id":1535,"depth":1661,"text":1536},{"id":1542,"depth":1661,"text":1543},{"id":1549,"depth":1661,"text":1550},{"id":1556,"depth":1661,"text":1557},{"id":1563,"depth":1661,"text":1564},{"id":1570,"depth":1661,"text":1571},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-22","💰",[1675,1678,1681,1684,1687,1690],{"q":1676,"a":1677},"How do I combine statements from several banks into one picture without duplicates?","Send a PDF statement from VTB, Sberbank, T-Bank or Ozon Bank. The bot detects the bank, account and card itself, reconciles overlapping periods and never records an already-loaded operation twice, and it lists any mismatches in the overlap in its reply. Other banks are covered by a fallback CSV and XLSX parser.",{"q":1679,"a":1680},"How do I separate business spending from personal spending?","Operations carry directions: work, your own projects, personal. A direction comes from the category or is set on an operation, and the Spread button assigns it to a whole group of expenses at the same place, past and future. The Business tab shows income, expenses and reinvestments over 12 months and a summary of which direction eats the most.",{"q":1682,"a":1683},"How do I keep track of debts by person?","A debt is recorded in a phrase: “owe Dima 14k”, “returned Dima 5k”. Each person has a ledger of movements with a balance, and different grammatical forms of a name resolve to the same person. Money moved for a debt in a statement counts as neither income nor an expense.",{"q":1685,"a":1686},"How do I account for cash that the bank does not see?","Cash withdrawals and deposits are recorded in words: “took out 6500 cash”, “put 5000 on the card”. That is a transfer between the card and your pocket, not an expense, so it stays out of the month's spending. After the message the bot writes how much it thinks is left in your pocket, and on Mondays it asks you to reconcile cash.",{"q":1688,"a":1689},"Where is the data stored, and what goes to Google?","The data lives in a PostgreSQL database in Docker on the server where the bot runs. Only Telegram IDs on the allow-list can open the bot and the dashboard; everyone else is refused and the dashboard API answers 401. Receipt photos and message text go to Google Gemini when a complex parse is needed; without keys only the built-in rules work.",{"q":1691,"a":1692},"Can a second person use it?","Yes. A second person enters their own expenses, which go to their own accounts, and sees their own summary, operations, accounts, export and a joint summary. The owner's sections (business, debts, budgets, categories, dashboard, night run, admin commands) are closed to them.",[],{},"\u002Fen\u002Fkeysy\u002Ffinansovyi-assistent",[],[1698,1701,1704],{"value":1699,"label":1700},"4 banks","VTB, Sberbank, T-Bank and Ozon Bank: the bank is detected from the PDF itself",{"value":1702,"label":1703},"~120 tests","pytest functions: text parsing, statement matching, debts, cash, export; no database or network needed",{"value":1705,"label":1706},"1 phrase","in the chat: an expense, a debt or a recurring payment is recorded and categorised",{"title":1422,"description":28},[1709,294,117,1710,1711,1712,309,120],"Python 3.13","PostgreSQL 17","SQLAlchemy 2.0","pdfplumber","production","en\u002Fkeysy\u002Ffinansovyi-assistent","our own product: a bot and web dashboard, four banks' statements, Gemini","A Telegram bot and web dashboard for personal finance: parses PDF statements from four banks, takes expenses as plain sentences, separates business from personal. Python, aiogram 3, PostgreSQL, Gemini.","zybOn7RNCigtUkdS9sBcpyf_4WUPY215ZzdZYiz3pI8",{"id":1719,"title":1720,"body":1721,"client":28,"cover":28,"date":1911,"demoUrl":28,"description":28,"direction":1217,"emoji":1379,"extension":33,"faq":1912,"featured":79,"links":1931,"meta":1936,"navigation":79,"nda":94,"order":80,"own":79,"path":1937,"related":1938,"results":1939,"seo":1949,"shot":1950,"shotAlt":1951,"stack":1952,"status":1713,"stem":1954,"subtitle":1955,"summary":1956,"__hash__":1957},"cases_en\u002Fen\u002Fkeysy\u002Ftoshastream.md","Peregovorka (ToshaStream): streaming server and team voice chat",{"type":18,"value":1722,"toc":1895},[1723,1725,1732,1735,1737,1740,1769,1771,1775,1778,1782,1785,1789,1792,1796,1799,1803,1814,1818,1821,1825,1832,1834,1851,1853,1887,1889,1892],[1426,1724,1429],{"id":1428},[21,1726,1727,1728,1731],{},"The earlier setup was a bare ",[1635,1729,1730],{},"nginx-rtmp"," with no configuration: the stream dropped from time to time, and anyone who knew the address could connect to the stream or even publish to it. There were no roles or closed sessions. Such a setup is as unfit for a work call as it is for a broadcast.",[21,1733,1734],{},"What was needed was a closed platform for a small circle of people: voice channels, text chats and, when needed, a live stream of gameplay from a home PC. There were two more conditions. A viewer on a phone over LTE and a viewer on a gigabit line should get different streams, not one compromise. And the service must not endanger its neighbors: the streaming server used to share a machine with the VPN entry node, so any failure of the stream threatened the VPN for every client.",[1426,1736,1439],{"id":1438},[21,1738,1739],{},"In the repository the product is called “Peregovorka” (Russian for “meeting room”); its earlier name was ToshaStream. It opens in the browser through a secret link, and the stream can also be watched in VLC. The page works in two modes: “Peregovorka” (voice and chats without a player) and “Stream” (player, stream chat and voice side by side). What has been built, per the README and the release log:",[1444,1741,1742,1745,1748,1751,1754,1757,1760,1763,1766],{},[1447,1743,1744],{},"Ingest of the stream over SRT with encryption and a password, plus a backup RTMP ingest.",[1447,1746,1747],{},"Two qualities at once: native 3440×1440 and a lighter 1720×720 for mobile internet.",[1447,1749,1750],{},"Browser viewing over HLS and over WebRTC (WHEP) with under a second of latency, and viewing in VLC over RTSP and SRT.",[1447,1752,1753],{},"Voice on a WebRTC mesh with our own TURN: three shared channels, calls in private conversations, groups, and a “voice only” mode.",[1447,1755,1756],{},"Screen sharing through the server: 1080p, 30 frames per second, up to 18 Mbit\u002Fs.",[1447,1758,1759],{},"Chats: three shared ones, private ones, groups and a stream chat; reactions, replies, polls, pinning, search, link previews, attachments and voice messages.",[1447,1761,1762],{},"Guest, member, admin and direct-messages-only roles, time-limited accounts, a sign-in journal and a “sign out everywhere” button.",[1447,1764,1765],{},"Push notifications to each device without duplicates, and a per-conversation mute.",[1447,1767,1768],{},"A nightly database snapshot, container health checks and an automatic restart of a stuck service.",[1426,1770,1517],{"id":1516},[1519,1772,1774],{"id":1773},"srt-instead-of-rtmp","SRT instead of RTMP",[21,1776,1777],{},"SRT (Secure Reliable Transport) is a video transport protocol over UDP that re-requests lost packets. The stream from the streamer's PC reaches the server over SRT with a password and an encryption key. RTMP is kept as a backup ingest. The server itself does not re-encode video, so the quality of the stream depends entirely on the settings on the gaming PC; the OBS setup guide is kept separately.",[1519,1779,1781],{"id":1780},"two-stream-qualities-without-re-encoding-on-the-server","Two stream qualities without re-encoding on the server",[21,1783,1784],{},"Both streams, 3440×1440 and 1720×720, are encoded by the graphics card on the PC, and the server only distributes them. The only thing that is re-encoded is the audio track, converted to AAC for Safari, which costs a few percent of one CPU core. The flip side: with the PC off, neither stream exists.",[1519,1786,1788],{"id":1787},"viewing-hls-for-stability-whep-for-speed","Viewing: HLS for stability, WHEP for speed",[21,1790,1791],{},"HLS (video streamed in segments) gives 6 to 8 seconds of latency but tolerates an unstable mobile network, and it lets you rewind about 16 seconds (8 segments of 2 seconds each). WHEP is a way to receive the stream over WebRTC with under a second of latency. The viewer picks between the regular mode and a “fast” one. Chrome, Edge and Safari play the 1440p browser stream, and Firefox gets 720p in H.264.",[1519,1793,1795],{"id":1794},"voice-on-a-webrtc-mesh-with-our-own-turn","Voice on a WebRTC mesh with our own TURN",[21,1797,1798],{},"A WebRTC mesh means every participant connects directly to every other: voice doesn't pass through the server at all, and the server only introduces participants and relays signalling packets. That gives latency in the tens of milliseconds and almost no server load. The price is that everyone sends their voice to everyone, so the limit is deliberate: 10 people per channel. TURN is a relay for people who can't connect directly. coturn uses temporary credentials valid for an hour, the secret never leaves the server, and the relay cannot decrypt a conversation.",[1519,1800,1802],{"id":1801},"private-access-auth_request-in-nginx-and-roles","Private access: auth_request in nginx and roles",[21,1804,1805,1806,1809,1810,1813],{},"Every request to HLS, WHEP and audio goes through ",[1635,1807,1808],{},"auth_request",": before serving, nginx asks the service whether the person is signed in. External players like VLC use a separate secret. Roles live in the database and are changed from the admin panel on the fly, without a restart or dropping anyone's call. The direct-messages-only role sees a single conversation with the admin: shared chats, channels, the stream and other people's names are closed to it on the server. State-changing requests are accepted only as POST with a ",[1635,1811,1812],{},"Sec-Fetch-Site"," check.",[1519,1815,1817],{"id":1816},"chats-voice-messages-and-history","Chats, voice messages and history",[21,1819,1820],{},"Chat history is stored in SQLite and survives a restart. A voice message is recorded straight from the input field (up to 5 minutes), and the server transcodes it to mp3 with ffmpeg so that iPhone, Android and desktop can all play it. Links get a preview built by the server, protected against SSRF: internal and private addresses are never resolved. Notifications don't duplicate: while a person reads a chat on one device, pushes don't go to any of their others.",[1519,1822,1824],{"id":1823},"service-resilience","Service resilience",[21,1826,1827,1828,1831],{},"Containers answer health checks, and the chat answers ",[1635,1829,1830],{},"\u002Fhealthz",", which reads from and writes to the database. A watchdog timer restarts a stuck container within a minute, but no more than three times an hour. The database is snapshotted nightly, and a separate machine collects the snapshots and verifies a restore; a failure arrives as a Telegram message. The service runs on its own machine, so it cannot bring the VPN down.",[1426,1833,1578],{"id":1577},[1444,1835,1836,1839,1842,1845,1848],{},[1447,1837,1838],{},"Viewer latency under a second over WHEP, 6 to 8 seconds over HLS.",[1447,1840,1841],{},"Up to 10 people per voice channel, connected directly, with no load on the server.",[1447,1843,1844],{},"Work calls and broadcasts run on our own infrastructure, without third-party messengers.",[1447,1846,1847],{},"Chat history is kept for 90 days and survives a restart.",[1447,1849,1850],{},"Voice is end-to-end encrypted between browsers.",[1426,1852,1602],{"id":1601},[1444,1854,1855,1860,1866,1872,1877,1882],{},[1447,1856,1857,1859],{},[1450,1858,1263],{},": the chat, accounts, roles, voice signalling and viewer counter service.",[1447,1861,1862,1865],{},[1450,1863,1864],{},"MediaMTX",": SRT ingest and HLS, WHEP and RTSP delivery.",[1447,1867,1868,1871],{},[1450,1869,1870],{},"WebRTC and coturn",": voice directly between participants and our own TURN.",[1447,1873,1874,1876],{},[1450,1875,1366],{},": accounts, messages and read state.",[1447,1878,1879,1881],{},[1450,1880,119],{},": the HTTPS entry and an access check on every request.",[1447,1883,1884,1886],{},[1450,1885,650],{},": starting the services with CPU and memory ceilings.",[1426,1888,1647],{"id":1646},[21,1890,1891],{},"Version 1.50.0, released on September 29, 2026; in production, our own product. The README calls it by its working name, “Peregovorka”; internal names of paths and streams remain the earlier ones for now.",[21,1893,1894],{},"Known limitations per the README: the text chat is not end-to-end encrypted, so anyone with root on the machine can read it; access to the stream is limited by account but not by role; group voice is a mesh, not an SFU, so more than 10 people per channel would need a different design; stream recording is currently switched off, and turning it back on should go together with a configured rotation. Planned: verify the stream after the SRT latency fix, put the Telegram account-management bot through its paces, and change the SRT publishing password.",{"title":28,"searchDepth":29,"depth":29,"links":1896},[1897,1898,1899,1908,1909,1910],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":1900},[1901,1902,1903,1904,1905,1906,1907],{"id":1773,"depth":1661,"text":1774},{"id":1780,"depth":1661,"text":1781},{"id":1787,"depth":1661,"text":1788},{"id":1794,"depth":1661,"text":1795},{"id":1801,"depth":1661,"text":1802},{"id":1816,"depth":1661,"text":1817},{"id":1823,"depth":1661,"text":1824},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-29",[1913,1916,1919,1922,1925,1928],{"q":1914,"a":1915},"Why SRT instead of RTMP, and how does it prevent drops?","SRT runs over UDP and re-requests lost packets. RTMP runs over TCP and piles up delay on packet loss until the connection breaks, which is exactly how the earlier bare nginx-rtmp setup kept dropping. RTMP remains in the service only as a backup ingest in case UDP is blocked somewhere.",{"q":1917,"a":1918},"What is the latency of the stream in the browser?","Under a second over WebRTC (WHEP) and 6 to 8 seconds over HLS. The latter is a deliberate trade for stability on mobile networks. HLS lets you rewind about 16 seconds. Chrome, Edge and Safari play the browser stream; Firefox gets 720p in H.264.",{"q":1920,"a":1921},"Can the server hear voice calls?","No. Voice goes directly between participants, up to 10 people per channel, and is encrypted between browsers. Our own TURN server is only a relay for people who can't connect directly, and it cannot decrypt the audio. Text chats, unlike voice, are not end-to-end encrypted: they sit in the database on the server.",{"q":1923,"a":1924},"Can work calls and chats run on our own server instead of a cloud messenger?","Yes. The service has three shared voice channels, calls in private conversations, groups, screen sharing, and chats with history and polls. The chat can be moved into a separate window over a game in windowed mode (Chrome and Edge from version 116). Everything runs in the browser on desktop and phone, with nothing to install.",{"q":1926,"a":1927},"What data is stored and who can access it?","Passwords are stored as bcrypt hashes, accounts and chat history in SQLite on your own server, with 90 days of history and 180 days of the sign-in journal. Access is split into guest, member, admin and direct-messages-only roles. Every request to the stream and voice is checked through auth_request in nginx.",{"q":1929,"a":1930},"Can this be deployed for another team?","Yes, we take such deployments on commission. The service starts in Docker Compose, an admin renames chats and interface texts from the admin panel, and the look is chosen from 10 palettes and 10 fonts. The streamer's PC graphics card encodes both stream qualities, so the server doesn't need a powerful machine.",[1932],{"label":1933,"url":1934,"note":1935},"Open the meeting room","https:\u002F\u002Fstream.devunit-lab.ru\u002Fperegovorka","voice channel and stream, right in the browser",{},"\u002Fen\u002Fkeysy\u002Ftoshastream",[],[1940,1943,1946],{"value":1941,"label":1942},"\u003C 1 s","viewer latency over WebRTC (WHEP)",{"value":1944,"label":1945},"Up to 10","people per voice channel, connected directly",{"value":1947,"label":1948},"90 days","of chat history in the database, surviving a service restart",{"title":1720,"description":28},"\u002Fshots\u002Fdevunit-lab-peregovorka.jpg","DevUnit Lab Peregovorka: the guide to chats, voice and streaming and the user profile in the mobile client",[1263,1864,1321,1324,1953,1366,119,120],"coturn","en\u002Fkeysy\u002Ftoshastream","our own product in production, version 1.50.0: voice, chats and a private stream","A private platform on your own server: SRT streaming in two qualities, browser viewing, peer-to-peer voice over WebRTC, chats and role-based access. A streaming server and voice chat for a team.","7VmQXrK4PgtlEKcpVWqtQmwrL0sQONnSR8l4crVgygE",{"id":1959,"title":1960,"body":1961,"client":28,"cover":28,"date":1911,"demoUrl":28,"description":28,"direction":1230,"emoji":2199,"extension":33,"faq":2200,"featured":79,"links":2219,"meta":2231,"navigation":79,"nda":94,"order":80,"own":79,"path":2232,"related":2233,"results":2234,"seo":2244,"shot":2245,"shotAlt":2246,"stack":2247,"status":2249,"stem":2250,"subtitle":2251,"summary":2252,"__hash__":2253},"cases_en\u002Fen\u002Fkeysy\u002Fvpn-klient.md","OshaVPN: VPN client for Android, Windows 10\u002F11 and Windows 7",{"type":18,"value":1962,"toc":2182},[1963,1965,1968,1971,1974,1976,1979,2018,2021,2023,2027,2034,2038,2041,2045,2067,2071,2074,2078,2081,2085,2088,2092,2095,2099,2102,2104,2124,2126,2171,2173,2176,2179],[1426,1964,1429],{"id":1428},[21,1966,1967],{},"The client runs its own VPN service on the Marzban panel, but access had to be distributed through third-party clients: NekoBox, Hiddify, v2rayNG. A non-technical user had to make sense of protocols, paste a config by hand, pick an entry point from a list of technical names, and find another one manually after every drop. Each of those steps ended up as a support question.",[21,1969,1970],{},"The second problem is the fleet of devices. Some users are on Windows 7, for which modern clients no longer build. On phones, people need to share their internet with a laptop and to use Russian services without switching the VPN on and off.",[21,1972,1973],{},"What was needed was a client of its own with one Connect button: the subscription is entered once or arrives as a link from the bot, every setting is preset for the user, and Russian sites and apps work directly, bypassing the tunnel.",[1426,1975,1439],{"id":1438},[21,1977,1978],{},"The OshaVPN app for Android, Windows 10\u002F11 and Windows 7. What has been built, according to the release log:",[1444,1980,1981,1984,1991,1994,1997,2000,2003,2006,2009,2012,2015],{},[1447,1982,1983],{},"One button with three states: off, connecting, running. The main screen shows the subscription expiry and the remaining traffic.",[1447,1985,1986,1987,1990],{},"Subscription entry by pasting a link, scanning a QR code, or tapping an ",[1635,1988,1989],{},"oshavpn:\u002F\u002F"," link from the bot. Up to three subscriptions with switching between them.",[1447,1992,1993],{},"Subscriptions refresh at launch and hourly, without reconnecting if the entry points haven't changed. A dead subscription is marked “not responding” with an offer to switch to a live one.",[1447,1995,1996],{},"Entry-point selection by a “race” and switching on a drop, with a memory of what worked on this network.",[1447,1998,1999],{},"Split routing: Russia direct, an “everything through VPN” switch, a “Services” catalog with “through VPN \u002F direct” rules, and games and torrents kept outside the tunnel.",[1447,2001,2002],{},"Kill switch: WFP filters on Windows, the system “always-on VPN” on Android.",[1447,2004,2005],{},"Over-the-air self-updates with a signed release list, a tester channel and a regular one, and a rollback on Windows if an update fails.",[1447,2007,2008],{},"A “distance to the server” measurement taken without the VPN, detection of other VPNs and DPI-bypass tools with an offer to turn them off, plus autostart and auto-connect after the system boots.",[1447,2010,2011],{},"A built-in installer on Windows, an “Almost ready” permissions screen on Android, a “How to use” section and a version history for testers.",[1447,2013,2014],{},"“Network recon”: a run against Russian and foreign targets that tells ordinary internet apart from heavy restrictions and allowlists.",[1447,2016,2017],{},"Diagnostics: app and engine logs in one format, and reports sent to the developer's receiver with secrets masked.",[21,2019,2020],{},"What the app does not have and will not have: manual config editing, a choice of protocols and ciphers, or lists of nodes with technical names.",[1426,2022,1517],{"id":1516},[1519,2024,2026],{"id":2025},"a-swappable-engine-sing-box-and-xray-behind-one-layer","A swappable engine: sing-box and Xray behind one layer",[21,2028,2029,2030,2033],{},"The VPN engine is the program that encrypts traffic and carries it through the tunnel. The client's own code (UI, subscription, routing, entry-point selection) never talks to the engine directly: a narrow ",[1635,2031,2032],{},"engine-api"," layer sits between them, and each engine plugs in with its own implementation. The first engine was sing-box and the second is Xray; Xray is now the main one, and every entry point on the test bench, including Reality and XHTTP, connects from a single subscription. On Android both engines are built into one library with gomobile; on Windows they run as separate processes. Switching is a setting, with no reinstall. Windows 7 stays on sing-box because Xray built with a newer Go doesn't start there.",[1519,2035,2037],{"id":2036},"entry-point-selection-by-a-race","Entry-point selection by a race",[21,2039,2040],{},"On connect, the entry points from the subscription are tried at the same time and the first one to open the internet wins; on the test bench the race takes 0.9 to 1.3 seconds. An entry point whose TLS handshake doesn't complete is not considered alive and the automation never picks it. After a failed probe, the same race looks for another working entry point. On Xray the switch goes through the routing API in a fraction of a second, without restarting the engine. If every entry point fails at once while the network is up, the client asks the server for a status page: when the path behind the entry points is down, the app doesn't cycle through them but waits quietly under a “server temporarily unavailable” message.",[1519,2042,2044],{"id":2043},"split-routing-russia-direct","Split routing: Russia direct",[21,2046,2047,2048,2051,2052,2055,2056,2059,2060,2055,2063,2066],{},"The rule sets ",[1635,2049,2050],{},"geoip-ru",", ",[1635,2053,2054],{},"geosite-category-ru"," and ",[1635,2057,2058],{},"geosite-yandex"," are built into the app and updated through the tunnel; for Xray the same lists are compiled into ",[1635,2061,2062],{},"geoip.dat",[1635,2064,2065],{},"geosite.dat",". The local network, Russian domains, Yandex, MAX and games go direct, and torrents bypass the tunnel in every mode. On Windows, games and torrent clients are also recognized by process name. A “Services” catalog loaded from a shared file lets the user choose what goes through the VPN and what goes direct, including a “launcher and anti-cheat follow the game” bundle.",[1519,2068,2070],{"id":2069},"kill-switch-wfp-on-windows-and-always-on-vpn-on-android","Kill switch: WFP on Windows and always-on VPN on Android",[21,2072,2073],{},"WFP (Windows Filtering Platform) is the system mechanism for filtering network traffic. In TUN mode the client installs “leak protection” filters: outgoing traffic that bypasses the tunnel is blocked, DNS outside the tunnel is always blocked, and the local network is open depending on a setting. During a drop, an update or an engine restart, programs see “no internet” instead of the real address. On Android the TUN interface stays up until you press Disconnect; full protection before the app starts comes from the system “always-on VPN” with blocking of traffic outside the VPN, and the main screen reminds you if it is off.",[1519,2075,2077],{"id":2076},"self-updates-with-a-signed-release-list","Self-updates with a signed release list",[21,2079,2080],{},"The release list is signed with an ECDSA P-256 key and the public key is built into the clients: a substituted list and a foreign key are rejected, which tests lock in. Android downloads the APK for its own architecture and installs it by itself while the screen is off; Windows downloads only the changed files, and the updater can roll back a replacement if it fails. The check runs hourly, and installation happens with the window hidden and with no input from the user. There is a tester channel and a regular one, and an option to opt out of updates under “For developers”.",[1519,2082,2084],{"id":2083},"network-recon-for-allowlists","Network recon for allowlists",[21,2086,2087],{},"In regions with allowlists, only Russian sites open. “Recon” runs about two hundred targets outside the VPN over a few minutes: Russian and foreign sites, hosters and CDNs, TLS name spoofing, plain HTTP, QUIC, DNS, and data after the handshake. The result is stated in words (ordinary internet, heavy restrictions or allowlists) and goes to the developer as a report; candidate targets for a new entry point come from the server in a signed file, without an app release.",[1519,2089,2091],{"id":2090},"diagnostics-without-secrets","Diagnostics without secrets",[21,2093,2094],{},"App and engine logs use one line format, and the engine writes to a file with rotation. Reports about crashes, entry-point probes and anomalies go to the developer's receiver through a queue on disk, and subscription links, keys, UUIDs, passwords and tokens are masked already when the log line is written. The details are in the case on the report receiver.",[1519,2096,2098],{"id":2097},"a-client-for-windows-7","A client for Windows 7",[21,2100,2101],{},"A separate build on .NET Framework 4.8 and WinForms, sharing logic with the main client. Windows 7 has no TUN, so it works through the system proxy: browsers and programs that respect the proxy go through the tunnel. The engine is sing-box built for older Windows, with a WS+TLS entry point.",[1426,2103,1578],{"id":1577},[1444,2105,2106,2109,2112,2115,2118,2121],{},[1447,2107,2108],{},"Three platforms in one product: Android 0.15.13, and Windows 10\u002F11 and Windows 7 at 0.15.12.",[1447,2110,2111],{},"Every test-bench entry point (Reality, XHTTP, QUIC, WS+TLS) connects on Xray from a single subscription; the protocols are VLESS, Trojan and Shadowsocks, plus Hysteria2 and TUIC on sing-box.",[1447,2113,2114],{},"The calculated worst case for moving to another entry point after a drop is 14.5 seconds.",[1447,2116,2117],{},"Split routing was checked by the client from a phone: Gosuslugi, Sber, Ozon, Wildberries.",[1447,2119,2120],{},"Self-updates were verified live: with the VPN on, an update installs in seconds and the tunnel comes back up by itself.",[1447,2122,2123],{},"Reports at the receiver contain no UUIDs, subscription links or keys, checked on real reports.",[1426,2125,1602],{"id":1601},[1444,2127,2128,2134,2140,2149,2154,2160,2166],{},[1447,2129,2130,2133],{},[1450,2131,2132],{},"Kotlin, Jetpack Compose (Material 3)",": the Android app, with an adaptive layout and a theme that follows the system.",[1447,2135,2136,2139],{},[1450,2137,2138],{},"C# \u002F .NET 10, WPF",": the Windows 10\u002F11 client; the engines run as separate processes, and TUN goes through wintun.",[1447,2141,2142,2145,2146,1638],{},[1450,2143,2144],{},".NET Framework 4.8, WinForms",": a separate Windows 7 client, sharing logic from ",[1635,2147,2148],{},"OshaVpn.Core",[1447,2150,2151,2153],{},[1450,2152,658],{},": the main engine, providing Reality, XHTTP, VLESS, Trojan and Shadowsocks.",[1447,2155,2156,2159],{},[1450,2157,2158],{},"sing-box",": the second engine, kept for comparison and for Windows 7, providing Hysteria2 and TUIC.",[1447,2161,2162,2165],{},[1450,2163,2164],{},"gomobile, Docker",": building both engines into one library for Android.",[1447,2167,2168,2170],{},[1450,2169,645],{},": the server side, handling subscriptions and user accounting; the client reads Marzban and Remnawave subscriptions.",[1426,2172,1647],{"id":1646},[21,2174,2175],{},"In testing with users. The latest release is dated September 29, 2026: Android 0.15.13, and Windows 10\u002F11 and Windows 7 at 0.15.12. The iPhone and iPad client is written and builds, but is postponed until a separate decision.",[21,2177,2178],{},"Known limitations per the README: the client works over IPv4 only; the Windows leak protection has been covered by a self-check and tests, but a live run on a clean machine with administrator rights is still ahead; the Windows 7 client was built and run on Windows 11 and has not been checked on a real Windows 7. Tester mode is on by default because all builds are still test builds; a test subscription is built into the builds, so you can install the app and see how it works without signing up.",[21,2180,2181],{},"The client is the third part of one VPN project, together with the Tech Poly VPN billing and the Russia → Europe route.",{"title":28,"searchDepth":29,"depth":29,"links":2183},[2184,2185,2186,2196,2197,2198],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":2187},[2188,2189,2190,2191,2192,2193,2194,2195],{"id":2025,"depth":1661,"text":2026},{"id":2036,"depth":1661,"text":2037},{"id":2043,"depth":1661,"text":2044},{"id":2069,"depth":1661,"text":2070},{"id":2076,"depth":1661,"text":2077},{"id":2083,"depth":1661,"text":2084},{"id":2090,"depth":1661,"text":2091},{"id":2097,"depth":1661,"text":2098},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"📱",[2201,2204,2207,2210,2213,2216],{"q":2202,"a":2203},"Why build your own VPN client when NekoBox, Hiddify and v2rayNG exist?","Third-party clients expect the user to understand protocols, paste configs by hand and hunt for another entry point after a drop. OshaVPN hides all of that behind one button: you enter the subscription once or receive it as a link from the bot, and the app picks a live entry point, switches on a drop and refreshes the subscription by itself. Manual config editing and cipher selection are left out on purpose.",{"q":2205,"a":2206},"Which platforms does the app run on?","Android 10 and newer, Windows 10\u002F11, and a separate small client for Windows 7 built on .NET Framework 4.8. Installation is from a file: a universal APK and a Windows archive with a built-in installer, with no Google Play. An iPhone and iPad client is written but postponed; for now an iPhone connects through the third-party OneXray app, following the guide inside the app.",{"q":2208,"a":2209},"How does automatic entry-point selection work after a drop?","On connect, the entry points from the subscription are tried at the same time and the one that opens the internet first is used. If an entry point fails, the same race runs again and finds another working one; on Xray the switch happens without restarting the engine. The calculated worst case for moving to another entry point is 14.5 seconds; before the timers were tuned, a test-bench measurement showed 18 seconds.",{"q":2211,"a":2212},"Does all traffic go through the VPN?","Not by default: the local network, .ru, .рф and .su domains, Yandex, MAX and games go direct, and everything else goes through the tunnel. The client verified Russian services from a real phone on Gosuslugi, Sber, Ozon and Wildberries. There is an “everything through VPN” switch, and blocked sites on Russian addresses can be sent into the tunnel with a rule.",{"q":2214,"a":2215},"What prevents traffic leaks if the engine crashes?","On Windows 10\u002F11 and Windows 7 in TUN mode, WFP “leak protection” filters block outgoing traffic that bypasses the tunnel, so during a drop or an engine restart programs see “no internet” rather than your real address. On Android the TUN interface stays up until you press Disconnect, and the system “always-on VPN” protects the device before the app starts; the main screen reminds you if it is off.",{"q":2217,"a":2218},"Can a client like this be built for our own VPN service?","Yes. The UI, subscription handling, routing and entry-point selection don't depend on the engine and talk to it through a narrow engine-api layer, so the app can be adapted to another panel or another set of entry points without a rewrite. The client already works with Marzban and Remnawave subscriptions, and self-updates and diagnostics are built in.",[2220,2224,2228],{"label":2221,"url":2222,"note":2223},"Android (APK)","https:\u002F\u002Fentry.geremytest.ru\u002Fdl\u002FOshaVPN-android.apk","universal APK; a test subscription is built in",{"label":2225,"url":2226,"note":2227},"Windows 10 \u002F 11","https:\u002F\u002Fentry.geremytest.ru\u002Fdl\u002FOshaVPN-windows.zip","archive with a built-in installer",{"label":1300,"url":2229,"note":2230},"https:\u002F\u002Fentry.geremytest.ru\u002Fdl\u002FOshaVPN-win7.zip",".NET Framework 4.8 build",{},"\u002Fen\u002Fkeysy\u002Fvpn-klient",[1194,702],[2235,2238,2241],{"value":2236,"label":2237},"3 platforms","Android, Windows 10\u002F11 and Windows 7 with shared connection logic",{"value":2239,"label":2240},"14.5 s","calculated worst case for moving to another entry point after a drop",{"value":2242,"label":2243},"0 secrets","in diagnostic reports: UUIDs, subscription links and keys are masked on the device",{"title":1960,"description":28},"\u002Fshots\u002Fdevunit-lab-klient-dostupa.jpg","DevUnit Lab secure-access client for Android: the main screen with the connect button and the “How to use” section",[803,804,2248,807,658,2158,645],"C# \u002F .NET 10 (WPF)","testing","en\u002Fkeysy\u002Fvpn-klient","our own app to replace NekoBox, Hiddify and v2rayNG, now in testing with users","A turnkey VPN client for Android, Windows 10\u002F11 and Windows 7: one Connect button, entry points chosen by a race, Russian sites direct, a kill switch and over-the-air updates. Swappable Xray or sing-box engine, Marzban subscriptions.","K59HaIKHh5Ssse7D3OSbW_HWS29xKU0YSafLVutBIfU",{"id":2255,"title":2256,"body":2257,"client":28,"cover":28,"date":2478,"demoUrl":28,"description":28,"direction":1180,"emoji":2479,"extension":33,"faq":2480,"featured":79,"links":2499,"meta":2500,"navigation":79,"nda":79,"order":2501,"own":94,"path":2502,"related":2503,"results":2504,"seo":2513,"shot":2514,"shotAlt":2515,"stack":2516,"status":1713,"stem":2521,"subtitle":2522,"summary":2523,"__hash__":2524},"cases_en\u002Fen\u002Fkeysy\u002Finternet-magazin-rubashek.md","Online Shirt Store on Nuxt 4 with an Owner Admin Panel",{"type":18,"value":2258,"toc":2462},[2259,2261,2264,2267,2281,2283,2290,2293,2322,2324,2328,2338,2342,2345,2349,2356,2360,2363,2367,2370,2374,2377,2381,2384,2386,2406,2408,2457,2459],[1426,2260,1429],{"id":1428},[21,2262,2263],{},"A shop selling school and everyday shirts in Cheboksary sold «by phone and chat» and delivered around the city and the districts of Chuvashia. The client needed a full online store that the owner runs alone: adds products and photos, changes prices and sees orders without contacting a developer or understanding the technical side.",[21,2265,2266],{},"The conditions:",[1444,2268,2269,2272,2275,2278],{},[1447,2270,2271],{},"the site is found in search for queries like «buy school shirts Cheboksary»;",[1447,2273,2274],{},"the buyer chooses a size and colour instead of guessing in a chat;",[1447,2276,2277],{},"wholesale purchases are calculated automatically;",[1447,2279,2280],{},"the owner learns about a new order at once, in a messenger.",[1426,2282,1439],{"id":1438},[21,2284,2285,2286,2289],{},"A Nuxt 4 storefront and a FastAPI API with MySQL 8, plus an admin panel for the owner. The ",[1450,2287,2288],{},"admin panel"," is a closed section of the site where the owner edits products, prices, photos and texts and views orders.",[21,2291,2292],{},"What was implemented:",[1444,2294,2295,2298,2301,2304,2307,2310,2313,2316,2319],{},[1447,2296,2297],{},"A catalogue with «kids'» and «teens'» sections, and a product card with colour options, a size chart and several photos.",[1447,2299,2300],{},"Size tables for kids and teens, and a shirt measuring diagram with labels and an explanation.",[1447,2302,2303],{},"A cart: add, change quantity, remove, total. The contents are stored in the browser and survive a page reload.",[1447,2305,2306],{},"Checkout for several products at once, with prices calculated by the server.",[1447,2308,2309],{},"Wholesale: the quantity and discount are set in the panel, and a product is marked as taking part in its own card.",[1447,2311,2312],{},"The admin panel: products, prices, photos, «hit» and «new» labels, removal from the storefront, and an order list with status changes.",[1447,2314,2315],{},"A «Site content» section: contacts and legal details, the schedule of market trading, the story of the shop, wholesale terms, three selling points on the home page, FAQ and page texts.",[1447,2317,2318],{},"Light and dark themes with a switch, and a custom 404 page.",[1447,2320,2321],{},"A bot for access requests to order notifications: a new person writes to the bot and the owner approves the request with a button in the chat.",[1426,2323,1517],{"id":1516},[1519,2325,2327],{"id":2326},"nuxt-server-side-rendering-for-buy-school-shirts-searches","Nuxt server-side rendering for «buy school shirts» searches",[21,2329,2330,2333,2334,2337],{},[1450,2331,2332],{},"Server-side rendering (SSR)"," means a page is assembled on the server and delivered as ready HTML, not as an empty shell that JavaScript fills in. So a search robot sees the products at once. For search we did the following: every page has its own title and description, Product markup with price, availability and photos, ClothingStore markup with the pickup point coordinates, a canonical URL, a ",[1635,2335,2336],{},"robots.txt"," that closes the admin panel, and a sitemap built on the fly from visible pages and products.",[1519,2339,2341],{"id":2340},"stock-by-size-and-colour-in-the-product-card","Stock by size and colour in the product card",[21,2343,2344],{},"A product has a list of colours, and each colour has its own list of sizes with stock. On the product page the buyer picks a colour by a swatch and a size among those in stock. The choice goes into the cart, the order, the admin panel and the Telegram notification. A size with no stock is not shown on the storefront.",[1519,2346,2348],{"id":2347},"phone-photos-rotation-by-the-camera-tag-and-webp","Phone photos: rotation by the camera tag and webp",[21,2350,2351,2352,2355],{},"The owner uploads an ordinary phone snapshot. A phone records the frame's orientation in a camera tag instead of rotating the pixels, and without reading the tag a photo lies on its side. The server reads the tag, rotates the frame, shrinks it and converts it to ",[1450,2353,2354],{},"webp",", a modern image format that weighs noticeably less than JPEG.",[1519,2357,2359],{"id":2358},"the-server-calculates-prices-not-the-browser","The server calculates prices, not the browser",[21,2361,2362],{},"The cart contents are stored in the browser, and a buyer can edit the browser's data however they like. So at checkout the server recalculates prices and the total from the database. It also applies the wholesale price.",[1519,2364,2366],{"id":2365},"order-alerts-in-telegram-and-an-access-request-bot","Order alerts in Telegram and an access-request bot",[21,2368,2369],{},"An order is duplicated into the owner's Telegram chat. Access to notifications for other people is granted by a bot: a person writes to the bot, the request goes to the owner, and the owner approves or rejects it with a button in the chat. Approved people are added to the recipients with no changes to server settings. VKontakte alerts are written but not switched on: the community token is missing.",[1519,2371,2373],{"id":2372},"admin-panel-protection","Admin panel protection",[21,2375,2376],{},"Passwords are stored as hashes and sign-in creates a session. The 15-minute lockout after 5 wrong passwords applies to a username regardless of the address the guessing comes from, because the general per-IP request limit on nginx does not stop guessing from many addresses. The panel is available on a separate subdomain with its own session cookie that does not overlap with the storefront. All write routes in the API check sign-in.",[1519,2378,2380],{"id":2379},"deployment-as-one-container-stack","Deployment as one container stack",[21,2382,2383],{},"The store runs in four Docker containers: database, cache, API and storefront. The database schema is applied by Alembic migrations automatically when the API starts. The general entry nginx on the studio's web server publishes the site, with certificates issued by Let's Encrypt.",[1426,2385,1578],{"id":1577},[1444,2387,2388,2391,2394,2397,2400,2403],{},[1447,2389,2390],{},"The owner adds products and changes prices, stock and texts alone, without a developer.",[1447,2392,2393],{},"The buyer picks a colour and size, and the order reaches the owner in Telegram with the chosen items.",[1447,2395,2396],{},"The wholesale price switches on automatically, and prices do not depend on what sits in the browser.",[1447,2398,2399],{},"Pages are served as ready HTML, each with its own title and description, and products carry structured markup.",[1447,2401,2402],{},"Uploading a photo from a phone needs no processing on the owner's side.",[1447,2404,2405],{},"The repository has no speed measurements and no automated tests, so we give no scores.",[1426,2407,1602],{"id":1601},[1444,2409,2410,2416,2422,2428,2434,2440,2445,2451],{},[1447,2411,2412,2415],{},[1450,2413,2414],{},"Nuxt 4, Vue 3, Tailwind CSS 4"," — the storefront and the admin panel with server-side rendering and light and dark themes.",[1447,2417,2418,2421],{},[1450,2419,2420],{},"FastAPI and SQLAlchemy 2 (async)"," — the REST API for the catalogue, orders and site settings.",[1447,2423,2424,2427],{},[1450,2425,2426],{},"Alembic"," — database schema migrations, six revisions at the time of the document, applied at startup.",[1447,2429,2430,2433],{},[1450,2431,2432],{},"MySQL 8"," — products, variants, stock, orders, site settings.",[1447,2435,2436,2439],{},[1450,2437,2438],{},"Redis 7"," — cache and sessions.",[1447,2441,2442,2444],{},[1450,2443,650],{}," — database, cache, API and storefront as one stack; Python 3.13 and Node 24 inside the images.",[1447,2446,2447,2450],{},[1450,2448,2449],{},"nginx and Let's Encrypt"," — publishing the storefront and the panel through the shared entry container of the web server.",[1447,2452,2453,2456],{},[1450,2454,2455],{},"A Telegram bot"," — order notifications and requests for access to them.",[1426,2458,1647],{"id":1646},[21,2460,2461],{},"Version 1.17.1 of 26 September 2026, the store runs on the production server. Search indexing is closed while it is being finished. Before promotion the block must be removed, Yandex Metrica connected and the site verified in the webmaster tools, which needs verification codes from the client. Payment is on receipt only, and online payment is not connected: a QR-code payment or an online payment service is under discussion. The numbers in the size chart need to be checked with the owner. The repository has no automated tests and no description of backups. Other client details are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":2463},[2464,2465,2466,2475,2476,2477],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":2467},[2468,2469,2470,2471,2472,2473,2474],{"id":2326,"depth":1661,"text":2327},{"id":2340,"depth":1661,"text":2341},{"id":2347,"depth":1661,"text":2348},{"id":2358,"depth":1661,"text":2359},{"id":2365,"depth":1661,"text":2366},{"id":2372,"depth":1661,"text":2373},{"id":2379,"depth":1661,"text":2380},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-26","👔",[2481,2484,2487,2490,2493,2496],{"q":2482,"a":2483},"How do you move from selling by phone and chat to an online store?","You need a store the owner runs alone. Here, in the admin panel, the owner adds products and photos, changes prices, marks items as «hit» or «new», removes a product from the storefront and sees orders with a one-click call to the buyer. No developer is needed.",{"q":2485,"a":2486},"How do you upload a product photo from a phone so it does not lie on its side?","An ordinary phone snapshot is uploaded. The server rotates the frame by the camera tag, shrinks it and converts it to webp, so pages stay light.",{"q":2488,"a":2489},"How do you track stock by size and colour?","In the product card you add a list of colours, and each colour has its own sizes with stock on hand. A size with no stock disappears from the storefront, and so does a colour with no sizes. The buyer picks a colour and size on the product page, and the choice goes into the cart, the order and the notification to the owner.",{"q":2491,"a":2492},"How do wholesale prices work in the online store?","The admin panel sets the quantity at which wholesale starts and the discount size, and each product is marked as taking part in wholesale. The cart shows how many pieces are missing for the wholesale price. The total is calculated by the server from the database, not by the browser.",{"q":2494,"a":2495},"How does an online store get found for «buy school shirts in Cheboksary»?","The storefront is rendered on the server, so search robots receive ready HTML with the products. Every page has its own title and description, the product page carries Product markup with price and availability, and the sitemap builds itself. Indexing is currently closed while the store is being finished, and is switched on before promotion.",{"q":2497,"a":2498},"How is the store's admin panel protected?","Writing to the API is possible only after sign-in. After 5 wrong passwords in a row for one username, sign-in is blocked for 15 minutes through either of the two panel addresses. The panel opens on a separate subdomain with its own session cookie, and the response time on sign-in does not reveal whether a username exists.",[],{},15,"\u002Fen\u002Fkeysy\u002Finternet-magazin-rubashek",[],[2505,2508,2510],{"value":2506,"label":2507},"2 sections","kids' and teens' shirts, each with its own size table",{"value":2354,"label":2509},"phone photos are rotated, shrunk and converted by the server",{"value":2511,"label":2512},"15 minutes","sign-in lockout after 5 wrong passwords in a row",{"title":2256,"description":28},"\u002Fshots\u002Fdevunit-lab-internet-magazin.jpg","Online store of school shirts on Nuxt by DevUnit Lab: the home page on a phone",[2517,2518,2519,117,2520,2432,1362,120,119],"Nuxt 4","Vue 3","Tailwind CSS 4","SQLAlchemy 2","en\u002Fkeysy\u002Finternet-magazin-rubashek","a shirt shop in Cheboksary: the owner manages products, prices and orders alone","An online store for school and everyday shirts in Cheboksary on Nuxt 4 and FastAPI: a catalogue with colours and stock by size, a cart, wholesale prices, an admin panel and order alerts in Telegram.","NOa1XQpuuhwdIFbNBFt3gEKG9PDStQYx63hz5zyVJOE",{"id":2526,"title":2527,"body":2528,"client":28,"cover":28,"date":2723,"demoUrl":28,"description":28,"direction":1217,"emoji":2724,"extension":33,"faq":2725,"featured":94,"links":2744,"meta":2745,"navigation":79,"nda":94,"order":2501,"own":79,"path":2746,"related":2747,"results":2748,"seo":2758,"shot":28,"shotAlt":28,"stack":2759,"status":1713,"stem":2760,"subtitle":2761,"summary":2762,"__hash__":2763},"cases_en\u002Fen\u002Fkeysy\u002Fvpn-trassa.md","VPN infrastructure: Russian entry node, European exit",{"type":18,"value":2529,"toc":2708},[2530,2532,2535,2542,2544,2551,2578,2580,2584,2590,2594,2599,2603,2606,2610,2613,2617,2628,2632,2639,2641,2655,2657,2700,2702,2705],[1426,2531,1429],{"id":1428},[21,2533,2534],{},"The address of the exit VPN server was being suppressed on the Russian side. Measurements on 20 August 2026 showed that 35–60% of TCP connection attempts from Russia to that address were lost, while cloudflare.com and google.com showed no loss, and from the Netherlands the same address also showed 0%. Ping (ICMP) to the server ran without loss, and three sessions set up around the filter lived for 305 seconds without a single failure.",[21,2536,2537,2538,2541],{},"What was filtered was neither an app nor the VLESS protocol, but the pair of Russia and that address, and only at connection setup (the ",[1450,2539,2540],{},"TCP handshake",", the exchange of service packets at the start of a connection). Every new client opened its own cross-border connection and hit the filter all over again. A second requirement: changing the domain should take minutes, not hours of manual repair. A third: torrent traffic must not lead to hosting-provider complaints.",[1426,2543,1439],{"id":1438},[21,2545,2546,2547,2550],{},"Stop opening a new cross-border connection for every client and keep one persistent connection into which all of them are multiplexed. ",[1450,2548,2549],{},"Multiplexing"," means carrying many streams inside one connection. What is implemented:",[1444,2552,2553,2556,2559,2566,2569,2572,2575],{},[1447,2554,2555],{},"An entry relay in Russia: nginx terminates TLS on the user-facing domain and routes traffic by SNI on a single port.",[1447,2557,2558],{},"A persistent frp tunnel from the relay to the exit server instead of direct connections; a second independent tunnel was added in version 3.0.0.",[1447,2560,2561,2562,2565],{},"Proxying of the VLESS WebSocket tunnel and of the Marzban panel with subscriptions; a tunnel path without an ",[1635,2563,2564],{},"Upgrade"," header answers 404, like a page that does not exist.",[1447,2567,2568],{},"Domains split by role: traffic, subscriptions, admin.",[1447,2570,2571],{},"A domain change in one command without dropping client sessions; automatic certificate renewal with a deploy hook.",[1447,2573,2574],{},"A standalone node in the Netherlands: Marzban, VLESS Reality, torrents and AI services exiting through Cloudflare WARP.",[1447,2576,2577],{},"TCP forwarding for the UNLOCK greeting-card service through the Netherlands node when the direct path between Russia and the exit server is closed.",[1426,2579,1517],{"id":1516},[1519,2581,2583],{"id":2582},"sni-routing-on-a-single-port","SNI routing on a single port",[21,2585,2586,2589],{},[1450,2587,2588],{},"SNI"," is the site name a client announces at the start of a TLS connection. The relay reads it and decides where to send the stream: to a website, to the panel or into the tunnel. Websites, the VPN and the tunnel therefore share one HTTPS port and look identical from outside. Since 18 September 2026 the relay carries only the VPN: the websites moved to a separate server.",[1519,2591,2593],{"id":2592},"a-persistent-frp-tunnel-instead-of-a-connection-per-client","A persistent frp tunnel instead of a connection per client",[21,2595,2596,2598],{},[1450,2597,663],{}," is a reverse-proxy tool that keeps one long-lived connection between nodes. The filter that fires at connection setup no longer sees new connections. A pool of persistent connections to the panel, instead of a new one per request, completes the design.",[1519,2600,2602],{"id":2601},"tunnel-drops-and-the-second-tunnel","Tunnel drops and the second tunnel",[21,2604,2605],{},"The analysis in version 3.0.0 found 46 tunnel drops per day, with no error in the log before each break. The cause is the tcpMux mode: frp turns off its own heartbeat, the multiplexer has a fixed 10-second write timeout, and on a lossy link a single missed ping killed a healthy session. The keepalive interval was raised from 30 to 120 seconds and a second independent tunnel was added. With the first tunnel taken down, the subscription answered 10 requests out of 10.",[1519,2607,2609],{"id":2608},"splitting-domains-traffic-subscriptions-admin","Splitting domains: traffic, subscriptions, admin",[21,2611,2612],{},"Each domain has one role. The traffic domain carries a continuous WebSocket, which will be noticed sooner or later. A subscription is requested rarely, in short HTTPS calls. The payoff: if the traffic domain is blocked, the update channel survives, and the client taps \"refresh subscription\" and gets a config with a new address. The admin panel does not sit on any public domain; it listens on a separate port that admits only the exit server's address. The limit of the design: all domains point to one IP address, and a block by address takes them down together.",[1519,2614,2616],{"id":2615},"a-standalone-node-in-the-netherlands-vless-reality-and-cloudflare-warp","A standalone node in the Netherlands: VLESS Reality and Cloudflare WARP",[21,2618,2619,2620,2623,2624,2627],{},"A separate node with its own Marzban panel, database and keys, sharing nothing with the main exit server. ",[1450,2621,2622],{},"VLESS Reality"," disguises the connection as TLS traffic to someone else's website and needs neither a domain nor a certificate. BitTorrent traffic (detected by protocol) and 44 AI-service domains go through ",[1450,2625,2626],{},"Cloudflare WARP",", so the hosting provider sees only an encrypted connection to Cloudflare. All other web traffic goes direct. Requests to private subnets are blocked, traffic is accounted per user, and SSH is protected from password guessing by fail2ban.",[1519,2629,2631],{"id":2630},"domain-change-and-certificate-renewal","Domain change and certificate renewal",[21,2633,2634,2635,2638],{},"A full-migration script updates the certificate, the nginx config and the subscriptions. Changes are applied with ",[1635,2636,2637],{},"nginx -s reload",", so client sessions are not dropped. Let's Encrypt certificates are renewed through webroot without stopping nginx, and a deploy hook makes nginx re-read the renewed certificate: previously certbot updated the files while nginx kept serving the old one.",[1426,2640,1578],{"id":1577},[1444,2642,2643,2646,2649,2652],{},[1447,2644,2645],{},"A domain change is one command, with no dropped live sessions.",[1447,2647,2648],{},"The fallback works: with the first tunnel taken down, the subscription answered 10 of 10 requests.",[1447,2650,2651],{},"Torrent traffic and 44 AI-service domains exit through Cloudflare WARP, and the hosting provider sees only an encrypted connection to Cloudflare.",[1447,2653,2654],{},"Leak check for torrent traffic: all 44 client connections went to the WARP container, with no direct connections to peers.",[1426,2656,1602],{"id":1601},[1444,2658,2659,2664,2670,2675,2680,2685,2690,2695],{},[1447,2660,2661,2663],{},[1450,2662,119],{}," — TLS termination and SNI routing on one port.",[1447,2665,2666,2669],{},[1450,2667,2668],{},"Xray \u002F VLESS Reality"," — the VPN protocol; Reality removes the need for a domain and certificate on the exit node.",[1447,2671,2672,2674],{},[1450,2673,645],{}," — a panel for users, subscriptions and traffic accounting.",[1447,2676,2677,2679],{},[1450,2678,663],{}," — the persistent multiplexing tunnel between nodes.",[1447,2681,2682,2684],{},[1450,2683,2626],{}," — the exit for torrents and AI services.",[1447,2686,2687,2689],{},[1450,2688,655],{}," — Let's Encrypt certificates with automatic renewal.",[1447,2691,2692,2694],{},[1450,2693,120],{}," — deployment of nginx, frp, Marzban and WARP.",[1447,2696,2697,2699],{},[1450,2698,297],{}," — the Marzban user database.",[1426,2701,1647],{"id":1646},[21,2703,2704],{},"In production: relay 4.0.0 and the Netherlands node 1.3.0, both dated 18 September 2026. This is the base of the Tech Poly VPN service.",[21,2706,2707],{},"Next on the README roadmap: serving subscriptions from a separate machine, a second relay in another network, an automatic subscription refresh interval in clients, a domain and certificate for the Netherlands node's panel, and automatic backup of the Marzban database.",{"title":28,"searchDepth":29,"depth":29,"links":2709},[2710,2711,2712,2720,2721,2722],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":2713},[2714,2715,2716,2717,2718,2719],{"id":2582,"depth":1661,"text":2583},{"id":2592,"depth":1661,"text":2593},{"id":2601,"depth":1661,"text":2602},{"id":2608,"depth":1661,"text":2609},{"id":2615,"depth":1661,"text":2616},{"id":2630,"depth":1661,"text":2631},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-18","🛰",[2726,2729,2732,2735,2738,2741],{"q":2727,"a":2728},"How is the VPN route from Russia to Europe built?","The entry node in Russia accepts clients on a TLS domain and routes websites, the panel and the VPN by SNI on a single port. All VPN traffic then goes into one persistent frp tunnel to the exit server abroad. Separately, a standalone node in the Netherlands runs Marzban with VLESS Reality.",{"q":2730,"a":2731},"Why not connect to the exit server directly?","Measurements on 20 August 2026 showed that 35–60% of TCP handshakes from Russia to the exit server's address were lost, while ping had no loss. The filter targeted the pair of Russia and that address, and only at the moment a connection was set up. One persistent tunnel instead of a new connection per client avoids that filter.",{"q":2733,"a":2734},"What happens if the traffic domain gets blocked?","Domains have separate roles: one carries traffic, another serves subscriptions, and the admin panel sits on a closed port. If the traffic domain is blocked, the client refreshes its subscription through the second domain and receives a config with a new address. The limit: all domains point to one IP address, so an address-level block still takes them all down.",{"q":2736,"a":2737},"How do you avoid hosting-provider complaints about torrents?","On the Netherlands node, BitTorrent traffic and 44 AI-service domains exit through Cloudflare WARP, so the hosting provider only sees an encrypted connection to Cloudflare. Ordinary web traffic goes direct, which avoids captchas. Torrents over WARP are slower than direct ones, because WARP does not accept inbound connections.",{"q":2739,"a":2740},"How do you change the VPN domain without dropping clients?","A script moves the route to a new domain with one command: the certificate, the nginx config and the subscriptions are updated. Live sessions survive because nginx reloads its config instead of restarting. Renewed certificates are picked up by a certbot deploy hook.",{"q":2742,"a":2743},"What are the weak points of this setup?","All relay domains point to one IP address, so a block by address takes them down together. The first tunnel was a single point of failure; a second independent tunnel was added in version 3.0.0. The README does not say whether it is deployed on the production servers.",[],{},"\u002Fen\u002Fkeysy\u002Fvpn-trassa",[851,1194],[2749,2752,2755],{"value":2750,"label":2751},"1 command","moves the VPN to a new domain: certificate, nginx and subscriptions, with live sessions intact",{"value":2753,"label":2754},"10 of 10","subscription requests answered with the first tunnel taken down: the second independent tunnel carried them",{"value":2756,"label":2757},"44 domains","of AI services, plus all torrent traffic, exit through Cloudflare WARP",{"title":2527,"description":28},[119,2668,645,663,2626,655,120,297],"en\u002Fkeysy\u002Fvpn-trassa","our own product: a relay in Russia, a persistent tunnel and a node in the Netherlands","A VPN that does not get blocked on the cross-border path: an entry relay in Russia with SNI routing, a persistent frp tunnel and a standalone exit node on Marzban with VLESS Reality and Cloudflare WARP.","n22qr_Za-V9bSC1BMLplqTh2kQFZKWspMXJIWS9QGrI",{"id":2765,"title":2766,"body":2767,"client":28,"cover":28,"date":1911,"demoUrl":28,"description":28,"direction":1193,"emoji":2966,"extension":33,"faq":2967,"featured":79,"links":2986,"meta":2987,"navigation":79,"nda":79,"order":263,"own":94,"path":2988,"related":2989,"results":2990,"seo":3000,"shot":3001,"shotAlt":3002,"stack":3003,"status":1713,"stem":3005,"subtitle":3006,"summary":3007,"__hash__":3008},"cases_en\u002Fen\u002Fkeysy\u002Fhelp-desk.md","Telegram ticketing system for an IT department",{"type":18,"value":2768,"toc":2950},[2769,2771,2774,2777,2780,2782,2785,2817,2819,2823,2826,2829,2833,2836,2840,2843,2846,2850,2853,2857,2868,2872,2875,2879,2882,2884,2904,2906,2945,2947],[1426,2770,1429],{"id":1428},[21,2772,2773],{},"IT requests came from everywhere: direct messages to the sysadmin, the group chat, a word in the hallway. Some were lost, some were done twice, nobody knew who was working on what or how much time support consumed. By the end of the month management had nothing to look at except \"we worked a lot\".",[21,2775,2776],{},"The second half of the problem was the site itself. With dozens of departments, the credentials for routers, cameras and Wi-Fi lived in a notebook and in chat history, one person remembered who sat at which computer, and a domain rollout or a licence audit would have meant walking through every office.",[21,2778,2779],{},"The client, a museum complex in Moscow, wanted a single intake channel inside Telegram, without an external ticketing tool such as Jira, with asset tracking and numbers for the manager.",[1426,2781,1439],{"id":1438},[21,2783,2784],{},"Help Desk Bunker is a ticketing system for an internal IT department that lives entirely in Telegram. What is implemented:",[1444,2786,2787,2790,2793,2796,2799,2802,2805,2808,2811,2814],{},[1447,2788,2789],{},"tickets with a full lifecycle: priority, SLA, visit time, pause, closure report, 1–10 rating;",[1447,2791,2792],{},"closure is confirmed by the employee, not the assignee;",[1447,2794,2795],{},"four roles with different interfaces: employee, system administrator, manager, owner;",[1447,2797,2798],{},"a credential registry with encrypted passwords and a view log;",[1447,2800,2801],{},"a knowledge base whose articles grow out of closure reports;",[1447,2803,2804],{},"a site registry: departments, rooms, workstations, equipment, consumables stock, QR labels for desks;",[1447,2806,2807],{},"a PC program that collects a computer's passport on its own;",[1447,2809,2810],{},"company analytics, a manager's dashboard, a morning digest, a monthly report with CSV;",[1447,2812,2813],{},"broadcasts, employee-to-IT messaging outside tickets, a weekly database dump to the owners, restart from Telegram;",[1447,2815,2816],{},"an optional AI assistant on Gemini as a first line of support, off by default.",[1426,2818,1517],{"id":1516},[1519,2820,2822],{"id":2821},"ticket-lifecycle-and-sla-in-a-telegram-bot","Ticket lifecycle and SLA in a Telegram bot",[21,2824,2825],{},"An employee files a ticket through a four-step wizard: category, location, photo, description. If a workstation is assigned to them, it is offered as a button. The card arrives in the IT chat and is redrawn in place on every status change, so no trail of duplicates builds up. Statuses: new, accepted, in progress, paused, awaiting confirmation, reopened, closed, rejected. \"Accepted\" and \"in progress\" are deliberately separate: work time is measured from the moment the assignee actually starts.",[21,2827,2828],{},"The priority sets the resolution target, the SLA (an agreed time to react and to solve). Overdue tickets are highlighted in the queue, listed in the SLA section and in the digest. Before a visit the bot reminds the assignee 30, 15, 10 and 5 minutes ahead, and the employee once, 10 minutes ahead. Tickets without a reaction are escalated every 15 minutes during working hours.",[1519,2830,2832],{"id":2831},"credential-registry-fernet-encryption-and-a-view-log","Credential registry: Fernet encryption and a view log",[21,2834,2835],{},"The registry keeps access details for routers, switches, cameras, servers, Wi-Fi, hosting panels and mail. The database holds no password, only Fernet ciphertext (symmetric encryption from the cryptography library). The reason is concrete: once a week the bot sends a database dump to Telegram, and without encryption the passwords would travel in plain text. A password is shown in a separate message the bot deletes after a minute, and every view is logged. Without an encryption key the registry refuses to accept passwords at all. Managers have no access to the registry.",[1519,2837,2839],{"id":2838},"asset-tracking-workstations-and-qr-labels","Asset tracking, workstations and QR labels",[21,2841,2842],{},"The site registry is maintained from a phone: departments, rooms and workstations are created in batches, one line per record, duplicates are not created, and records are retired rather than deleted. Equipment is the fourth dimension with its own lifecycle: a passport (manufacturer, model, serial and inventory numbers, warranty, network, OS, office suite), a \"Serviced\" button with an interval until the next service, search by serial number, IP and MAC, attachments to a specific PC.",[21,2844,2845],{},"A ticket is linked to a specific device, so analytics includes a \"by equipment\" report: repeated failures of one unit are an argument for replacement rather than another repair. QR labels for desks are printed as an A4 sheet; scanning opens the bot, seats the employee \"at this desk\" and offers a \"Ticket from this desk\" button. Consumables are written off right at the closure step, and a \"consumption per period\" report gives a number for purchasing.",[1519,2847,2849],{"id":2848},"knowledge-base-built-from-closure-reports","Knowledge base built from closure reports",[21,2851,2852],{},"Right after the short report the bot offers to save the fix as an article: the symptoms are taken from the employee's description verbatim, so the article can be found in their own words. The main entry point is the ticket wizard: after a category is chosen, up to three ready solutions are shown, and a \"this helped\" answer ends the dialogue without a ticket. Such cases are counted separately in the company summary as tickets that never happened.",[1519,2854,2856],{"id":2855},"company-analytics","Company analytics",[21,2858,2859,2860,2863,2864,2867],{},"One engine, different permissions: ",[1635,2861,2862],{},"\u002Fanalytics"," for the administrator, ",[1635,2865,2866],{},"\u002Fdashboard"," for the manager. A period summary, critical nodes (a risk index derived from ticket history, overdue items and reopenings), IT workload, breakdown by category, a request rating, SLA breaches, CSV export. The monthly report goes out on the 1st; the morning digest warns about scheduled equipment maintenance, consumables running low and expiring VPN access.",[1519,2869,2871],{"id":2870},"pc-program-an-agent-with-no-installation","PC program: an agent with no installation",[21,2873,2874],{},"A single file, a batch wrapper with PowerShell inside, runs on Windows 10\u002F11 without administrator rights and connects to nothing. It collects the PC name, serial number, OS with activation status via the licensing service, hardware, network, printers, installed software, local accounts, mail profiles and 1C databases, asks the person for phone numbers and passwords, and writes a JSON report to the desktop. Passwords are encrypted on the employee's computer (AES-256 with the key wrapped by RSA-OAEP); the bot decrypts them and moves them into the registry. A repeat report from the same machine produces a diff: a memory stick disappeared, the IP changed, new software appeared. A silent mode exists for running over the network or from a scheduler.",[1519,2876,2878],{"id":2877},"aiogram-3-and-sqlalchemy-async","aiogram 3 and SQLAlchemy async",[21,2880,2881],{},"The bot is written on aiogram 3, roles are injected through middleware, unfinished dialogues live in Redis and survive a restart. The database is MariaDB with 20 tables and 16 Alembic migrations; taking a ticket locks the row so two administrators cannot take the same one. Docker Compose brings up MariaDB and Redis with health checks, and migrations apply automatically. APScheduler runs reminders, escalations, the digest, the monthly report and the weekly database dump.",[1426,2883,1578],{"id":1577},[1444,2885,2886,2889,2892,2895,2898,2901],{},[1447,2887,2888],{},"Every request goes through one channel and is stored in the database, not in chat history.",[1447,2890,2891],{},"A ticket cannot be closed without the employee's confirmation; support quality is measured with a 1–10 rating.",[1447,2893,2894],{},"Unanswered tickets and SLA breaches are raised by the bot itself: escalation, digest, SLA section.",[1447,2896,2897],{},"Infrastructure credentials are stored encrypted with a view log.",[1447,2899,2900],{},"Computer passports are collected by a program instead of an office walk-through; a repeat report shows what changed.",[1447,2902,2903],{},"Management gets a monthly report and a dashboard without access to tickets.",[1426,2905,1602],{"id":1601},[1444,2907,2908,2913,2919,2924,2929,2934,2940],{},[1447,2909,2910,2912],{},[1450,2911,1609],{}," — the asynchronous bot; all menus are inline buttons.",[1447,2914,2915,2918],{},[1450,2916,2917],{},"SQLAlchemy 2.0 async and Alembic"," — database access and schema migrations without data loss.",[1447,2920,2921,2923],{},[1450,2922,297],{}," — storage for tickets, employees, the site registry and credentials in a separate container.",[1447,2925,2926,2928],{},[1450,2927,1362],{}," — storage for unfinished dialogues that survives a bot restart.",[1447,2930,2931,2933],{},[1450,2932,306],{}," — visit reminders, escalation, digest, monthly report, database dump.",[1447,2935,2936,2939],{},[1450,2937,2938],{},"cryptography (Fernet)"," — encryption of registry passwords.",[1447,2941,2942,2944],{},[1450,2943,650],{}," — one way to run everything, with health-check dependencies between services.",[1426,2946,1647],{"id":1646},[21,2948,2949],{},"Version 2.24.0 of 29 September 2026, in production at the client, development continues. Planned per the repository roadmap: automated tests for status transitions and role permissions, backups outside Telegram, liveness monitoring, AI-generated reports. The PC program targets Windows; the bot serves one organisation and one IT chat. Other client details are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":2951},[2952,2953,2954,2963,2964,2965],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":2955},[2956,2957,2958,2959,2960,2961,2962],{"id":2821,"depth":1661,"text":2822},{"id":2831,"depth":1661,"text":2832},{"id":2838,"depth":1661,"text":2839},{"id":2848,"depth":1661,"text":2849},{"id":2855,"depth":1661,"text":2856},{"id":2870,"depth":1661,"text":2871},{"id":2877,"depth":1661,"text":2878},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"🎫",[2968,2971,2974,2977,2980,2983],{"q":2969,"a":2970},"How do you track IT requests in Telegram without a separate ticketing tool?","An employee files a ticket in the bot in four steps: category, location, photo, description. A card with buttons lands in the IT team's chat and is redrawn on every status change. The whole cycle — priority, SLA, visit, report, closure, rating — happens right there and is stored in MariaDB.",{"q":2972,"a":2973},"Can an administrator close a ticket without the employee knowing?","No. After it is marked as solved, the ticket waits, and only the employee's reply closes it. If the employee says it is not solved, the ticket reopens. After closure the employee gives a 1–10 rating, and the assignee leaves a short report: what was done, which parts were used, how long it took.",{"q":2975,"a":2976},"How does an SLA work in a Telegram bot?","The priority is set when the ticket is taken and defines the resolution target. The default response target for a new ticket is 30 minutes. Overdue tickets are highlighted in the queue, listed in the SLA section and in the morning digest, and tickets nobody reacts to are escalated: first to the chat, then to administrators personally, then to the owner.",{"q":2978,"a":2979},"Can the same bot track computers and workstations?","Yes. The asset registry has four dimensions: departments, rooms, workstations and equipment. Each device has a passport, a service history and a link to a desk. A small program for the PC collects the serial number, OS, activation, network and installed software on its own, and a QR label on the desk opens the bot and shows who sits there.",{"q":2981,"a":2982},"Is it safe to keep router and camera passwords in a Telegram bot?","The database stores Fernet ciphertext, not the password, and the key lives only in the server environment. A password is shown in a separate message the bot deletes after a minute, and every view is logged with a name and a date. Managers have no access to the registry. Credentials whose loss would stop the business are not meant for the bot.",{"q":2984,"a":2985},"What does management get?","A separate dashboard with no access to tickets: a period summary, critical nodes, IT workload, SLA breaches and a CSV export without personal contacts. The monthly report arrives on the 1st automatically, the morning digest on working days.",[],{},"\u002Fen\u002Fkeysy\u002Fhelp-desk",[],[2991,2994,2997],{"value":2992,"label":2993},"4 roles","employee, administrator, manager, owner — each with its own interface",{"value":2995,"label":2996},"30 min","response target for a new ticket, then escalation to the chat, admins and owner",{"value":2998,"label":2999},"1–10","employee rating after closure: support quality becomes measurable",{"title":2766,"description":28},"\u002Fshots\u002Fdevunit-lab-tiket-sistema.jpg","DevUnit Lab ticket system in Telegram: the admin panel with the request queue, SLA, equipment register and reports",[293,294,3004,2426,297,1362,306,120],"SQLAlchemy","en\u002Fkeysy\u002Fhelp-desk","for a museum complex in Moscow: tickets, asset tracking, knowledge base and analytics","IT help desk inside Telegram with no Jira: SLA, closure only after the employee confirms, 1–10 ratings, an encrypted credential registry, computer asset tracking, a knowledge base and reports for management. Python, aiogram 3, MariaDB.","-d0XXPUCtEy_860RDGcnFyLvPHVxRwriE-TjgkNV25I",{"id":3010,"title":3011,"body":3012,"client":28,"cover":28,"date":3184,"demoUrl":28,"description":28,"direction":1230,"emoji":3185,"extension":33,"faq":3186,"featured":79,"links":3205,"meta":3210,"navigation":79,"nda":94,"order":263,"own":79,"path":3211,"related":3212,"results":3213,"seo":3223,"shot":3224,"shotAlt":3225,"stack":3226,"status":3228,"stem":3229,"subtitle":3230,"summary":3231,"__hash__":3232},"cases_en\u002Fen\u002Fkeysy\u002Foshaplay.md","OshaPlay: audiophile music player for Android",{"type":18,"value":3013,"toc":3169},[3014,3016,3019,3022,3024,3027,3056,3058,3062,3065,3069,3072,3076,3079,3083,3086,3090,3093,3097,3100,3102,3119,3121,3161,3163,3166],[1426,3015,1429],{"id":1428},[21,3017,3018],{},"Android's system mixer (AudioFlinger) resamples any audio to its own output rate, usually 48 kHz. A 24-bit 96 kHz FLAC therefore reaches the DAC already altered, and ordinary players say nothing about it. On Android 12 there is no built-in way around the mixer: bit-perfect USB output only appeared in Android 14.",[21,3020,3021],{},"The second problem is trust in files. Transcoded MP3s and stretched CD rips are often sold as Hi-Res, and by ear the difference is not always obvious. The third is that an ordinary player is a poor fit for audiobooks: it doesn't remember the position in each book, doesn't offer a separate speed per book, and leaves no trace of where you fell asleep.",[1426,3023,1439],{"id":1438},[21,3025,3026],{},"OshaPlay is a player for your own library. It talks to a USB DAC itself over the USB Audio Class (UAC2) protocol, delivers the track at its original sample rate and bit depth, and lets the DAC handle the volume. A complete player is built around that. What has been built, according to the release log:",[1444,3028,3029,3032,3035,3038,3041,3044,3047,3050,3053],{},[1447,3030,3031],{},"A library: albums with covers, artists, all tracks, folder browsing and search. The scanner reads only new and changed files.",[1447,3033,3034],{},"Playlists with their own cover art and drag-and-drop track order; a queue labeled with where it is playing from.",[1447,3036,3037],{},"Background playback of FLAC, m4a, MP3 and Opus, a notification and lock-screen controls.",[1447,3039,3040],{},"Direct bit-for-bit output to a USB DAC, and a custom resampler for 88.2 and 176.4 kHz.",[1447,3042,3043],{},"Real-lossless verification and a “Fakes” filter.",[1447,3045,3046],{},"A Books section: a shelf with progress, chapter order, a speed setting per book and a sleep timer with markers.",[1447,3048,3049],{},"One search across RuTracker, Bandcamp, Jamendo and Live Music Archive, with a built-in torrent client.",[1447,3051,3052],{},"Over-the-air self-updates with signature verification, and reports to the developer.",[1447,3054,3055],{},"“Not music”: hide voice recorder files, call recordings and voice messages from the library, search and collections.",[1426,3057,1517],{"id":1516},[1519,3059,3061],{"id":3060},"direct-usb-dac-output-that-bypasses-the-mixer","Direct USB DAC output that bypasses the mixer",[21,3063,3064],{},"The app claims the DAC through the USB Host API and sends audio in isochronous transfers over usbfs. An isochronous transfer is a USB mode with a guaranteed stream rate, the same kind audio uses. The output is written in C++ (NDK) with a ring buffer and plugged into Media3 as a custom AudioSink. The sample rate follows the track, and volume is changed with the phone's buttons and applied on the DAC side. “Hand DAC to system” and “Take DAC” buttons switch the mode, and after a minute of pause the DAC is handed back to the system automatically.",[1519,3066,3068],{"id":3067},"a-custom-resampler-with-distortion-below-130-db","A custom resampler with distortion below −130 dB",[21,3070,3071],{},"A resampler converts the sample rate. If the DAC lacks 88.2 and 176.4 kHz, a custom polyphase resampler steps in, keeping distortion below −130 dB. While the player holds the DAC, Android can't see it, so notifications and calls sound from the phone's speaker.",[1519,3073,3075],{"id":3074},"catching-fake-hi-res-by-its-spectrum","Catching fake Hi-Res by its spectrum",[21,3077,3078],{},"The player parses FLAC frames, decodes them to float and builds a spectrum (a fast Fourier transform, FFT, in Kotlin). An MP3 transcode shows up as a frequency cutoff, a stretched CD as a sharp spectrum drop around 19 to 24.5 kHz, and 24-bit files are checked for empty low bits. The verdict appears as an icon on every track, and tapping it opens a breakdown: the track's numbers and an explanation of why a spectrum ending near 22 kHz at 44.1 and 48 kHz is a format limit, not a fake.",[1519,3080,3082],{"id":3081},"audiobooks-position-speed-and-sleep-markers","Audiobooks: position, speed and sleep markers",[21,3084,3085],{},"Books are detected by genre and by folders named “Аудиокниги”, “Книги” or “Audiobooks”. Chapters follow the numbers in the tags or the numbers in file names (“2” before “10”); if the guess is wrong, a chapter can be dragged into place. Speed from 0.8× to 2.5× is saved per book and also works in direct DAC output. The sleep timer leaves the bookmarks “Sleep timer set” and “Fell asleep”, and the last five nights are kept. After a long pause the book rewinds slightly: 5 seconds after a minute, 15 after half an hour, 30 after a day.",[1519,3087,3089],{"id":3088},"stability-on-huawei-with-the-screen-off","Stability on Huawei with the screen off",[21,3091,3092],{},"Audio stutter with the screen off was traced through phone reports: the EMUI shell was freezing the player for seconds. The DAC audio queue was set back to 0.4 s, and the settings gained “EMUI app launch” and “Battery saver” buttons with an explanation. If the player is still put to sleep mid-output, a “Music may be interrupted” notification arrives once a day.",[1519,3094,3096],{"id":3095},"self-updates-and-reports","Self-updates and reports",[21,3098,3099],{},"The release list is signed with an ECDSA P-256 key; the app downloads a release and installs it silently when music isn't playing. A log of up to 5 MB, the phone's audio profile, headphones and listening statistics go to the developer's receiver; more in the case on the report receiver.",[1426,3101,1578],{"id":1577},[1444,3103,3104,3107,3110,3113,3116],{},[1447,3105,3106],{},"Tracks go to the DAC bit for bit: the sample rate follows the track and the DAC itself controls volume (verified through reports from the client's phone, per the README).",[1447,3108,3109],{},"A custom resampler with distortion below −130 dB for 88.2 and 176.4 kHz.",[1447,3111,3112],{},"Fake Hi-Res doesn't get into the “Hi-Res” section.",[1447,3114,3115],{},"One search across four music sources with built-in downloading.",[1447,3117,3118],{},"A Books section with its own speed and position for every book.",[1426,3120,1602],{"id":1601},[1444,3122,3123,3129,3135,3140,3146,3152],{},[1447,3124,3125,3128],{},[1450,3126,3127],{},"Kotlin, Jetpack Compose, Material 3",": the app's interface.",[1447,3130,3131,3134],{},[1450,3132,3133],{},"Media3 (ExoPlayer, MediaSessionService)",": playback and the background service.",[1447,3136,3137,3139],{},[1450,3138,808],{},": the DAC stream and the resampler, where timing and precision matter.",[1447,3141,3142,3145],{},[1450,3143,3144],{},"Android USB Host API",": access to the DAC and UAC1\u002FUAC2 class requests.",[1447,3147,3148,3151],{},[1450,3149,3150],{},"libtorrent4j",": the built-in torrent client for downloading into the library.",[1447,3153,3154,3156,3157,3160],{},[1450,3155,1366],{},": the library index; ",[1450,3158,3159],{},"WorkManager, PackageInstaller",": self-updates.",[1426,3162,1647],{"id":1646},[21,3164,3165],{},"Version 0.10.0, released on September 28, 2026; in development, our own product. The app updates over the air. Planned next: ToshaMusic as another Hi-Res source, and a Windows build with WASAPI Exclusive.",[21,3167,3168],{},"Limitations per the README: direct output has been tested in the emulator only for the case of no DAC, and work with a real dongle is visible through reports from the phone; because of a Cloudflare check, the user signs in to RuTracker themselves in the built-in browser; Bandcamp was checked by search in the emulator, while buying and downloading were checked only on the client's phone; Jamendo has not been tested without a key. The quality check is heuristic.",{"title":28,"searchDepth":29,"depth":29,"links":3170},[3171,3172,3173,3181,3182,3183],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":3174},[3175,3176,3177,3178,3179,3180],{"id":3060,"depth":1661,"text":3061},{"id":3067,"depth":1661,"text":3068},{"id":3074,"depth":1661,"text":3075},{"id":3081,"depth":1661,"text":3082},{"id":3088,"depth":1661,"text":3089},{"id":3095,"depth":1661,"text":3096},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-28","🎧",[3187,3190,3193,3196,3199,3202],{"q":3188,"a":3189},"What is bit-perfect playback and why does Android spoil it?","Bit-perfect means a track reaches the DAC (digital-to-analog converter) unchanged, at its original sample rate and bit depth. Android's system mixer resamples all audio to its own output rate, usually 48 kHz, so a 24-bit 96 kHz FLAC arrives at the DAC already altered. OshaPlay talks to the DAC directly over USB Audio Class and skips the mixer.",{"q":3191,"a":3192},"How do you get bit-perfect output on Android 12 when there is no built-in mode?","Built-in bit-perfect USB output only appeared in Android 14. On Android 12 the app claims the DAC itself through the USB Host API and sends audio in isochronous transfers over usbfs. The sample rate follows the track, and volume is changed with the phone's buttons and applied on the DAC side.",{"q":3194,"a":3195},"Which DACs and phones are supported?","The app targets Android 12 and newer (minSdk 31) and was built for a specific setup: a Huawei P60 Pro, a dongle on the Realtek ALC5686 and Simgot EM6L earphones. Direct output handles UAC2 with adaptive or synchronous transfer only; asynchronous DACs with feedback and UAC1 devices still play through the system.",{"q":3197,"a":3198},"How can I tell that lossless is genuine and not a transcoded MP3?","The player analyzes a file's spectrum to catch MP3 transcodes and stretched CD rips passed off as Hi-Res, and checks 24-bit files for empty low bits. Fakes go to a “Fakes” filter and never appear in the “Hi-Res” section. The check is heuristic, so each verdict can be opened for a breakdown.",{"q":3200,"a":3201},"Can it play audiobooks with a saved position and playback speed?","Yes, in the Books section since version 0.10.0. Books are detected by genre and folder, the position is remembered per book, speed runs from 0.8× to 2.5× with pitch preserved and is stored per book, and there is a sleep timer with markers. Music always plays at 1× bit-perfect.",{"q":3203,"a":3204},"Can a player or USB output like this be built for another device?","Yes, we take such work on commission. In the code the layers are separated by directory: USB descriptor parsing, the C++ direct output, the resampler, quality verification and self-updates. They can be adapted to another DAC or scenario.",[3206],{"label":3207,"url":3208,"note":3209},"Install the app (APK)","https:\u002F\u002Fwww.devunit-lab.ru\u002Fdl\u002Foshaplay\u002FOshaPlay.apk","first install from a file, after that the app updates itself",{},"\u002Fen\u002Fkeysy\u002Foshaplay",[864],[3214,3217,3220],{"value":3215,"label":3216},"Bit-perfect","a track reaches the DAC without system-mixer resampling",{"value":3218,"label":3219},"−130 dB","distortion level of the custom resampler for 88.2 and 176.4 kHz",{"value":3221,"label":3222},"4 sources","RuTracker, Bandcamp, Jamendo and Live Music Archive in one search",{"title":3011,"description":28},"\u002Fshots\u002Fdevunit-lab-oshaplay.jpg","OshaPlay, the DevUnit Lab audio player for Android: the “Now playing” screen with Hi-Res verification and headphone settings",[803,804,3227,808,3150,1366],"Media3 \u002F ExoPlayer","development","en\u002Fkeysy\u002Foshaplay","bit-perfect USB DAC output and audiobooks, our own product at version 0.10.0","An audiophile Android player: bit-perfect output to a USB DAC over UAC2 that bypasses the system mixer, a custom resampler with distortion below −130 dB, Hi-Res fake detection and an audiobook section.","rL8ZdDB4Q-_EzRM5LVXXYPAnFyzwP-4ApLP8FCO_cYg",{"id":3234,"title":3235,"body":3236,"client":28,"cover":28,"date":3439,"demoUrl":28,"description":28,"direction":1193,"emoji":3440,"extension":33,"faq":3441,"featured":79,"links":3460,"meta":3461,"navigation":79,"nda":94,"order":263,"own":79,"path":3462,"related":3463,"results":3464,"seo":3474,"shot":28,"shotAlt":28,"stack":3475,"status":1713,"stem":3476,"subtitle":3477,"summary":3478,"__hash__":3479},"cases_en\u002Fen\u002Fkeysy\u002Ftech-poly-vpn.md","Tech Poly VPN: VPN subscription billing in Telegram",{"type":18,"value":3237,"toc":3424},[3238,3240,3243,3246,3249,3251,3258,3291,3293,3297,3312,3316,3323,3327,3330,3334,3341,3345,3348,3352,3355,3357,3377,3379,3419,3421],[1426,3239,1429],{"id":1428},[21,3241,3242],{},"A VPN is sold by subscription, and every step easily turns into manual work: issue a trial key, accept payment, check the receipt, extend the term in the panel, remind about expiry, work out the discount. A mistake at any step means either a customer without access or access without payment.",[21,3244,3245],{},"Companies add a separate pain. A director pays for employees centrally, the employees do not want to create Telegram accounts, and accounting needs a reconciliation statement based on actual charges at the end of the month. Working that out by hand in a spreadsheet is slow and error-prone.",[21,3247,3248],{},"The goal is one service that takes a customer from first contact to renewal and produces the corporate statement. In this setup the administrator only confirms the receipt.",[1426,3250,1439],{"id":1438},[21,3252,3253,3254,3257],{},"Tech Poly VPN (the ",[1635,3255,3256],{},"polybot"," bot) is the studio's own product. It is an asynchronous Telegram bot that sells subscriptions, issues VLESS keys through the Marzban panel and keeps track of money and terms itself. What is implemented:",[1444,3259,3260,3263,3266,3269,3272,3275,3278,3281,3284],{},[1447,3261,3262],{},"sign-up with an invite code or a corporate promo code, and trial access for a few hours (12 by default);",[1447,3264,3265],{},"SBP payment requests with manual receipt review by an administrator and an atomic confirmation that cannot credit twice;",[1447,3267,3268],{},"creating and renewing a user in Marzban, with the request rolled back to pending if the panel does not respond;",[1447,3270,3271],{},"prices per group, two types of promo codes (percent and days), a referral discount with a cap;",[1447,3273,3274],{},"a corporate track: groups, a reseller role, employee keys not tied to Telegram, a charge ledger and reconciliation statements;",[1447,3276,3277],{},"expiry notifications with per-user quiet hours;",[1447,3279,3280],{},"a backup channel in VKontakte that issues configs when Telegram is unavailable;",[1447,3282,3283],{},"route monitoring, daily backups, broadcasts, an admin panel, statistics and an action log;",[1447,3285,3286,3287,3290],{},"a hidden ",[1635,3288,3289],{},"\u002Fversion"," command available to administrators only.",[1426,3292,1517],{"id":1516},[1519,3294,3296],{"id":3295},"marzban-api-the-bot-as-the-source-of-truth-for-money","Marzban API: the bot as the source of truth for money",[21,3298,3299,3300,3303,3304,3307,3308,3311],{},"Marzban is a VPN node control panel: it creates users and serves traffic. The bot drives it through a REST API and keeps money, terms, discounts and reports itself. The subscription expiry is stored in two places: ",[1635,3301,3302],{},"expire_date"," in the bot's database and ",[1635,3305,3306],{},"expire"," in Marzban, which is what actually cuts off traffic. Writes always go one way: Marzban returns the actual date first, and only that date is saved to the database. So the bot never treats a subscription as active when the traffic is already shut off. In the user's note field in the panel the bot writes ",[1635,3309,3310],{},"@username",", so the administrator sees people rather than anonymous identifiers.",[1519,3313,3315],{"id":3314},"idempotent-payment-a-row-lock-on-confirmation","Idempotent payment: a row lock on confirmation",[21,3317,3318,3319,3322],{},"Idempotency means that repeating an operation does not change the result. It matters here because two administrators clicking “Confirm” at the same moment must not credit the payment twice. The bot opens a transaction, reads the payment with ",[1635,3320,3321],{},"SELECT ... FOR UPDATE"," and checks its status under that lock. If the status is no longer “pending”, the transaction is rolled back and the administrator sees who processed the request and when. The race is excluded at the database level, not by a convention in the code.",[1519,3324,3326],{"id":3325},"rollback-on-a-panel-failure-discount-after-issuing","Rollback on a panel failure, discount after issuing",[21,3328,3329],{},"If a payment is already marked as paid but Marzban did not respond, a dedicated function returns the request to pending. The discount is spent only after the panel has answered successfully, so a failed payment does not burn the accumulated discount. The price is fixed in the ledger at the date of the operation: changing a group's tariff neither rewrites history nor breaks statements that have already been issued.",[1519,3331,3333],{"id":3332},"corporate-groups-and-reconciliation-statements","Corporate groups and reconciliation statements",[21,3335,3336,3337,3340],{},"The reseller role lives in a separate table rather than in a user field: a client's director may not use the service at all. The reseller issues keys to employees with a label such as “Accounting, PC-2”, and no registration in the bot is needed. The charge ledger is append-only, so records are never rewritten. The bot uses it to total a period and close it, and sends statements automatically on the 1st. The manual option is the ",[1635,3338,3339],{},"\u002Fact"," command.",[1519,3342,3344],{"id":3343},"backup-bot-in-vkontakte","Backup bot in VKontakte",[21,3346,3347],{},"In the extended stack a VKontakte bot runs next to the Telegram bot using LongPoll (receiving messages without a public callback address). The account is linked with a one-time code that expires, used trials are tracked, and access can be restored from an old link. Both bots share the Marzban client and retry-policy modules. The client receives a subscription link rather than a raw key, and the admin panel shows a QR code for the user.",[1519,3349,3351],{"id":3350},"operations-monitoring-backups-request-retries","Operations: monitoring, backups, request retries",[21,3353,3354],{},"APScheduler jobs run on Moscow time. Monitoring checks the node's TCP port every 5 minutes and alerts the administrator only after several failed checks in a row, so a single network blip does not wake anyone. A JSON backup is created daily, kept with rotation and can be uploaded to Google Drive. In the extended stack, requests to the database, Marzban, Telegram and VKontakte are retried with a growing delay, and containers shut down gracefully.",[1426,3356,1578],{"id":1577},[1444,3358,3359,3362,3365,3368,3371,3374],{},[1447,3360,3361],{},"Trial keys, purchases and renewals need no manual work except confirming the receipt.",[1447,3363,3364],{},"A second administrator confirming the same payment does not credit it twice.",[1447,3366,3367],{},"A panel failure does not leave a payment without a key: the request goes back to the queue.",[1447,3369,3370],{},"A company gets keys for employees without Telegram accounts and a monthly reconciliation statement built from the charge ledger.",[1447,3372,3373],{},"A customer who cannot reach Telegram gets a config through the backup VKontakte channel.",[1447,3375,3376],{},"The repository has no automated tests; acceptance is manual.",[1426,3378,1602],{"id":1601},[1444,3380,3381,3386,3392,3398,3403,3408,3414],{},[1447,3382,3383,3385],{},[1450,3384,300],{}," — the asynchronous Telegram bot: menus, payments, admin panel.",[1447,3387,3388,3391],{},[1450,3389,3390],{},"MariaDB (InnoDB)"," — subscriptions, payments, promo codes and the charge ledger; transactions and row locks protect against double crediting.",[1447,3393,3394,3397],{},[1450,3395,3396],{},"Marzban API"," — creating, renewing and subscribing VLESS users; the panel and nodes themselves are deployed separately.",[1447,3399,3400,3402],{},[1450,3401,306],{}," — expiry checks, monitoring, backups and reconciliation statements.",[1447,3404,3405,3407],{},[1450,3406,295],{}," — the backup config-delivery channel.",[1447,3409,3410,3413],{},[1450,3411,3412],{},"Google Drive (PyDrive2)"," — optional storage for backups.",[1447,3415,3416,3418],{},[1450,3417,120],{}," — the bot image with a fixed time zone; the extended stack uses a shared docker-compose file.",[1426,3420,1647],{"id":1646},[21,3422,3423],{},"Version 1.0.1 of 30 September 2026: a cleaned-up bot codebase that prints its version to the log on every start. The bot runs in production as our own product. The extended stack with the VKontakte bot and a shared docker-compose file is kept in a separate repository, where polybot 3.5.0 and vkbot 1.2.0 are listed. The README's list of remaining work includes automated tests, an environment template for the VKontakte bot, moving payment details out of the code into environment variables, and a dedicated schema migration tool.",{"title":28,"searchDepth":29,"depth":29,"links":3425},[3426,3427,3428,3436,3437,3438],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":3429},[3430,3431,3432,3433,3434,3435],{"id":3295,"depth":1661,"text":3296},{"id":3314,"depth":1661,"text":3315},{"id":3325,"depth":1661,"text":3326},{"id":3332,"depth":1661,"text":3333},{"id":3343,"depth":1661,"text":3344},{"id":3350,"depth":1661,"text":3351},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-30","🛡",[3442,3445,3448,3451,3454,3457],{"q":3443,"a":3444},"How can I sell VPN subscriptions through a Telegram bot with SBP payments?","The user taps “Buy”, pays through SBP (Russia's Faster Payments System) and sends the receipt. The receipt goes to the administrators with “Confirm” and “Reject” buttons. After confirmation the bot creates or renews the user in the Marzban panel and sends the key with its expiry date.",{"q":3446,"a":3447},"Can a payment be credited twice if two admins click at the same moment?","No. The payment status is read inside a database transaction with a row lock (SELECT ... FOR UPDATE). The second administrator sees who processed the request and when, and the subscription is not extended again.",{"q":3449,"a":3450},"What happens if the Marzban panel does not respond after a payment is confirmed?","The request goes back to the pending state, the money is not lost, and any accumulated discount is spent only after access has been issued successfully. The user is never left with a payment and no key.",{"q":3452,"a":3453},"How do I set up a corporate VPN for employees who do not use Telegram?","A company gets a corporate group with its own price and a reseller role. The reseller issues keys to employees with a readable label, and the employees do not need to register in the bot. Every charge is written to a ledger, and on the 1st of the month the bot builds a reconciliation statement from it.",{"q":3455,"a":3456},"What if Telegram is unavailable?","A backup channel issues configs through a VKontakte bot. The account is linked with a one-time code, and a trial period is available there too. The backup bot is part of the extended stack with a shared docker-compose file.",{"q":3458,"a":3459},"Where is the data stored and what happens when something fails?","Subscriptions, payments and the billing ledger live in MariaDB, and the bot runs in Docker. Backups are made daily locally and, if enabled, to Google Drive, and a final backup is taken on a clean shutdown. Route monitoring alerts the administrator only after several failed checks in a row.",[],{},"\u002Fen\u002Fkeysy\u002Ftech-poly-vpn",[851,702],[3465,3468,3471],{"value":3466,"label":3467},"12 h","default trial access: the user gets a key on their own, with no admin involved",{"value":3469,"label":3470},"2 channels","sales and key issuing in Telegram, backup config delivery in VKontakte",{"value":3472,"label":3473},"1st of month","reconciliation statements for corporate clients are built and sent automatically",{"title":3235,"description":28},[293,294,297,3396,306,295,120],"en\u002Fkeysy\u002Ftech-poly-vpn","our own product: SBP payments, key issuing in Marzban, a backup VKontakte bot","A Telegram bot sells VPN subscriptions: payment via Russia's SBP, key issuing and renewal in the Marzban panel, promo codes, referrals, corporate groups and reconciliation statements with no manual admin work.","BXys1IiXgM_yi05FfilMSD6YHDjUfBCqQ0usxKroHfA",{"id":3481,"title":3482,"body":3483,"client":28,"cover":28,"date":1911,"demoUrl":28,"description":28,"direction":1180,"emoji":3675,"extension":33,"faq":3676,"featured":79,"links":3695,"meta":3696,"navigation":79,"nda":94,"order":263,"own":79,"path":3697,"related":3698,"results":3699,"seo":3709,"shot":28,"shotAlt":28,"stack":3710,"status":1713,"stem":3712,"subtitle":3713,"summary":3714,"__hash__":3715},"cases_en\u002Fen\u002Fkeysy\u002Funlock.md","UNLOCK: Interactive Greeting Cards and Advent Calendars",{"type":18,"value":3484,"toc":3660},[3485,3487,3490,3493,3507,3509,3516,3518,3544,3546,3550,3557,3561,3568,3572,3575,3579,3582,3586,3589,3593,3596,3598,3618,3620,3655,3657],[1426,3486,1429],{"id":1428},[21,3488,3489],{},"An ordinary greeting card is a picture or a template landing page that the recipient scrolls past in a second. Nobody will deploy and pay for a separate backend-driven site for every personal occasion. We needed a way to turn a link into a small story without launching a new project for each client.",[21,3491,3492],{},"Additional requirements:",[1444,3494,3495,3498,3501,3504],{},[1447,3496,3497],{},"the service owner creates and edits cards alone, from a phone, without a developer;",[1447,3499,3500],{},"one card is sent to many guests, and each sees their own name;",[1447,3502,3503],{},"clients can be connected and limited by a plan;",[1447,3505,3506],{},"a card opens on a weak phone and does not go down with the server.",[1426,3508,1439],{"id":1438},[21,3510,3511,3512,3515],{},"An ",[1450,3513,3514],{},"interactive card"," is a page where the recipient does something themselves: solves, chooses, opens. We built it as a locked terminal and added an admin panel in Telegram.",[21,3517,2292],{},[1444,3519,3520,3523,3526,3529,3532,3535,3538,3541],{},[1447,3521,3522],{},"A card behind a link with a password screen, the Recovery Protocol, three screens and a finale; the recipient's choice goes to the owner's Telegram.",[1447,3524,3525],{},"A card-creation wizard in the bot with five questions: occasion, to whom, from whom, when, key.",[1447,3527,3528],{},"Seven themes: Romance, Marksman, Bunker, Birthday, Wedding, Neon and Noir.",[1447,3530,3531],{},"A web cabinet with a live preview: fields on the left, the card on the right, and a «How it went» report showing how far the recipient got.",[1447,3533,3534],{},"One card for many guests: the name in the link, bulk link generation, and export of guests and replies to CSV.",[1447,3536,3537],{},"An advent calendar: windows by day, four styles, and the password and date checked by the server.",[1447,3539,3540],{},"Plans, owner, operator and client roles, invitation codes, private cards and closed drafts.",[1447,3542,3543],{},"Delayed publishing, a QR code for the link, a webhook for submissions, and a showcase landing page for the service with demo cards.",[1426,3545,1517],{"id":1516},[1519,3547,3549],{"id":3548},"a-card-made-of-one-html-file","A card made of one HTML file",[21,3551,3552,3553,3556],{},"The card page is a single static HTML file: no build step, no dependencies, no backend. The font sits next to it, the fireworks are drawn by its own code, and sound effects are synthesised with Web Audio. It gets its theme and texts from the service but keeps its own copy of the values, so when the service is unavailable it opens on those and on the cache of the last visit. The copy is not written by hand: a ",[1635,3554,3555],{},"sync_site_defaults"," command generates it from the service schema, and the smoke tests fail if the file drifts from the schema.",[1519,3558,3560],{"id":3559},"a-web-cabinet-as-a-telegram-mini-app","A web cabinet as a Telegram Mini App",[21,3562,3563,3564,3567],{},"A ",[1450,3565,3566],{},"Mini App"," is a web page that opens inside Telegram. Sign-in needs no passwords: Telegram passes signed data about the user, and the service verifies the signature with the bot token on every request. Permissions are the same as in the chat: a client sees only their own cards. On a computer the cabinet opens through a one-time link from the bot: it lives for 10 minutes and is spent only by pressing a button, so messenger link previews do not burn it. The database stores only token hashes, and a session lasts 30 days.",[1519,3569,3571],{"id":3570},"one-card-for-many-guests-the-name-in-the-link","One card for many guests: the name in the link",[21,3573,3574],{},"A birthday, a wedding, a company event: many recipients, one card. A link like «\u002Fc\u002Faddress?to=Name» puts the name into the terminal, the greeting, the ticket and the preview. Without the parameter, the name from the card itself is used, so links sent earlier keep working. The report shows who opened, how far they got and who replied, including those who stayed silent.",[1519,3576,3578],{"id":3577},"the-advent-calendar-the-server-checks-the-password-and-the-date","The advent calendar: the server checks the password and the date",[21,3580,3581],{},"In a card the key is part of the game; in a calendar it guards the gift. A window's contents and files reach the browser only after two server checks: the password is right and the day has come. A window opens at midnight in the recipient's time zone, and missed days are not lost. Letter case, the letter «ё» and extra spaces in the password are ignored.",[1519,3583,3585],{"id":3584},"a-link-preview-for-every-card-in-messengers","A link preview for every card in messengers",[21,3587,3588],{},"A messenger's robot does not run scripts and reads only what is baked into the file. So the service serves the page at «\u002Fc\u002Faddress» and inserts the title, description and image of that very card. Without this, every client would show the same link card in a chat.",[1519,3590,3592],{"id":3591},"plans-and-client-roles","Plans and client roles",[21,3594,3595],{},"Each plan has three numbers: how many cards, how many megabytes per card and how many days it lives. The plan arrives with the invitation code: the owner presses «Code for a client», the person sends the code to the bot and immediately works under their plan. Three days before expiry the card owner gets a warning in the chat.",[1426,3597,1578],{"id":1577},[1444,3599,3600,3603,3606,3609,3612,3615],{},[1447,3601,3602],{},"A new client is connected with an invitation code: no deploy, no separate site and no code changes.",[1447,3604,3605],{},"A card turns from a picture into a short scenario, and the recipient's reply arrives in Telegram.",[1447,3607,3608],{},"A card stays working when the service is unavailable.",[1447,3610,3611],{},"The database publishes no port and is visible only to the service.",[1447,3613,3614],{},"Plans offer five options: from a trial card for 14 days to a Studio plan with an unlimited number of cards.",[1447,3616,3617],{},"Smoke tests run in CI on every push; the README notes 2,305 checks.",[1426,3619,1602],{"id":1601},[1444,3621,3622,3628,3633,3638,3644,3650],{},[1447,3623,3624,3627],{},[1450,3625,3626],{},"Python 3.13 and aiohttp"," — the API that serves card settings and receives the recipient's choice.",[1447,3629,3630,3632],{},[1450,3631,294],{}," — the admin bot on long polling: the creation wizard, edit panels, invitation codes.",[1447,3634,3635,3637],{},[1450,3636,1710],{}," — cards, replies, plans and sessions; timestamps are stored as TIMESTAMPTZ.",[1447,3639,3640,3643],{},[1450,3641,3642],{},"One HTML file"," — the card itself, with no build step or dependencies.",[1447,3645,3646,3649],{},[1450,3647,3648],{},"Docker Compose and nginx"," — two containers, the service and the database; exactly one port is published.",[1447,3651,3652,3654],{},[1450,3653,1395],{}," — smoke tests with Postgres on every push.",[1426,3656,1647],{"id":1646},[21,3658,3659],{},"Version 3.16.1 of 29 September 2026, in production, an in-house product of the studio. The latest releases are the service's showcase landing page and a cabinet that works well on phone and desktop. What is not there yet: payment acceptance, so plans are assigned manually with an invitation code or an owner command. A live run with a real Telegram token has not been done, and the tests run on a stub. The task list is kept in the repository's ROADMAP.",{"title":28,"searchDepth":29,"depth":29,"links":3661},[3662,3663,3664,3672,3673,3674],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":3665},[3666,3667,3668,3669,3670,3671],{"id":3548,"depth":1661,"text":3549},{"id":3559,"depth":1661,"text":3560},{"id":3570,"depth":1661,"text":3571},{"id":3577,"depth":1661,"text":3578},{"id":3584,"depth":1661,"text":3585},{"id":3591,"depth":1661,"text":3592},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"🔓",[3677,3680,3683,3686,3689,3692],{"q":3678,"a":3679},"What is an interactive greeting card behind a password link?","It is a link that opens not a picture but a locked terminal that asks for a password. If the recipient forgot it, a Recovery Protocol with personal questions kicks in. Enter it correctly and the terminal blooms into the theme, followed by three screens: a question, an invitation and a finale where the recipient assembles the evening. The choice arrives in the owner's Telegram.",{"q":3681,"a":3682},"How do you make one card for many guests?","The guest's name is passed in the link as a «?to=» parameter and is not stored in the card. Each guest sees their own name in the terminal, the greeting, on the ticket and in the link preview, and the reply arrives signed with the name. The cabinet has a «Guests» tab: paste a list of names, get a personal link for each, and export the list to CSV.",{"q":3684,"a":3685},"How do you build an online gift quest with an advent calendar?","A calendar is created from the bot chat: «New», «Advent calendar», four questions, and the link is ready. It is a grid of 1 to 31 windows, each opening on its own day in the recipient's time zone. Inside can be a note, photo, GIF, video, video note, voice message, music, a link button or a promo code. The server checks the password and the date.",{"q":3687,"a":3688},"How does the owner edit texts and design without touching code?","From the Telegram bot with buttons or from a web cabinet that opens right inside Telegram, or on a computer through a one-time link from the bot. Any of 72 labels can be changed, along with one of seven themes, the key, media and the link preview. Files for a card can simply be sent to the bot in chat.",{"q":3690,"a":3691},"What happens if the service is down?","The card keeps working on values baked into the file and on the cache of the last visit. The page is a single HTML file with its font stored next to it and fireworks drawn by its own code, so it makes no external requests.",{"q":3693,"a":3694},"How are client access and card lifetimes limited?","Plans set three numbers: how many cards, how much storage per card and how many days a card lives. A client is admitted to the admin panel with an invitation code and sees only their own cards. An expired card goes to the archive: the link stops opening, while the texts and statistics remain.",[],{},"\u002Fen\u002Fkeysy\u002Funlock",[],[3700,3703,3706],{"value":3701,"label":3702},"1 install","serves any number of cards and advent calendars, each with its own address and theme",{"value":3704,"label":3705},"72 labels","edited from the bot and the cabinet with no code or deploys",{"value":3707,"label":3708},"7 themes","for cards, plus 4 more styles for the calendar",{"title":3482,"description":28},[293,3711,294,296,120,119],"aiohttp","en\u002Fkeysy\u002Funlock","our own studio service: a card behind a password link, managed from Telegram","Our own service for interactive greeting cards and advent calendars: the recipient cracks a locked terminal, and the owner edits everything from a Telegram bot and a web cabinet. Python, aiohttp, PostgreSQL.","Ln39fqeUJLxcI89UamzZgk7shLytZy2c0ItwlUZ_5qU",{"id":3717,"title":3718,"body":3719,"client":28,"cover":28,"date":1911,"demoUrl":28,"description":28,"direction":1230,"emoji":3878,"extension":33,"faq":3879,"featured":94,"links":3898,"meta":3899,"navigation":79,"nda":94,"order":438,"own":79,"path":3900,"related":3901,"results":3902,"seo":3910,"shot":28,"shotAlt":28,"stack":3911,"status":1713,"stem":3912,"subtitle":3913,"summary":3914,"__hash__":3915},"cases_en\u002Fen\u002Fkeysy\u002Foshaplay-diag.md","Crash-report receiver for our own apps",{"type":18,"value":3720,"toc":3864},[3721,3723,3726,3729,3731,3734,3770,3772,3776,3779,3783,3794,3798,3801,3805,3808,3812,3815,3817,3831,3833,3856,3858,3861],[1426,3722,1429],{"id":1428},[21,3724,3725],{},"Our own apps, OshaPlay and the VPN client, have testers with phones and computers. A message like “it doesn't work for me” gives the developer nothing to act on: you need the log, the version, the network state and the crash reason. At the same time a report must not become a leak: the VPN client's log may contain subscription links and keys, and the player's report contains details about the phone and headphones.",[21,3727,3728],{},"There is one more condition: reports must not go to third-party services. Testers' data lives on the developer's own servers.",[1426,3730,1439],{"id":1438},[21,3732,3733],{},"Two report receivers, one per app. The app collects diagnostics itself, strips secrets on the device and sends them with a token. What has been built:",[1444,3735,3736,3739,3755,3758,3761,3764,3767],{},[1447,3737,3738],{},"The OshaPlay receiver: accepts a log, crashes and DAC reports as a compressed file of up to 5 MB, keeps them for 90 days within 800 MB.",[1447,3740,3741,3742,2051,3745,2051,3748,2051,3751,3754],{},"The VPN client's receiver on the stand's entry node: accepts four kinds of reports (",[1635,3743,3744],{},"crash",[1635,3746,3747],{},"log",[1635,3749,3750],{},"probe",[1635,3752,3753],{},"user",").",[1447,3756,3757],{},"An on-disk queue in the VPN client: up to 20 files and 50 MB, with reports resent when the link returns.",[1447,3759,3760],{},"Secret masking when the log is written, not when the server receives it.",[1447,3762,3763],{},"Tester mode: automatic reports on probes, crashes and anomalies, on by default and doubling as consent to send.",[1447,3765,3766],{},"Developer scripts: reading OshaPlay reports from the server, and pulling the VPN client's reports to the workstation.",[1447,3768,3769],{},"A liveness check of the OshaPlay receiver is part of the general check of the sites server.",[1426,3771,1517],{"id":1516},[1519,3773,3775],{"id":3774},"token-protected-intake-nothing-served-outward","Token-protected intake, nothing served outward",[21,3777,3778],{},"A receiver is a small Python program in a Docker container. A token is required on intake: without it a report is not accepted. Reports are never served outward, and only the developer reads them from the server. The OshaPlay receiver sits behind the sites server's shared entry nginx, and the VPN client's receiver sits on the stand's entry node. The same sites server also serves the player's releases for self-updates.",[1519,3780,3782],{"id":3781},"masking-secrets-on-the-device","Masking secrets on the device",[21,3784,3785,3786,3789,3790,3793],{},"Masking (replacing values with placeholders) is done by the client when the log is written. Subscription links, ",[1635,3787,3788],{},"vless:\u002F\u002F"," keys and similar, UUIDs, passwords, tokens and ",[1635,3791,3792],{},"Bearer"," headers are replaced before they are written, so an intercepted or stored report doesn't reveal secrets. The rules are covered by tests on both platforms and were checked on real reports on the server after the first sending.",[1519,3795,3797],{"id":3796},"an-on-disk-queue-and-protection-from-noise","An on-disk queue and protection from noise",[21,3799,3800],{},"If the receiver is unreachable, the VPN client puts a report into an on-disk queue: up to 20 files and 50 MB. Automatic reports of one kind go out no more often than once every 5 minutes. A 413 response from the receiver discards a report, and a 403 stops sending until the app restarts. The OshaPlay player is simpler: with no network, a report waits for the next launch.",[1519,3802,3804],{"id":3803},"what-is-inside-a-report","What is inside a report",[21,3806,3807],{},"A VPN client report carries a header with network, power, memory and session state. App and engine logs share one line format, and the engine writes to a rotating file: 5 files of 5 MB each. An OshaPlay report contains a log of up to 5 MB, the phone's model and firmware, audio outputs and inputs, audio decoders, connected headphones, the DAC check report and accumulated listening statistics. Music files are never sent.",[1519,3809,3811],{"id":3810},"when-a-report-goes-out","When a report goes out",[21,3813,3814],{},"OshaPlay sends a report every 12 hours, after a crash on the next launch, and immediately on a playback error; there is also a “Send report to the developer” button. The VPN client sends an archive with the app and engine logs on the “Send diagnostics” button, and in tester mode it reports problem entry points and anomalies by itself: a reconnect loop, an engine failure, a threefold latency jump, a twofold memory growth.",[1426,3816,1578],{"id":1577},[1444,3818,3819,3822,3825,3828],{},[1447,3820,3821],{},"Two receivers run in production: for the OshaPlay player and for the VPN client.",[1447,3823,3824],{},"VPN client reports on the server contain no UUIDs, subscription paths or subscription links, checked on real reports.",[1447,3826,3827],{},"A “Send diagnostics” archive from the emulator reached the receiver and carried both logs with no secrets.",[1447,3829,3830],{},"OshaPlay reports are used to analyze real failures: the cause of audio stutter with the screen off was found from phone reports.",[1426,3832,1602],{"id":1601},[1444,3834,3835,3840,3845,3850],{},[1447,3836,3837,3839],{},[1450,3838,293],{},": the receiver's code.",[1447,3841,3842,3844],{},[1450,3843,120],{},": packaging and running the receiver as a separate container.",[1447,3846,3847,3849],{},[1450,3848,119],{},": the HTTPS entry through which reports reach the receiver.",[1447,3851,3852,3855],{},[1450,3853,3854],{},"An intake token",": only our own apps get access, and the key is kept outside the repository.",[1426,3857,1647],{"id":1646},[21,3859,3860],{},"In production. The OshaPlay receiver was added in sites server release 1.11.0 of September 28, 2026; the current sites server version is 1.16.0 of September 29, 2026. The VPN client's receiver runs on the stand's entry node and accepts reports from the Android, Windows and Windows 7 clients.",[21,3862,3863],{},"A known limitation per the VPN client's README: while the entry node is unreachable, reports wait in the on-disk queue and are not lost until the queue fills up. Tester mode is on by default because all builds are still test builds; for a public release it has to be turned off.",{"title":28,"searchDepth":29,"depth":29,"links":3865},[3866,3867,3868,3875,3876,3877],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":3869},[3870,3871,3872,3873,3874],{"id":3774,"depth":1661,"text":3775},{"id":3781,"depth":1661,"text":3782},{"id":3796,"depth":1661,"text":3797},{"id":3803,"depth":1661,"text":3804},{"id":3810,"depth":1661,"text":3811},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"🩺",[3880,3883,3886,3889,3892,3895],{"q":3881,"a":3882},"How do you collect crash reports from an app without third-party cloud services?","The app itself sends its log, version, network state and crash reason to a receiver running on your own server. Sending is token-protected, and the developer reads the reports with a script. Instead of “it doesn't work for me” in a chat, you get a set of data to work with, and all of it stays with the owner.",{"q":3884,"a":3885},"Which reports does the VPN client's receiver accept?","Four kinds: a crash, a log sent with the “Send diagnostics” button (log), entry-point probe results (probe) and a user message (user). Tester mode, on by default, also sends automatic reports on crashes and anomalies, such as a reconnect loop, an engine failure or a threefold latency jump.",{"q":3887,"a":3888},"Can keys or subscription links end up in a report?","Not in the VPN client's reports: subscription links, keys, UUIDs, passwords and tokens are masked on the device when the log is written, before sending. This was checked on real reports on the server, which contain no UUIDs, subscription paths or links. OshaPlay never sends music files.",{"q":3890,"a":3891},"What happens if the receiver is unreachable?","The VPN client puts reports into an on-disk queue (up to 20 files and 50 MB) and sends them when the receiver is back. The OshaPlay player, when there is no network, keeps the report until the next launch. Sending never blocks the app.",{"q":3893,"a":3894},"How long are reports kept, and who can see them?","The OshaPlay receiver keeps reports for 90 days, up to 800 MB in total, with a single report of at most 5 MB compressed. Reports are never served outward: only the developer reads them. Intake is protected with a token.",{"q":3896,"a":3897},"Can such a receiver be built for our team's app?","Yes. The scheme carries over to another app: it needs a report format, a token, a send queue on the device and a small Python service in Docker behind nginx. Both implementations, for the player and for the VPN client, already run in production.",[],{},"\u002Fen\u002Fkeysy\u002Foshaplay-diag",[846,851],[3903,3906,3908],{"value":3904,"label":3905},"2 receivers","one for the OshaPlay player and one for the VPN client, both on our own servers",{"value":2242,"label":3907},"in VPN client reports: UUIDs, subscription links and keys are masked on the device",{"value":1947,"label":3909},"retention of OshaPlay reports, up to 800 MB in total",{"title":3718,"description":28},[293,120,119],"en\u002Fkeysy\u002Foshaplay-diag","OshaPlay and the VPN client send crashes and logs to servers we run ourselves","Our own crash-report and log receivers for the OshaPlay player and the VPN client: data goes to our own server with a token, secrets are masked on the device, and a report waits in a queue if the link is down.","OCnzHBhWlqA7sUoiPCThx1FbmPECa7zwMnkG1AUrA4c",{"id":3917,"title":3918,"body":3919,"client":28,"cover":28,"date":1911,"demoUrl":4131,"description":28,"direction":1180,"emoji":4160,"extension":33,"faq":4161,"featured":94,"links":4180,"meta":4181,"navigation":79,"nda":94,"order":438,"own":79,"path":4182,"related":4183,"results":4184,"seo":4194,"shot":4195,"shotAlt":4196,"stack":4197,"status":1713,"stem":4198,"subtitle":4199,"summary":4200,"__hash__":4201},"cases_en\u002Fen\u002Fkeysy\u002Fsajt-studii.md","DevUnit Lab: A Bilingual Studio Website on Nuxt 4",{"type":18,"value":3920,"toc":4143},[3921,3923,3926,3929,3943,3945,3952,3954,3984,3986,3990,4001,4005,4023,4027,4035,4039,4045,4049,4056,4060,4067,4069,4086,4088,4123,4125,4136,4140],[1426,3922,1429],{"id":1428},[21,3924,3925],{},"The previous studio site was one long page: service areas and cases had no URLs of their own, and therefore no search queries of their own. The studio needed a site where every service area has its own page, every case has its own URL, and there is an English version and a glossary for clients who are far from IT.",[21,3927,3928],{},"Additional conditions:",[1444,3930,3931,3934,3937,3940],{},[1447,3932,3933],{},"content is edited as text files in git, with no admin panel and no database;",[1447,3935,3936],{},"clients under NDA are anonymised, while our own products are named;",[1447,3938,3939],{},"price lists switch on with a single flag once they are written;",[1447,3941,3942],{},"the site opens fast on a phone from an ad link.",[1426,3944,1439],{"id":1438},[21,3946,3947,3948,3951],{},"A multi-page site on Nuxt 4 and Nuxt Content 3 with server-side rendering. ",[1450,3949,3950],{},"Nuxt Content"," is a module that turns markdown and YAML files into site pages and validates their structure against a schema, so a typo in a field never reaches a visitor.",[21,3953,2292],{},[1444,3955,3956,3959,3966,3969,3972,3975,3978,3981],{},[1447,3957,3958],{},"Home page: headline, key figures, service areas, featured cases, work stages, the stack by group, common questions and answers, and a brief form.",[1447,3960,3961,3962,3965],{},"A page for each of the five service areas: lead, stack, its own questions and answers, and the area's cases. The case list is built from the ",[1635,3963,3964],{},"direction"," field, with no hand-made lists.",[1447,3967,3968],{},"A case catalogue with a filter in the URL; a case page shows the status, an NDA or «own product» label, result tiles, the stack and related cases.",[1447,3970,3971],{},"Russian and English versions with language detection and a switcher.",[1447,3973,3974],{},"A glossary of 52 IT terms in each language; badges on technologies link to it with a hint.",[1447,3976,3977],{},"A chat widget and a brief form on the home page.",[1447,3979,3980],{},"Light and dark themes, floating blocks, a cursor glow, blocks revealing on scroll and a bottom navigation bar on phones.",[1447,3982,3983],{},"Yandex Metrica with goals and a cookie notice; both switch on only when a counter number is set.",[1426,3985,1517],{"id":1516},[1519,3987,3989],{"id":3988},"content-in-markdown-instead-of-a-database","Content in markdown instead of a database",[21,3991,3992,3993,3996,3997,4000],{},"Cases, service areas, the glossary and shared data live in the ",[1635,3994,3995],{},"content\u002F"," folder as markdown, YAML and JSON. The field schema is described with Zod and checked at startup: a case must have a subtitle, an emoji and a direction, and the status is chosen from a list. The English version lives in ",[1635,3998,3999],{},"content\u002Fen\u002F"," under the same file names and with the same schema.",[1519,4002,4004],{"id":4003},"nuxt-i18n-and-hreflang-for-two-languages","Nuxt i18n and hreflang for two languages",[21,4006,4007,4010,4011,4014,4015,4018,4019,4022],{},[1450,4008,4009],{},"hreflang"," is a hint to a search engine that a page has a version in another language. Russian has no prefix and English uses ",[1635,4012,4013],{},"\u002Fen",". The ",[1635,4016,4017],{},"@nuxtjs\u002Fi18n"," module picks the language from the browser, remembers the choice and puts ",[1635,4020,4021],{},"lang"," and hreflang into every page head. Russian and English texts are stored in separate content collections.",[1519,4024,4026],{"id":4025},"sitemap-and-robotstxt-as-server-routes","Sitemap and robots.txt as server routes",[21,4028,4029,2055,4032,4034],{},[1635,4030,4031],{},"sitemap.xml",[1635,4033,2336],{}," are built in code from the content collections, so a new case gets into the sitemap by itself, in both language versions. One environment variable switches a staging site to «do not index»: it adds meta robots noindex and a disallow rule in robots.txt.",[1519,4036,4038],{"id":4037},"json-ld-structured-data-for-search-and-ai-answers","JSON-LD structured data for search and AI answers",[21,4040,4041,4044],{},[1450,4042,4043],{},"JSON-LD"," is data about a page in a format robots read: who we are, which questions we answer, what a term means. The home page carries Organization and WebSite, question-and-answer blocks carry FAQPage, and the glossary carries DefinedTermSet.",[1519,4046,4048],{"id":4047},"captcha-free-brief-anti-spam","Captcha-free brief anti-spam",[21,4050,4051,4052,4055],{},"The brief goes to the ",[1635,4053,4054],{},"\u002Fapi\u002Flead"," server route. It drops requests with a filled hidden field, requests sent less than four seconds after the form appeared, and texts with three or more links, and it rejects a fourth request within 10 minutes from one address. The bot gets «ok» in reply, so it is not told what failed. The per-address counter is kept in process memory and resets when the container restarts.",[1519,4057,4059],{"id":4058},"one-container-on-the-web-server","One container on the web server",[21,4061,4062,4063,4066],{},"The image is built locally, because the web server does not have enough memory to build Nuxt, and is uploaded ready-made with ",[1635,4064,4065],{},"docker save",". The container does not publish a port: only the front nginx sees it, through a dedicated Docker network. The container's time zone is set to Europe\u002FMoscow and its memory is limited to 512 MB.",[1426,4068,1578],{"id":1577},[1444,4070,4071,4074,4077,4080,4083],{},[1447,4072,4073],{},"Since 29 September 2026 the site has run on the main domain, and the old staging host redirects to it.",[1447,4075,4076],{},"Every service area and every case has its own URL, and every page has its own title, description and markup.",[1447,4078,4079],{},"The site runs without a database and an admin: content changes go through git.",[1447,4081,4082],{},"A glossary of 52 terms is available in Russian and English.",[1447,4084,4085],{},"The brief is protected from bots without a captcha and without extra steps for the visitor.",[1426,4087,1602],{"id":1601},[1444,4089,4090,4096,4102,4107,4112,4117],{},[1447,4091,4092,4095],{},[1450,4093,4094],{},"Nuxt 4, Vue 3, TypeScript"," — server-side rendering, so search engines see ready text.",[1447,4097,4098,4101],{},[1450,4099,4100],{},"Nuxt Content 3"," — content in markdown and YAML, structure checked against a Zod schema, queries over collections.",[1447,4103,4104,4106],{},[1450,4105,4017],{}," — Russian and English, language detection, hreflang.",[1447,4108,4109,4111],{},[1450,4110,2519],{}," — layout and our own design tokens in one place.",[1447,4113,4114,4116],{},[1450,4115,120],{}," — one container per web server, with the Europe\u002FMoscow time zone.",[1447,4118,4119,4122],{},[1450,4120,4121],{},"Yandex Metrica"," — goals for contact clicks, the brief and the chat; connected only when a counter number is set, with a cookie notice next to it.",[1426,4124,1647],{"id":1646},[21,4126,4127,4128,4135],{},"Version 1.1.0 of 29 September 2026, in production at ",[4129,4130,4134],"a",{"href":4131,"rel":4132},"https:\u002F\u002Fwww.devunit-lab.ru",[4133],"nofollow","devunit-lab.ru",". Still open: not all price lists are written, so the «Cost» block is hidden in the sections; cases have no real screenshots, only automatic covers. The repository has no automated tests.",[1519,4137,4139],{"id":4138},"the-previous-version","The previous version",[21,4141,4142],{},"The previous site was a set of static pages in plain HTML, CSS and JavaScript with Tailwind CSS 3 for styles. It included a studio page in Russian and English with a brief, a chat and a glossary, an English-language site for a foreign audience, and a landing page called «Дверной ГОСТ» for an interior-door installer. History was kept by date from 30 July 2026, with no version numbers. The studio site in Russian and English has been replaced by the Nuxt version; the English-language site and the landing page are described in the repository as static.",{"title":28,"searchDepth":29,"depth":29,"links":4144},[4145,4146,4147,4155,4156,4157],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":4148},[4149,4150,4151,4152,4153,4154],{"id":3988,"depth":1661,"text":3989},{"id":4003,"depth":1661,"text":4004},{"id":4025,"depth":1661,"text":4026},{"id":4037,"depth":1661,"text":4038},{"id":4047,"depth":1661,"text":4048},{"id":4058,"depth":1661,"text":4059},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647,"children":4158},[4159],{"id":4138,"depth":1661,"text":4139},"🧪",[4162,4165,4168,4171,4174,4177],{"q":4163,"a":4164},"Can a studio website be built without WordPress and without a database?","Yes. The devunit-lab.ru site is built on Nuxt 4 and Nuxt Content 3: pages, cases and the glossary live in markdown and JSON files in git, and one container does the server-side rendering. Editing the site means editing a file and rebuilding. The container needs 150 to 300 MB of memory.",{"q":4166,"a":4167},"How do you build a bilingual site with correct URLs for search engines?","The Russian version opens with no prefix and the English one at \u002Fen. The @nuxtjs\u002Fi18n module detects the language from the browser on the first visit and remembers the choice in a cookie. Every page has hreflang, and the English content lives in a separate folder with the same file names.",{"q":4169,"a":4170},"What SEO markup does the studio site use?","Every page has its own title, description and Open Graph tags and a canonical URL, while the sitemap and robots.txt are generated by server routes from the content collections. JSON-LD markup: Organization and WebSite on the home page, FAQPage for questions and answers, DefinedTermSet for the glossary. In staging mode one environment variable closes the site to indexing.",{"q":4172,"a":4173},"Why does the site need an IT glossary?","It explains in plain words what Docker, self-hosted or n8n mean for a client. The glossary holds 52 terms in each language, and technology badges on the site carry a «What is it?» hint that links to the right term.",{"q":4175,"a":4176},"How does a request arrive from the site, and is the form protected from spam?","The brief is sent to the \u002Fapi\u002Flead server route, which passes it to a separate request-intake service; without that service the form runs in demo mode and suggests writing on Telegram. Protection without a captcha: a hidden trap field, rejection of submissions made under four seconds after the form appears, at most three requests per 10 minutes from one address, field length limits and rejection of texts with three links.",{"q":4178,"a":4179},"What did the studio site look like before Nuxt?","The previous site was a set of static pages in HTML, CSS and JavaScript, with styles compiled by Tailwind CSS 3. The Russian and English versions were one large file per language, and the chat and the brief called a service outside the repository. The new version kept the style and animations but gave every service area and every case its own URL.",[],{},"\u002Fen\u002Fkeysy\u002Fsajt-studii",[],[4185,4188,4191],{"value":4186,"label":4187},"RU \u002F EN","two language versions with language detection and hreflang",{"value":4189,"label":4190},"52 terms","a glossary in each language with DefinedTermSet markup",{"value":4192,"label":4193},"1 container","no database and no admin: content in git, 150 to 300 MB of memory",{"title":3918,"description":28},"\u002Fshots\u002Fdevunit-lab-sajt-studii-en.jpg","Home page of the DevUnit Lab studio website: headline, buttons and the container diagram",[2517,4100,2518,116,4017,2519,120],"en\u002Fkeysy\u002Fsajt-studii","our own studio site, in production since 29 September 2026","Our own studio website on Nuxt 4 and Nuxt Content 3: content in markdown with no database, Russian and English versions, JSON-LD markup, a 52-term IT glossary, a case catalogue and a brief form.","OP_Ciq9kVkMJuX4GAOZpcT4nnY7skhziXFxZbUeC9nA",{"id":4203,"title":4204,"body":4205,"client":28,"cover":28,"date":4380,"demoUrl":28,"description":28,"direction":1193,"emoji":4381,"extension":33,"faq":4382,"featured":79,"links":4401,"meta":4402,"navigation":79,"nda":79,"order":438,"own":94,"path":4403,"related":4404,"results":4405,"seo":4415,"shot":28,"shotAlt":28,"stack":4416,"status":1713,"stem":4417,"subtitle":4418,"summary":4419,"__hash__":4420},"cases_en\u002Fen\u002Fkeysy\u002Fstrahovye-pdf.md","Branding e-policies (OSAGO) with a Telegram bot",{"type":18,"value":4206,"toc":4365},[4207,4209,4212,4215,4217,4220,4258,4260,4264,4267,4271,4274,4278,4281,4285,4288,4292,4295,4299,4305,4307,4324,4326,4360,4362],[1426,4208,1429],{"id":1428},[21,4210,4211],{},"Accident commissioners work on call: a client has an accident, takes out the OSAGO policy (Russia's compulsory motor third-party liability insurance) and phones. Previously the firm stuck a label with its phone number onto the printed form and added a stamp. An electronic policy cannot be labelled that way, and most clients keep it on their phone.",[21,4213,4214],{},"The goal is the same PDF with the commissioner's contacts in the right places: within a couple of seconds and without manual layout work. The volume is small, up to 20 forms a day, handled by three people. The key requirement is not to damage the document itself: the form number, the holder's and the car's data, the QR code and the insurer's signature must stay untouched.",[1426,4216,1439],{"id":1438},[21,4218,4219],{},"A Telegram bot accepts a finished electronic OSAGO policy and returns the same one with branding: a yellow card with the commissioner's phone number in place of the insurer's service block, and a “CALL IN CASE OF AN ACCIDENT” stamp over the paragraph with the terms. What is implemented:",[1444,4221,4222,4225,4228,4231,4234,4237,4240,4246],{},[1447,4223,4224],{},"protected zones: the form number, holder's name, VIN, plate number, premium, QR code and electronic signature block, as well as the insurer's logo and hotline, stay where they are;",[1447,4226,4227],{},"automatic placement: the card and the stamp are located from the page text, not from fixed coordinates;",[1447,4229,4230],{},"recognition of forms from four insurance companies, with a dedicated placement strategy for one of them;",[1447,4232,4233],{},"file-size control: a 380 KB policy comes out at roughly 520 KB;",[1447,4235,4236],{},"processing without storage: the document lives in memory and goes straight back;",[1447,4238,4239],{},"an allow-list plus owner approval by button, and processing statistics for the day, week, month, year and per person;",[1447,4241,4242,4243,4245],{},"button menus, a progress bar by processing stage and a hidden ",[1635,4244,3289],{}," command for the owner;",[1447,4247,4248,2051,4251,2055,4254,4257],{},[1635,4249,4250],{},"inspect",[1635,4252,4253],{},"check",[1635,4255,4256],{},"brand"," command-line tools for checking the layout on your own form without the bot.",[1426,4259,1517],{"id":1516},[1519,4261,4263],{"id":4262},"protected-pdf-zones-what-must-never-be-covered","Protected PDF zones: what must never be covered",[21,4265,4266],{},"The first version placed blocks at coordinates from a config file and fell apart on the second form. Now the bot analyzes the page first: text blocks, lines and words, images, the QR code. Anything that looks like policy data goes into a list of forbidden rectangles. The card is then shrunk until it stops overlapping them: the side with the smaller overlap is cut off. The card's width is capped at 45% of the page so that it does not stretch across the whole sheet.",[1519,4268,4270],{"id":4269},"finding-a-block-by-its-text-in-a-pdf","Finding a block by its text in a PDF",[21,4272,4273],{},"The bot finds the place for the card by meaning: every form has a paragraph about the notice of the financial ombudsman, which means nothing to the client. It is found with a regular expression, and its rectangle becomes the card's place. If the paragraph is missing, the insurer's header is used instead. The stamp goes over the paragraph with clauses 4 and 5, which is also located by its start and end.",[1519,4275,4277],{"id":4276},"an-occupancy-map-where-the-page-is-free","An occupancy map: where the page is free",[21,4279,4280],{},"If the target paragraph is not found, free space is chosen from an occupancy map. The page is cut into cells of 1.5 points, a table of sums is built over them, and any rectangle can be checked for emptiness in a single step. The search goes to the right first, then downward.",[1519,4282,4284],{"id":4283},"pdf-size-after-the-overlay","PDF size after the overlay",[21,4286,4287],{},"By default, erasing text under the card also redraws images. On a real form a full-page raster background sits under the text, and the file was being recompressed from 380 KB to 6.5 MB. The fix is to erase only the text and leave images untouched: nothing changes visually, since the card is opaque anyway. Fonts are trimmed to the glyphs actually used, otherwise each would add about 700 KB. The project has a test for this.",[1519,4289,4291],{"id":4290},"card-text-by-priority","Card text by priority",[21,4293,4294],{},"The card text is assembled in priority groups. When space is tight, the insurer's phone, the website, the list of services and the spare number go first, while the main phone number and the name always stay. The font size is fitted to the block height.",[1519,4296,4298],{"id":4297},"automated-acceptance-check-of-the-result","Automated acceptance check of the result",[21,4300,4301,4302,4304],{},"The ",[1635,4303,4253],{}," command is an automated verification of the result: it reports if the card spilled outside its block, the stamp went beyond the clauses 4 and 5 paragraph, or a protected zone was touched. The heavy work runs in a separate thread so that it does not block receiving Telegram messages.",[1426,4306,1578],{"id":1577},[1444,4308,4309,4312,4315,4318,4321],{},[1447,4310,4311],{},"A policy with the commissioner's contacts is ready within a couple of seconds after the file is sent, with no manual layout.",[1447,4313,4314],{},"The form number, the holder's and car's data, the premium and the QR code are never touched.",[1447,4316,4317],{},"The file size after branding stays comparable to the original, about +40%, not several times larger.",[1447,4319,4320],{},"Clients' documents are not stored anywhere: only counters remain on disk.",[1447,4322,4323],{},"The first release shipped with 42 tests on a synthetic form; there is no automated check on real forms.",[1426,4325,1602],{"id":1601},[1444,4327,4328,4333,4339,4344,4350,4355],{},[1447,4329,4330,4332],{},[1450,4331,1709],{}," — the service language.",[1447,4334,4335,4338],{},[1450,4336,4337],{},"PyMuPDF"," — page analysis and redrawing: text blocks, images, erasing and overlaying.",[1447,4340,4341,4343],{},[1450,4342,294],{}," — the Telegram bot: menu, processing progress, statistics, access.",[1447,4345,4346,4349],{},[1450,4347,4348],{},"segno"," — the QR code on the card.",[1447,4351,4352,4354],{},[1450,4353,1366],{}," — processing counters, with no files or their content.",[1447,4356,4357,4359],{},[1450,4358,650],{}," — deployment: a read-only container with no open ports, the bot connects to Telegram itself.",[1426,4361,1647],{"id":1646},[21,4363,4364],{},"Version 1.1.1 of 20 September 2026, in production at the client. Known limitations: after branding the insurer's built-in signature can no longer be verified (authenticity is confirmed through the QR code and on the RSA website); only four insurance companies' forms are supported; two instances must not run with one bot token; the backup of the counters volume is made manually. Other details about the client are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":4366},[4367,4368,4369,4377,4378,4379],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":4370},[4371,4372,4373,4374,4375,4376],{"id":4262,"depth":1661,"text":4263},{"id":4269,"depth":1661,"text":4270},{"id":4276,"depth":1661,"text":4277},{"id":4283,"depth":1661,"text":4284},{"id":4290,"depth":1661,"text":4291},{"id":4297,"depth":1661,"text":4298},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-20","📄",[4383,4386,4389,4392,4395,4398],{"q":4384,"a":4385},"How do I add an accident commissioner's contacts to an electronic OSAGO policy?","An employee sends the policy PDF to the bot and within seconds receives the same file with a yellow contact card and a “CALL IN CASE OF AN ACCIDENT” stamp. The card is placed over a block of the form the client does not need, and the stamp goes over the paragraph with the terms.",{"q":4387,"a":4388},"Can the branding cover the form number, the QR code or the policy data?","No. The bot analyzes the page and marks the form number, the holder's name, VIN, plate number, premium, QR code and the electronic signature block as protected zones. Branding elements never enter those areas. A separate check command reports if anything was touched.",{"q":4390,"a":4391},"What happens to the insurer's built-in electronic signature?","Once the file is modified, the signature can no longer be verified. The policy remains valid, and authenticity is checked through the QR code and on the Russian Union of Motor Insurers (RSA) website. The bot warns about this once and pins the message in the chat.",{"q":4393,"a":4394},"Why does the file not grow several times larger after branding?","The bot erases only the text under the card and leaves images alone, and it keeps only the glyphs actually used in fonts. A 380 KB policy comes out at roughly 520 KB instead of 6.5 MB.",{"q":4396,"a":4397},"Where are clients' policies stored?","Nowhere. The file is processed in memory and sent straight back. Only counters are written to disk: when, who, how many pages and bytes, and how many milliseconds it took. No files, no file names and no policy data.",{"q":4399,"a":4400},"How does the bot tell whose form it is, and does it work with other insurers?","The insurer is identified from the page text, and forms from four insurance companies are supported. For others a general page analysis runs, but without guarantees: coordinates from the settings serve only as a fallback.",[],{},"\u002Fen\u002Fkeysy\u002Fstrahovye-pdf",[],[4406,4409,4412],{"value":4407,"label":4408},"4 insurers","forms of four companies are supported; the insurer is detected from the page text",{"value":4410,"label":4411},"380 → 520 KB","policy size after branding, not 6.5 MB as in the first build",{"value":4413,"label":4414},"0 files","kept on disk: the document lives only in memory, only counters are written",{"title":4204,"description":28},[293,4337,294,4348,1366,120],"en\u002Fkeysy\u002Fstrahovye-pdf","for accident commissioners: contacts on the form, data and QR code untouched","A Telegram bot stamps an accident commissioner's contacts onto an electronic OSAGO policy in seconds, leaving data, the QR code and the signature block untouched. Four insurers' forms. Python, PyMuPDF, aiogram 3.","EuymbZ4f3VvoUNHm8i-tSuMWVuET9k1VhbIzSaxS6zA",{"id":4422,"title":4423,"body":4424,"client":28,"cover":28,"date":28,"demoUrl":28,"description":28,"direction":1207,"emoji":4508,"extension":33,"faq":4509,"featured":94,"links":4525,"meta":4526,"navigation":79,"nda":79,"order":438,"own":94,"path":4527,"related":4528,"results":4529,"seo":4538,"shot":28,"shotAlt":28,"stack":4539,"status":4540,"stem":4541,"subtitle":4542,"summary":4543,"__hash__":4544},"cases_en\u002Fen\u002Fkeysy\u002Fwb-monitoring.md","Telegram bot for Wildberries delivery slots",{"type":18,"value":4425,"toc":4498},[4426,4428,4434,4436,4443,4451,4453,4457,4460,4462,4473,4475,4493,4495],[1426,4427,1429],{"id":1428},[21,4429,3563,4430,4433],{},[1450,4431,4432],{},"delivery slot"," is a booked time at which a Wildberries warehouse accepts a shipment. During holidays slots are snapped up within minutes: by the time a manager notices an opening and logs into the seller dashboard, it is already gone. Stock sits at the supplier's warehouse, money is frozen and sales are lost. Watching the dashboard by hand around the clock is not realistic.",[1426,4435,1439],{"id":1438},[21,4437,4438,4439,4442],{},"We built a Telegram bot that polls the Wildberries API every 30 seconds and instantly sends a notification when a free slot appears. An ",[1450,4440,4441],{},"API"," is an interface through which a program reads a service's data without opening the dashboard.",[1444,4444,4445,4448],{},[1447,4446,4447],{},"Filtering by warehouse, date and acceptance ratio, so only relevant alerts arrive.",[1447,4449,4450],{},"A direct booking link in the notification: from alert to booked slot takes about 10 seconds.",[1426,4452,1517],{"id":1516},[1519,4454,4456],{"id":4455},"polling-the-wildberries-api-every-30-seconds","Polling the Wildberries API every 30 seconds",[21,4458,4459],{},"The bot runs continuously on a server. Every 30 seconds it asks the Wildberries API for slot data, compares it with the filter settings and, if a suitable slot is found, sends a Telegram message with a booking link. Nobody has to keep the dashboard open.",[1426,4461,1578],{"id":1577},[1444,4463,4464,4467,4470],{},[1447,4465,4466],{},"A 30-second polling interval, faster than manual checking.",[1447,4468,4469],{},"24\u002F7 monitoring with no days off or night shifts.",[1447,4471,4472],{},"Stock reaches the warehouse on time, so sales do not stall.",[1426,4474,1602],{"id":1601},[1444,4476,4477,4482,4487],{},[1447,4478,4479,4481],{},[1450,4480,293],{}," — the bot's main language.",[1447,4483,4484,4486],{},[1450,4485,470],{}," — the source of slot data.",[1447,4488,4489,4492],{},[1450,4490,4491],{},"Telegram API"," — instant notifications in a messenger.",[1426,4494,1647],{"id":1646},[21,4496,4497],{},"Completed. Under a non-disclosure agreement, the company name is not disclosed. Other details about the client are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":4499},[4500,4501,4502,4505,4506,4507],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":4503},[4504],{"id":4455,"depth":1661,"text":4456},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"📦",[4510,4513,4516,4519,4522],{"q":4511,"a":4512},"How does the bot find out about free Wildberries slots?","It polls the Wildberries API every 30 seconds and sends a Telegram notification as soon as a free slot appears.",{"q":4514,"a":4515},"Can I receive only the slot alerts I need?","Yes. Filtering is set up by warehouse, date and acceptance ratio, so there is no noise.",{"q":4517,"a":4518},"Does the bot book the delivery slot by itself?","Each notification includes a direct booking link, and going from alert to booked slot takes about 10 seconds. The decision stays with a person.",{"q":4520,"a":4521},"Does slot monitoring run around the clock?","Yes, the bot runs 24\u002F7 with no days off or night shifts.",{"q":4523,"a":4524},"Why does a supplier need to watch delivery slots?","During holidays slots are taken within minutes. A fast alert helps book in time so stock does not sit idle at the supplier's warehouse.",[],{},"\u002Fen\u002Fkeysy\u002Fwb-monitoring",[],[4530,4533,4535],{"value":4531,"label":4532},"30 sec","polling interval, faster than any manager",{"value":1403,"label":4534},"monitoring, no days off or night shifts",{"value":4536,"label":4537},"↑ Turnover","stock ships on time, so sales don't stall",{"title":4423,"description":28},[293,470,4491],"done","en\u002Fkeysy\u002Fwb-monitoring","for a Wildberries supplier: shipment slots without manual checking","A Telegram bot for a Wildberries supplier: it polls the marketplace API every 30 seconds and instantly reports free delivery slots at chosen warehouses, so the supplier can book before they are taken.","utwbzBUip0R-vG8QpslkJGpkvvIRkc4qvMzzjE2E-D0",{"id":4546,"title":4547,"body":4548,"client":28,"cover":28,"date":28,"demoUrl":28,"description":28,"direction":1207,"emoji":4692,"extension":33,"faq":4693,"featured":94,"links":4712,"meta":4713,"navigation":79,"nda":79,"order":4714,"own":94,"path":4715,"related":4716,"results":4717,"seo":4727,"shot":28,"shotAlt":28,"stack":4728,"status":1713,"stem":4729,"subtitle":4730,"summary":4731,"__hash__":4732},"cases_en\u002Fen\u002Fkeysy\u002Fn8n-crm.md","Lead automation on n8n: Telegram, CRM and Google Sheets",{"type":18,"value":4549,"toc":4679},[4550,4552,4555,4561,4563,4569,4595,4597,4601,4604,4608,4611,4615,4621,4625,4628,4630,4644,4646,4674,4676],[1426,4551,1429],{"id":1428},[21,4553,4554],{},"A small business took leads through a Telegram bot. After that, each lead was copied by hand: from the messenger into the CRM and into a spreadsheet. Manual copying took time, and leads got lost or duplicated. A manager found out about a deal status change only by opening the system.",[21,4556,3563,4557,4560],{},[1450,4558,4559],{},"CRM"," is a system that tracks customers and deals; here it is Bitrix24. The bot, the CRM and Google Sheets had to be connected so that data moved between them without a person in the middle.",[1426,4562,1439],{"id":1438},[21,4564,4565,4566,4568],{},"An automation hub on ",[1450,4567,468],{},", a workflow automation tool: chains of steps are assembled from ready-made blocks and run when an event happens. Four groups of workflows were built:",[1444,4570,4571,4577,4583,4589],{},[1447,4572,4573,4576],{},[1450,4574,4575],{},"Lead capture."," A message from the Telegram bot reaches n8n, is split into fields and is created in the CRM.",[1447,4578,4579,4582],{},[1450,4580,4581],{},"Sync."," The same lead and its status are written to Google Sheets, for people who prefer a spreadsheet to a CRM.",[1447,4584,4585,4588],{},[1450,4586,4587],{},"Notifications."," When a deal changes status in Bitrix24, the responsible manager gets a Telegram message.",[1447,4590,4591,4594],{},[1450,4592,4593],{},"Payment webhooks."," An event from the payment system arrives in n8n and is recorded without manual reconciliation.",[1426,4596,1517],{"id":1516},[1519,4598,4600],{"id":4599},"telegram-trigger-receiving-a-message-from-the-bot","Telegram Trigger: receiving a message from the bot",[21,4602,4603],{},"The first node of the workflow is subscribed to the \"message\" event. As soon as a customer writes to the bot, n8n starts the chain. Every run is visible in the execution log, so checking whether a lead arrived does not require reading code.",[1519,4605,4607],{"id":4606},"appending-a-row-to-google-sheets","Appending a row to Google Sheets",[21,4609,4610],{},"The Google Sheets node appends a new row to the end of the tracking sheet. Leads accumulate in a familiar format, and the spreadsheet remains a fallback record if the CRM is unavailable.",[1519,4612,4614],{"id":4613},"bitrix24-and-payment-webhooks","Bitrix24 and payment webhooks",[21,4616,3563,4617,4620],{},[1450,4618,4619],{},"webhook"," is an address that one system calls on its own when an event happens in it. The bot, Bitrix24, the payment system and Google Sheets are not connected to each other directly: each one talks only to n8n. It receives the event, reshapes the data and passes it on. Replacing one system therefore does not break the others.",[1519,4622,4624],{"id":4623},"workflows-as-files","Workflows as files",[21,4626,4627],{},"An n8n workflow is exported as a JSON file. It is imported on another server, the Telegram and Google credentials are assigned, and the workflow is activated. Keys and tokens live in n8n itself, not in the workflow file.",[1426,4629,1578],{"id":1577},[1444,4631,4632,4635,4638,4641],{},[1447,4633,4634],{},"A bot lead lands in the CRM and the spreadsheet by itself, with no manual copying.",[1447,4636,4637],{},"The manager is notified when a deal changes status.",[1447,4639,4640],{},"Payment events are handled by the same automation hub.",[1447,4642,4643],{},"n8n runs on the company's own server: no plan limits and no dependence on a third-party cloud.",[1426,4645,1602],{"id":1601},[1444,4647,4648,4653,4658,4664,4669],{},[1447,4649,4650,4652],{},[1450,4651,468],{}," — workflow orchestration: connects the bot, the CRM, the spreadsheet and the payment system.",[1447,4654,4655,4657],{},[1450,4656,4491],{}," — receives customer leads and notifies managers.",[1447,4659,4660,4663],{},[1450,4661,4662],{},"Bitrix24"," — the client's CRM: deals and their statuses.",[1447,4665,4666,4668],{},[1450,4667,483],{}," — the tracking sheet for people who work in it.",[1447,4670,4671,4673],{},[1450,4672,469],{}," — instant delivery of events between systems.",[1426,4675,1647],{"id":1646},[21,4677,4678],{},"In production. The workflows run in the client's n8n; the basic lead-capture workflow is kept in the repository as an export file. Other client details are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":4680},[4681,4682,4683,4689,4690,4691],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":4684},[4685,4686,4687,4688],{"id":4599,"depth":1661,"text":4600},{"id":4606,"depth":1661,"text":4607},{"id":4613,"depth":1661,"text":4614},{"id":4623,"depth":1661,"text":4624},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"🔗",[4694,4697,4700,4703,4706,4709],{"q":4695,"a":4696},"How does a lead from a Telegram bot get into the CRM?","The bot passes the message to n8n. The workflow splits it into fields, creates a record in the CRM and appends a row to Google Sheets. Nobody copies the lead by hand.",{"q":4698,"a":4699},"What is n8n and why would a small business use it?","n8n is a workflow automation tool: a chain of steps, called nodes, is assembled from ready-made blocks and runs when an event happens. It connects a messenger, a CRM and a spreadsheet without custom software or manual work.",{"q":4701,"a":4702},"How does the manager learn that a deal changed status?","When the status changes in Bitrix24, a webhook fires: the CRM calls the n8n address on its own. The workflow finds the responsible manager and sends them a Telegram message.",{"q":4704,"a":4705},"Where does n8n run and where is the data stored?","n8n is self-hosted on the company's own server rather than on a cloud plan. Workflows are stored as JSON export files, so they can be moved between servers and kept in git.",{"q":4707,"a":4708},"Is there a limit on the number of operations?","With a self-hosted deployment there is no plan limit on executions: the only ceiling is the server itself.",{"q":4710,"a":4711},"Can other steps or systems be added?","Yes. An n8n workflow is made of separate nodes, so the chain is extended without a rewrite: another spreadsheet, another notification, or any system that has an API or webhooks.",[],{},35,"\u002Fen\u002Fkeysy\u002Fn8n-crm",[],[4718,4721,4724],{"value":4719,"label":4720},"0 manual copying","a bot lead lands in the CRM and the spreadsheet by itself",{"value":4722,"label":4723},"By webhook","the manager is notified when a deal changes status",{"value":4725,"label":4726},"Self-hosted","n8n on your own server: no plan limits and no third-party cloud",{"title":4547,"description":28},[468,4491,483,4662,469],"en\u002Fkeysy\u002Fn8n-crm","for a small business: a bot lead lands in Bitrix24 and a spreadsheet by itself","Lead automation for a small business on n8n: a message from a Telegram bot reaches Bitrix24 and Google Sheets through webhooks with no manual copying, the manager is notified when a deal changes status, and payment webhooks are handled in the same place.","lb_EUUYNV3T-Xm5nX_igdl0DKnGu-gDnzmzVqFTd9VM",{"id":4734,"title":4735,"body":4736,"client":28,"cover":28,"date":3439,"demoUrl":28,"description":28,"direction":1207,"emoji":4942,"extension":33,"faq":4943,"featured":94,"links":4962,"meta":4963,"navigation":79,"nda":79,"order":610,"own":94,"path":4964,"related":4965,"results":4966,"seo":4976,"shot":28,"shotAlt":28,"stack":4977,"status":4540,"stem":4978,"subtitle":4979,"summary":4980,"__hash__":4981},"cases_en\u002Fen\u002Fkeysy\u002Fbox-calc.md","FEFCO box die-line calculation in C++",{"type":18,"value":4737,"toc":4929},[4738,4740,4743,4761,4764,4766,4777,4812,4814,4818,4829,4833,4836,4840,4846,4850,4876,4878,4895,4897,4921,4923,4926],[1426,4739,1429],{"id":1428},[21,4741,4742],{},"A cardboard packaging manufacturer receives an order as the inner dimensions of a box: length, width, height. To cut the blank, it needs the size of the flat die-line, taking into account the board thickness, the glue flap and the fold allowances. To estimate material use, it needs to know how many such blanks fit on one sheet of corrugated board.",[21,4744,3563,4745,4748,4749,4752,4753,4756,4757,4760],{},[1450,4746,4747],{},"die-line (blank)"," is the flat sheet that is cut and creased and then folded into a box. ",[1450,4750,4751],{},"Creasing"," is a pressed fold line. ",[1450,4754,4755],{},"Corrugated board"," is multi-layer cardboard with a fluted layer inside. ",[1450,4758,4759],{},"FEFCO"," is the European catalogue of standard box designs, where every style has a code: 0201, 0203 and so on.",[21,4762,4763],{},"Spreadsheet calculations produced errors: board thickness is easy to forget, every style has its own formula, and sheet layout was estimated by eye. A mistake showed up only at cutting. The manufacturer needed a core that calculates by the catalogue rules and can be embedded into its production software.",[1426,4765,1439],{"id":1438},[21,4767,4768,4769,4772,4773,4776],{},"A C++17 library called ",[1635,4770,4771],{},"packaging"," and a command-line calculator, ",[1635,4774,4775],{},"packaging_calc",", built on top of it. What is implemented:",[1444,4778,4779,4794,4797,4800,4803,4806,4809],{},[1447,4780,4781,4782,4785,4786,4789,4790,4793],{},"Three FEFCO box styles: ",[1450,4783,4784],{},"0200"," (flaps on top only), ",[1450,4787,4788],{},"0201"," (standard slotted box, flaps meeting with a gap), ",[1450,4791,4792],{},"0203"," (full-overlap flaps).",[1447,4795,4796],{},"Blank dimensions: length as 2L + 2W + glue flap + creasing allowances, width as height + flaps + allowances, and flap height.",[1447,4798,4799],{},"Outer dimensions and two volumes: internal and overall.",[1447,4801,4802],{},"Nesting of blanks on a board sheet, with a 90° rotation and the share of area used.",[1447,4804,4805],{},"Input validation: positive dimensions, board thickness up to 20 mm, a valid flap gap. An error produces a message and return code 1.",[1447,4807,4808],{},"Tests for the formulas, nesting, validation and style-code parsing, wired into CTest.",[1447,4810,4811],{},"No external dependencies: only a C++17 compiler and CMake 3.14 or newer. It builds on Linux, macOS and Windows.",[1426,4813,1517],{"id":1516},[1519,4815,4817],{"id":4816},"fefco-0201-die-line-the-blank-formula","FEFCO 0201 die-line: the blank formula",[21,4819,4820,4821,4824,4825,4828],{},"The program fills in a ",[1635,4822,4823],{},"BoxSpec"," structure (inner dimensions, thickness, glue flap, gap) and calls ",[1635,4826,4827],{},"calculate()"," with a style code. The result is the blank: length, width, flap height and area. For 0201 the blank width is calculated from the gap between flaps, not from a fixed box width as in the prototype. Reference example from the README: a 300×200×150 mm box, 3 mm board, style 0201 gives a blank of 1047.0×356.0 mm, a flap of 98.5 mm and an area of 372,732 mm².",[1519,4830,4832],{"id":4831},"creasing-allowance-and-board-thickness","Creasing allowance and board thickness",[21,4834,4835],{},"Board thickness enters the calculation twice: as an allowance for each fold line and as an addition to the outer dimensions. The same box measures 306×206×156 mm on the outside, with an internal volume of 9.00 L; the overall volume is calculated separately from the outer dimensions. The creasing allowance is set equal to the board thickness. This is an approximation and may differ at a specific manufacturer, so the formulas need to be checked against the plant's tooling.",[1519,4837,4839],{"id":4838},"nesting-blanks-on-a-corrugated-sheet","Nesting blanks on a corrugated sheet",[21,4841,4301,4842,4845],{},[1635,4843,4844],{},"nest_on_sheet()"," function takes the blank and the sheet size, tries a grid layout in the original orientation and rotated by 90°, and returns the number of blanks and the share of sheet area used. On a 2000×1200 mm sheet the reference blank fits 5 times when rotated, with about 77.7% of the area used. The layout is a simple grid, and knife width is not yet taken into account.",[1519,4847,4849],{"id":4848},"library-and-command-line-calculator","Library and command-line calculator",[21,4851,4852,4853,4856,4857,4859,4860,4862,4863,4865,4866,4868,4869,4872,4873,1638],{},"The calculation plugs into another project as an ordinary CMake library: include the ",[1635,4854,4855],{},"packaging\u002Fbox.hpp"," header, fill in ",[1635,4858,4823],{},", call ",[1635,4861,4827],{}," and, if needed, ",[1635,4864,4844],{},". For manual checks there is the ",[1635,4867,4775],{}," utility with parameters for style, dimensions, thickness, glue flap, gap and sheet; help is available with ",[1635,4870,4871],{},"--help",". Tests are run with ",[1635,4874,4875],{},"ctest",[1426,4877,1578],{"id":1577},[1444,4879,4880,4883,4886,4889,4892],{},[1447,4881,4882],{},"Three FEFCO styles are calculated from one construction description, not from separate tables.",[1447,4884,4885],{},"The blank, outer dimensions and volumes for the reference box match the expected values from the README; verified by building the project and running the tests.",[1447,4887,4888],{},"Nesting returns the number of blanks and the share of sheet area used: 5 pieces and about 77.7% for a 2000×1200 mm sheet.",[1447,4890,4891],{},"Invalid input is rejected with a clear message instead of producing a wrong drawing.",[1447,4893,4894],{},"With no external dependencies, the library embeds into production software.",[1426,4896,1602],{"id":1601},[1444,4898,4899,4904,4910,4916],{},[1447,4900,4901,4903],{},[1450,4902,486],{}," — fast computation, and the code can be embedded into line software or a standalone service.",[1447,4905,4906,4909],{},[1450,4907,4908],{},"CMake"," — one command builds the library, the utility and the tests on any platform.",[1447,4911,4912,4915],{},[1450,4913,4914],{},"CTest"," — runs the tests for formulas, nesting and validation.",[1447,4917,4918,4920],{},[1450,4919,4759],{}," — a shared language for describing box designs between the customer and the program.",[1426,4922,1647],{"id":1646},[21,4924,4925],{},"Version 0.2.0, released on 30 September 2026. The project has grown from a single-file prototype (0.1.0, February 2026) into a library, a utility and a test suite; board thickness now actually takes part in the calculation, and the 0201 blank width is calculated from the flap gap.",[21,4927,4928],{},"Next on the README roadmap: other FEFCO types, including 0427 and self-assembly boxes; corrugated board profiles and flute direction in nesting; knife width; exporting the die-line to DXF or SVG; cost calculation. Other details about the client are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":4930},[4931,4932,4933,4939,4940,4941],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":4934},[4935,4936,4937,4938],{"id":4816,"depth":1661,"text":4817},{"id":4831,"depth":1661,"text":4832},{"id":4838,"depth":1661,"text":4839},{"id":4848,"depth":1661,"text":4849},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"📐",[4944,4947,4950,4953,4956,4959],{"q":4945,"a":4946},"How do I calculate the blank size from the inner dimensions of a box?","You pass the length, width, height, board thickness, glue flap and flap gap. The library returns the blank length and width, flap height, area, outer dimensions and volumes. For a 300×200×150 mm box in 3 mm board, style FEFCO 0201, the blank comes out at 1047×356 mm.",{"q":4948,"a":4949},"Which FEFCO styles are supported?","Three catalogue styles: 0200 (flaps on top only), 0201 (standard slotted box, flaps meeting with a gap) and 0203 (full-overlap flaps). Other types, including 0427 and self-assembly boxes, are not implemented yet and are on the roadmap.",{"q":4951,"a":4952},"How many boxes fit on one sheet of corrugated board?","The nesting function takes the blank and the sheet size, tries placement in both orientations including a 90° rotation, and returns the number of blanks and the share of sheet area used. On a 2000×1200 mm sheet, the 0201 blank for a 300×200×150 box fits 5 times with about 77.7% of the sheet used.",{"q":4954,"a":4955},"How is board thickness taken into account?","Thickness goes into the creasing allowances and into the outer dimensions: a 300×200×150 box in 3 mm board measures 306×206×156 mm on the outside. The creasing allowance is set equal to the board thickness. This is an approximation and should be checked against the tooling of a specific plant.",{"q":4957,"a":4958},"Does the calculation need a server or spreadsheets?","No. It is a plain C++17 library with no external dependencies, built with CMake and linked directly into production software. For manual calculations there is a command-line calculator, packaging_calc, with the same parameters.",{"q":4960,"a":4961},"How can I check that the calculation is correct?","The repository has tests for the formulas, nesting, input validation and style parsing, run through CTest. Reference values for the 300×200×150 mm box are given in the README and were checked by running the code.",[],{},"\u002Fen\u002Fkeysy\u002Fbox-calc",[],[4967,4970,4973],{"value":4968,"label":4969},"3 FEFCO styles","0200, 0201 and 0203 are calculated from one data structure, not separate tables",{"value":4971,"label":4972},"77.7%","of a 2000×1200 mm sheet used: 5 rotated blanks for a 300×200×150 box",{"value":4974,"label":4975},"0 dependencies","only a C++17 compiler and CMake, so the library embeds into production software",{"title":4735,"description":28},[486,4908,4914,4759],"en\u002Fkeysy\u002Fbox-calc","a library and calculator for a cardboard packaging manufacturer","A C++17 library for a cardboard packaging manufacturer: from inner box dimensions and board thickness it calculates the blank size for FEFCO 0200, 0201 and 0203, volumes and blank nesting on a corrugated sheet.","OMUlybgK2vcaAL7BW4sgAUTkltY71THKB6yHLQKsgMk",{"id":4983,"title":4984,"body":4985,"client":5184,"cover":28,"date":1672,"demoUrl":5185,"description":28,"direction":1180,"emoji":5186,"extension":33,"faq":5187,"featured":94,"links":5206,"meta":5207,"navigation":79,"nda":94,"order":610,"own":94,"path":5208,"related":5209,"results":5210,"seo":5220,"shot":5221,"shotAlt":5222,"stack":5223,"status":1713,"stem":5224,"subtitle":5225,"summary":5226,"__hash__":5227},"cases_en\u002Fen\u002Fkeysy\u002Fdyhanie-povolzhya.md","WordPress Showcase Site with Ordering via VKontakte",{"type":18,"value":4986,"toc":5169},[4987,4989,4992,4995,5009,5011,5018,5020,5043,5045,5049,5052,5056,5059,5063,5066,5070,5073,5077,5080,5084,5087,5089,5109,5111,5158,5160],[1426,4988,1429],{"id":1428},[21,4990,4991],{},"The client, a brand of natural essential oils and hydrolates called «Дыхание Поволжья» (Breath of the Volga Region), runs billboard ads in Moscow. A passer-by follows the link from the billboard, almost always on a phone, browses the catalogue and goes to buy. Payment on the site is not needed: by agreement with the client, the whole purchase happens in a VKontakte community, and the site has to bring the buyer there with the product already chosen.",[21,4993,4994],{},"The requirements were simple:",[1444,4996,4997,5000,5003,5006],{},[1447,4998,4999],{},"instant loading on mobile, with no heavy page builders and no external fonts;",[1447,5001,5002],{},"styling that follows the client's brand book: colours, the Bebas Neue and Geologica fonts, a vector logo from the designer's files;",[1447,5004,5005],{},"an admin panel where a client with no WordPress experience changes products, prices, availability and almost all site texts;",[1447,5007,5008],{},"Russian and English versions, light and dark themes.",[1426,5010,1439],{"id":1438},[21,5012,5013,5014,5017],{},"A showcase site on WordPress with a custom theme, no third-party plugins or builders, in plain CSS and JS. A ",[1450,5015,5016],{},"showcase site"," presents products and leads to an order, but does not take payment itself. Next to it runs a VKontakte community bot in Python that receives the order from the site and hands it to a manager.",[21,5019,2292],{},[1444,5021,5022,5025,5028,5031,5034,5037,5040],{},[1447,5023,5024],{},"A custom «Products» post type: several volumes per product, each with its own retail and wholesale price, availability («In stock», «On order», «Out of stock»), a label on the photo, a gallery, a full description (composition, use, storage) and an English version.",[1447,5026,5027],{},"A cart on the buyer's device: a slide-out panel, quantity, line total and grand total, and the wholesale price switching on by itself at the right quantity. A volume that is out of stock cannot be added to the cart.",[1447,5029,5030],{},"Two routes to an order: a «Buy on VKontakte» button on a volume that links to the community product, and «Order on VK» from the cart, which passes the order contents to the bot.",[1447,5032,5033],{},"A «Site» panel in the admin: an overview with launch readiness, «Texts and blocks» with a live preview, «Contacts and legal details» with tax-ID and registration-number checks, «SEO and counters», «Documents» with version history, and «Prices and stock» in one table.",[1447,5035,5036],{},"Light and dark themes following the device setting with a manual switch, and RU\u002FEN with language detected from the browser and a switcher in the header.",[1447,5038,5039],{},"A personal-data policy and a cookie notice from templates with the legal details filled in automatically; Yandex Metrica and Google Analytics load only after consent.",[1447,5041,5042],{},"Protection: XML-RPC closed at the entry nginx, the login form limited by attempts, the administrator login hidden from outside, comments off, a daily snapshot of the database and photos.",[1426,5044,1517],{"id":1516},[1519,5046,5048],{"id":5047},"wordpress-a-custom-products-post-type","WordPress: a custom «Products» post type",[21,5050,5051],{},"Standard WordPress posts do not suit products with several volumes, so the theme declares its own post type. Volumes, prices, wholesale and availability are stored for each product as a separate set of fields and edited in the product card above the description. Product categories have their own pages, a card has a block of similar products, and the catalogue has pagination.",[1519,5053,5055],{"id":5054},"a-cart-in-localstorage","A cart in localStorage",[21,5057,5058],{},"The cart lives in the buyer's browser, in localStorage, and needs neither an account nor a server session. localStorage is storage that the browser keeps on the device between visits. The cart script calculates the total, switches wholesale on when the threshold is reached and refuses a volume that is out of stock.",[1519,5060,5062],{"id":5061},"ordering-through-vkontakte-messages","Ordering through VKontakte messages",[21,5064,5065],{},"Adding a product to a VKontakte cart from a third-party site is not possible; this is a limit of the platform itself. So the «Buy on VKontakte» button opens checkout in the community with the right product, while the site cart sends the order contents to its own API, receives a one-time code and opens the community chat with a link that carries the code. The code lives for three hours. For people who have already chatted with the community, VKontakte does not pass the link parameter, so the code is put on the clipboard and the bot understands it as text. If the site does not answer within three seconds or the bot is off, the cart falls back to copying the order text.",[1519,5067,5069],{"id":5068},"a-vkontakte-community-bot-on-aiohttp","A VKontakte community bot on aiohttp",[21,5071,5072],{},"The bot is written in Python with aiohttp and works through Long Poll, keeping dialogue state in SQLite. It reads the catalogue from the site: the site has an API that returns products, volumes, prices, wholesale thresholds and availability, cached for a minute. The bot takes an order by its code from the site exactly once, and the contents are checked against the site's database, not against what the browser sent. Wholesale, out-of-stock items and unfamiliar questions go to a manager. The administrator has version and statistics commands, a staff list with roles and a spam filter.",[1519,5074,5076],{"id":5075},"a-custom-theme-from-the-brand-book","A custom theme from the brand book",[21,5078,5079],{},"Colours and fonts come from the brand book, the font files are stored in the theme, and there are no requests to Google Fonts. The logo is a vector in several variants, with plants from the roll-up banner and a pattern from the mark on the first screen. At the client's request a conifer-green accent for buttons and badges was added on top of the brand book. The site icons are built from the mark, including variants for phones and a manifest.json.",[1519,5081,5083],{"id":5082},"an-admin-in-the-clients-own-language","An admin in the client's own language",[21,5085,5086],{},"The «Site» section opens right after login and shows a launch-readiness checklist. In «Texts and blocks» any label of the home page, header, footer, catalogue, cart and cookie notice can be edited; the text changes in the preview as you type, the view switches between desktop and phone, and every field has an «Original text» option. English texts are edited in the same place with «no translation» and «check translation» marks. The Word guide for the client is built by a script: it captures screenshots of the site and the admin in a browser, draws callouts and assembles a 29-page document with 17 screenshots.",[1426,5088,1578],{"id":1577},[1444,5090,5091,5094,5097,5100,5103,5106],{},[1447,5092,5093],{},"The site has been live on its production domain, written in Cyrillic, since 21 September 2026 with indexing open; the domains with Latin letters and with a typo lead to the main one by a permanent redirect.",[1447,5095,5096],{},"The client changes prices, availability, photos and texts on their own, with no developer needed.",[1447,5098,5099],{},"A buyer from an ad lands on a lightweight mobile catalogue and moves to the community chat with the order ready.",[1447,5101,5102],{},"The bot and the site work from one catalogue: there is no second product list and no price mismatch.",[1447,5104,5105],{},"The site opens in the visitor's device theme and language, Russian or English.",[1447,5107,5108],{},"A daily snapshot of the database and uploaded photos is kept for two weeks, and HTTPS renews automatically.",[1426,5110,1602],{"id":1601},[1444,5112,5113,5118,5124,5130,5136,5142,5147,5152],{},[1447,5114,5115,5117],{},[1450,5116,118],{}," with a custom theme — the client gets a familiar admin and editor, while the theme does not depend on third-party plugins or builders.",[1447,5119,5120,5123],{},[1450,5121,5122],{},"PHP 8.2"," — the language of the theme: post types, the «Site» panel, the API for the bot, structured markup.",[1447,5125,5126,5129],{},[1450,5127,5128],{},"MariaDB 10.11"," — the WordPress database: products, texts, settings.",[1447,5131,5132,5135],{},[1450,5133,5134],{},"Plain CSS and JS"," — the showcase, both themes, the cart and the gallery without frameworks, so the page is light on a phone.",[1447,5137,5138,5141],{},[1450,5139,5140],{},"Python and aiohttp"," — the VKontakte community bot: Long Poll, a button-driven dialogue, reading the catalogue from the site.",[1447,5143,5144,5146],{},[1450,5145,1366],{}," — the bot's dialogue state, with no separate database server.",[1447,5148,5149,5151],{},[1450,5150,650],{}," — the same environment locally and on the server, with the bot in its own container.",[1447,5153,5154,5157],{},[1450,5155,5156],{},"Let's Encrypt"," — HTTPS with automatic renewal on the studio's web server.",[1426,5159,1647],{"id":1646},[21,5161,5162,5163,5168],{},"Site version 0.9.9 of 22 September 2026, community bot version 1.2.1. The site is open at ",[4129,5164,5167],{"href":5165,"rel":5166},"https:\u002F\u002F%D0%B4%D1%8B%D1%85%D0%B0%D0%BD%D0%B8%D0%B5%D0%BF%D0%BE%D0%B2%D0%BE%D0%BB%D0%B6%D1%8C%D1%8F.%D1%80%D1%84",[4133],"дыханиеповолжья.рф",", indexing is on, and the Yandex Metrica counter is connected with a goal on the «Order on VK» click. The catalogue currently holds two items and is waiting for photos and prices from the client; the regional part for Moscow and Chuvashia will appear once the delivery terms are known. The project has no automated tests, and acceptance follows the criteria in the documentation.",{"title":28,"searchDepth":29,"depth":29,"links":5170},[5171,5172,5173,5181,5182,5183],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":5174},[5175,5176,5177,5178,5179,5180],{"id":5047,"depth":1661,"text":5048},{"id":5054,"depth":1661,"text":5055},{"id":5061,"depth":1661,"text":5062},{"id":5068,"depth":1661,"text":5069},{"id":5075,"depth":1661,"text":5076},{"id":5082,"depth":1661,"text":5083},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"Дыхание Поволжья","https:\u002F\u002Fдыханиеповолжья.рф","🌿",[5188,5191,5194,5197,5200,5203],{"q":5189,"a":5190},"Can you build an online store without online payment or delivery?","Yes. This site takes no payment on purpose: the cart assembles the order and the deal is closed by messaging in a VKontakte community. The cart passes the order contents to the community bot with a one-time code, so the buyer lands in the chat with the order already prepared. If the site does not respond or the bot is off, a fallback copies the order text instead.",{"q":5192,"a":5193},"Can the client change prices, stock and texts without a developer?","Yes. The «Products → Prices and stock» section shows all products in one table: prices, wholesale, availability, drag-and-drop order and a percentage change for all prices at once. Texts and photos of the blocks are edited in «Texts and blocks» with a live preview in desktop and phone views. The admin has a built-in guide with 11 sections, plus a separate 29-page guide in Word.",{"q":5195,"a":5196},"How does wholesale pricing work on the site?","Each product volume has its own retail price, wholesale price and wholesale threshold. The wholesale price switches on in the cart by itself once the required quantity is reached. The community bot takes the same thresholds from the website, so there is no second product list.",{"q":5198,"a":5199},"Why does the site need a VKontakte community bot?","The bot walks the buyer through the catalogue with buttons: product, volume, quantity, checkout. It reads prices and stock from the website through its API, passes wholesale and out-of-stock items to a manager, and answers common questions. That way an order from an ad is not lost between the site and the chat.",{"q":5201,"a":5202},"How is the site set up for SEO?","Pages and products have their own titles and descriptions, Schema.org markup in JSON-LD (Organization, WebSite, BreadcrumbList, Product with price and availability, ItemList), breadcrumbs, a sitemap, robots.txt, Latin URLs instead of Cyrillic ones, hreflang and a separate canonical URL for the English version. Analytics load only after cookie consent.",{"q":5204,"a":5205},"Where is the site hosted and what about backups?","The site runs in Docker on the studio's web server, with HTTPS and an automatically renewed Let's Encrypt certificate. A daily copy of the database and uploaded photos is kept for two weeks. XML-RPC is closed, the login form limits the number of attempts, and the administrator login is not visible from outside.",[],{},"\u002Fen\u002Fkeysy\u002Fdyhanie-povolzhya",[],[5211,5214,5217],{"value":5212,"label":5213},"2 languages","Russian and English, a switcher in the header, language detected from the browser",{"value":5215,"label":5216},"1 catalogue","for the site and the VKontakte bot: prices and stock are edited in one place",{"value":5218,"label":5219},"14 days","retention of the daily copy of the database and uploaded photos",{"title":4984,"description":28},"\u002Fshots\u002Fdevunit-lab-dyhanie-povolzhya.jpg","“Dykhanie Povolzhya” WordPress showcase by DevUnit Lab: the home page and the catalogue with prices on a phone",[118,5122,297,293,3711,120],"en\u002Fkeysy\u002Fdyhanie-povolzhya","for the essential-oils brand «Дыхание Поволжья», built for billboard mobile traffic","A lightweight WordPress showcase site for an essential-oils brand: catalogue with volumes and wholesale pricing, a cart stored on the buyer's device, orders via VKontakte messages and a community bot, RU\u002FEN, a client-run admin.","Cq_H86kPfZcAZGn8_y294ypo7BxW_4rIIrnJpruTBXw",{"id":5229,"title":5230,"body":5231,"client":28,"cover":28,"date":4380,"demoUrl":28,"description":28,"direction":1193,"emoji":5426,"extension":33,"faq":5427,"featured":94,"links":5446,"meta":5447,"navigation":79,"nda":94,"order":610,"own":79,"path":5448,"related":5449,"results":5450,"seo":5460,"shot":28,"shotAlt":28,"stack":5461,"status":1713,"stem":5462,"subtitle":5463,"summary":5464,"__hash__":5465},"cases_en\u002Fen\u002Fkeysy\u002Fgolos-bot.md","Golos Bot: Telegram voice messages transcribed to text",{"type":18,"value":5232,"toc":5412},[5233,5235,5238,5241,5243,5246,5308,5310,5314,5317,5321,5324,5328,5331,5335,5338,5342,5349,5351,5371,5373,5404,5406,5409],[1426,5234,1429],{"id":1428},[21,5236,5237],{},"Voice messages are convenient for the sender and inconvenient for the recipient. You cannot skim them, search them, quote them, or read them where sound is not an option: on the subway, in a meeting, at night next to a sleeping child. A five-minute voice message costs exactly five minutes of attention even when only two lines of it matter.",[21,5239,5240],{},"What was needed was a voice-to-text bot that works by simply forwarding a message. No sign-up on third-party sites, no uploading files in a browser, and honest behaviour: if there is no speech in the recording, the bot must say so rather than make something up.",[1426,5242,1439],{"id":1438},[21,5244,5245],{},"Golos Bot is an in-house Telegram bot for transcribing voice messages. Implemented in version 2.2.0:",[1444,5247,5248,5254,5260,5266,5272,5278,5284,5290,5296,5302],{},[1447,5249,5250,5253],{},[1450,5251,5252],{},"Any audio source."," Voice messages (ogg), video notes (the audio track is used), audio files in mp3, m4a, ogg and wav, video (a clip from a call, a lecture), and documents when audio was sent as a file.",[1447,5255,5256,5259],{},[1450,5257,5258],{},"Three modes."," Verbatim: every word as spoken, for agreements. Cleaned up: no fillers or repetition, split into paragraphs, for everyday use. Summary: key points and structure.",[1447,5261,5262,5265],{},[1450,5263,5264],{},"Redo differently."," The mode is switched with a button under the finished transcript; there is no need to resend the recording.",[1447,5267,5268,5271],{},[1450,5269,5270],{},"Reply language."," Russian, English or the original language.",[1447,5273,5274,5277],{},[1450,5275,5276],{},"As a file."," A long transcript is delivered as .txt instead of being cut into pieces.",[1447,5279,5280,5283],{},[1450,5281,5282],{},"Cache."," The same recording in the same mode comes back instantly, with no API cost.",[1447,5285,5286,5289],{},[1450,5287,5288],{},"Silence."," If there is no speech, the bot reports that instead of inventing text.",[1447,5291,5292,5295],{},[1450,5293,5294],{},"Gemini key pool."," Automatic switching when a quota is exhausted and automatic fallback to a backup model.",[1447,5297,5298,5301],{},[1450,5299,5300],{},"Limits."," Transcriptions per hour and maximum recording length; the hourly limit can be changed from the admin panel on the fly. A premium list without the hourly limit; admins are always unlimited.",[1447,5303,5304,5307],{},[1450,5305,5306],{},"Admin panel."," Statistics, analytics, keys, users, bans, broadcasts, settings, logs, maintenance. When a new user appears, admins get a card with the ID and username, once.",[1426,5309,1517],{"id":1516},[1519,5311,5313],{"id":5312},"gemini-audio-transcription-in-a-single-request","Gemini audio transcription in a single request",[21,5315,5316],{},"Google Gemini is a multimodal model: an AI model that understands sound directly, with no separate \"recognise speech, then process the text\" stage. The bot sends the audio together with a prompt (an instruction) that states the mode and language, and receives finished text. Fewer links in the chain means less loss of meaning. The model's reasoning depth and response timeout are configurable.",[1519,5318,5320],{"id":5319},"ffmpeg-checking-and-compressing-to-mono-mp3","ffmpeg: checking and compressing to mono MP3",[21,5322,5323],{},"Before anything goes to the model, ffprobe (a utility from the FFmpeg toolkit) reads the recording's duration and checks that it contains sound. If the file is over 20 MB, FFmpeg recompresses it to mono MP3 at 32 kbps: that is enough for speech recognition, and the size drops several times over. The pydub library is deliberately not used: it has not been updated since 2021 and imports the audioop module, which was removed from Python 3.13.",[1519,5325,5327],{"id":5326},"gemini-key-pool-and-quotas","Gemini key pool and quotas",[21,5329,5330],{},"The free Gemini quota is limited, so the bot keeps several keys. When a key hits its quota, it goes on cooldown (60 minutes by default) and the request goes through the next key. The user notices nothing; the admin gets a notification. The admin panel shows the state of each key and has a button to lift a cooldown manually. If the main model returns a 404 (renamed or withdrawn), the bot quietly switches to the backup model.",[1519,5332,5334],{"id":5333},"transcript-cache","Transcript cache",[21,5336,5337],{},"The result is stored in SQLite under a \"recording + mode\" key. Sending the same recording in the same mode again returns the cached text instantly. The cache lives for a limited time (30 days by default); the audio file itself is deleted right after processing. The database also holds users and their settings, a transcription log, key usage and the settings that can be changed from the admin panel.",[1519,5339,5341],{"id":5340},"administration-and-accounting","Administration and accounting",[21,5343,5344,5345,5348],{},"The admin panel opens with ",[1635,5346,5347],{},"\u002Fadmin"," and works on buttons under the message. Its header shows the bot version, so you can see what is actually deployed on the server. When a new user first appears, every admin receives a card: name, the ID as a separate block (copied with a tap), a link to the username and the total number of users in the database. The bot's logic is covered by unit checks and routing checks in which Telegram and Gemini are replaced with mocks, so no network is needed to run them.",[1426,5350,1578],{"id":1577},[1444,5352,5353,5356,5359,5362,5365,5368],{},[1447,5354,5355],{},"A voice message, video note, audio file or video becomes text: searchable, quotable and readable without sound.",[1447,5357,5358],{},"The mode can be changed after transcription without resending the recording.",[1447,5360,5361],{},"Repeats are served from the cache without calling the API.",[1447,5363,5364],{},"One key running out of quota does not stop the service: a key pool and a backup model.",[1447,5366,5367],{},"Audio is not stored; the transcript lives for a limited time.",[1447,5369,5370],{},"The bot runs in Docker with a healthcheck and log rotation; the version is written to the log at startup and sent to admins in a startup message.",[1426,5372,1602],{"id":1601},[1444,5374,5375,5381,5387,5393,5399],{},[1447,5376,5377,5380],{},[1450,5378,5379],{},"Python 3.14 and aiogram 3",": an asynchronous Telegram bot driven by inline buttons.",[1447,5382,5383,5386],{},[1450,5384,5385],{},"Google Gemini (google-genai)",": audio transcription in a single request; main and backup models are configurable.",[1447,5388,5389,5392],{},[1450,5390,5391],{},"FFmpeg and ffprobe",": duration, sound detection, compression of long recordings.",[1447,5394,5395,5398],{},[1450,5396,5397],{},"SQLite (aiosqlite)",": users, log, transcript cache, key usage, settings.",[1447,5400,5401,5403],{},[1450,5402,120],{},": python:3.14-slim image, non-root user, healthcheck, Moscow time zone.",[1426,5405,1647],{"id":1646},[21,5407,5408],{},"In-house product, in production. The current version is 2.2.0 from 20 September 2026: it added the \"Made in DevUnit Lab\" signature with a link to the site. Version 2.1.0 (15 August 2026) marked the move to a separate repository and the split of the single-file bot into a package of modules.",[21,5410,5411],{},"Limitations: a 20 MB download cap through the cloud Bot API, private chats only, no queue when every key is exhausted. Planned: a \"Copy text\" button, a summary as the first message, group support, quota monitoring, cost accounting, export to .docx and .srt.",{"title":28,"searchDepth":29,"depth":29,"links":5413},[5414,5415,5416,5423,5424,5425],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":5417},[5418,5419,5420,5421,5422],{"id":5312,"depth":1661,"text":5313},{"id":5319,"depth":1661,"text":5320},{"id":5326,"depth":1661,"text":5327},{"id":5333,"depth":1661,"text":5334},{"id":5340,"depth":1661,"text":5341},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"🎙",[5428,5431,5434,5437,5440,5443],{"q":5429,"a":5430},"How can I read a voice message without listening to it?","Forward the voice message to the bot. It replies with text in the mode you chose: verbatim, cleaned up (no fillers or repetition) or a summary. A long transcript arrives as a .txt file rather than in chunks.",{"q":5432,"a":5433},"Which recordings does the bot understand?","Voice messages, video notes (the audio track is used), audio files in mp3, m4a, ogg and wav, video, and documents when the audio was sent as a file. Reply language: Russian, English or the original language.",{"q":5435,"a":5436},"How does audio transcription with Gemini work?","The audio and an instruction (mode and language) go to the multimodal Google Gemini model in a single request. There is no separate speech-to-text step followed by text processing. Before sending, ffprobe checks the duration and whether there is sound, and FFmpeg recompresses files over 20 MB to mono MP3.",{"q":5438,"a":5439},"What happens when a Gemini key runs out of quota?","Keys work as a pool. An exhausted key goes on cooldown (60 minutes by default), the request goes through the next key, and the admin gets a notification. If the main model returns a 404, the bot switches to the backup model on its own.",{"q":5441,"a":5442},"Are my recordings stored?","The audio file is deleted right after processing. The transcript stays in a cache for a limited time, 30 days by default: the same recording in the same mode comes back instantly with no API cost.",{"q":5444,"a":5445},"Are there any limits?","Telegram's cloud Bot API does not hand bots files over 20 MB. Gemini safety filters may reject a recording on a sensitive topic, and noise or several people talking at once lowers quality. If there is no speech in the recording, the bot says so instead of inventing text.",[],{},"\u002Fen\u002Fkeysy\u002Fgolos-bot",[],[5451,5454,5457],{"value":5452,"label":5453},"3 modes","verbatim, cleaned up, summary; switched with a button after transcription",{"value":5455,"label":5456},"180 minutes","default maximum recording length, adjustable in settings",{"value":5458,"label":5459},"30 days","transcript cache lifetime; the audio file itself is deleted right after processing",{"title":5230,"description":28},[293,294,309,312,1366,120],"en\u002Fkeysy\u002Fgolos-bot","in-house Telegram bot: voice notes, video notes, audio and video to text via Gemini","A Telegram bot that turns voice messages into text: voice notes, video notes, audio and video are transcribed verbatim, cleaned up or as a summary. Google Gemini, a pool of API keys, FFmpeg, a transcript cache; audio is never stored.","_d90hlI4Kt0N4wrvGBzsUDw08VY_nTYl5nQUepP99Ig",{"id":5467,"title":5468,"body":5469,"client":28,"cover":28,"date":28,"demoUrl":28,"description":28,"direction":1217,"emoji":5562,"extension":33,"faq":5563,"featured":94,"links":5579,"meta":5580,"navigation":79,"nda":79,"order":610,"own":94,"path":5581,"related":5582,"results":5583,"seo":5592,"shot":28,"shotAlt":28,"stack":5593,"status":4540,"stem":5594,"subtitle":5595,"summary":5596,"__hash__":5597},"cases_en\u002Fen\u002Fkeysy\u002Fkorporativnyi-shlyuz.md","Corporate gateway on Xray and nginx for an IT team",{"type":18,"value":5470,"toc":5552},[5471,5473,5476,5478,5489,5500,5502,5506,5509,5511,5522,5524,5547,5549],[1426,5472,1429],{"id":1428},[21,5474,5475],{},"Because of regional restrictions, an entire IT company's team lost access to Canva, Notion, Jira and AI tools at once. Work stopped and deadlines were burning. Public workarounds risked leaking corporate data and violated the company's security policy.",[1426,5477,1439],{"id":1438},[21,5479,5480,5481,5484,5485,5488],{},"We deployed a sovereign corporate routing infrastructure on the company's own servers. ",[1450,5482,5483],{},"Routing"," means directing traffic to the right resources by set rules. ",[1450,5486,5487],{},"Sovereign"," here means the servers and data stay under the company's control, with no intermediaries.",[1444,5490,5491,5494,5497],{},[1447,5492,5493],{},"End-to-end traffic encryption.",[1447,5495,5496],{},"Role-based access control for employees.",[1447,5498,5499],{},"A single entry point: stable, transparent one-click access to all work resources.",[1426,5501,1517],{"id":1516},[1519,5503,5505],{"id":5504},"a-single-entry-point-for-employees","A single entry point for employees",[21,5507,5508],{},"An employee connects to the corporate gateway, and work traffic then flows through it to the required services. The gateway runs on the company's Linux servers; the stack includes Xray (a proxy-protocol core) and nginx (a web server at the entrance). Access rights depend on the employee's role.",[1426,5510,1578],{"id":1577},[1444,5512,5513,5516,5519],{},[1447,5514,5515],{},"The team's tools are accessible again.",[1447,5517,5518],{},"Corporate data stays inside the company's perimeter.",[1447,5520,5521],{},"Connecting takes an employee one click.",[1426,5523,1602],{"id":1601},[1444,5525,5526,5531,5537,5542],{},[1447,5527,5528,5530],{},[1450,5529,4725],{}," — everything on the company's servers, no third-party cloud.",[1447,5532,5533,5536],{},[1450,5534,5535],{},"Linux"," — the server foundation.",[1447,5538,5539,5541],{},[1450,5540,658],{}," — traffic routing and encryption.",[1447,5543,5544,5546],{},[1450,5545,119],{}," — the gateway's entry point.",[1426,5548,1647],{"id":1646},[21,5550,5551],{},"Completed. The client asked us not to disclose the company name. Other details about the client are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":5553},[5554,5555,5556,5559,5560,5561],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":5557},[5558],{"id":5504,"depth":1661,"text":5505},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"🔐",[5564,5567,5570,5573,5576],{"q":5565,"a":5566},"What problem does the corporate gateway solve?","Because of regional restrictions the team lost access to Canva, Notion, Jira and AI tools. The gateway restores access to all work resources through a single entry point.",{"q":5568,"a":5569},"Where does the gateway run and who owns the data?","The infrastructure is deployed on the company's own servers. Corporate data stays inside the perimeter instead of passing through public services.",{"q":5571,"a":5572},"How is employee traffic protected?","End-to-end traffic encryption and role-based access control are configured.",{"q":5574,"a":5575},"Is it hard for an employee to connect to the gateway?","No. Each employee gets a single entry point and connects with one click.",{"q":5577,"a":5578},"Why not use public services for bypassing restrictions?","Public workarounds risked leaking corporate data and violated the company's security policy.",[],{},"\u002Fen\u002Fkeysy\u002Fkorporativnyi-shlyuz",[],[5584,5586,5589],{"value":1409,"label":5585},"of the team's tools accessible again",{"value":5587,"label":5588},"0 leaks","corporate data stays inside the perimeter",{"value":5590,"label":5591},"1 click","to connect to the secure gateway, for any employee",{"title":5468,"description":28},[4725,5535,658,119],"en\u002Fkeysy\u002Fkorporativnyi-shlyuz","for an IT company: work tools available again, on its own servers","A corporate gateway for an IT company: secure traffic routing on the company's own servers, so the whole team can use Canva, Notion, Jira and AI tools again without public workarounds.","ZGX58FMKIG2sV-CcAc1d3FLwA9a0JDVDV2cjx4RIstE",{"id":5599,"title":5600,"body":5601,"client":5785,"cover":28,"date":1911,"demoUrl":5768,"description":28,"direction":1180,"emoji":5786,"extension":33,"faq":5787,"featured":94,"links":5806,"meta":5810,"navigation":79,"nda":94,"order":610,"own":94,"path":5811,"related":5812,"results":5813,"seo":5823,"shot":5824,"shotAlt":5825,"stack":5826,"status":1713,"stem":5828,"subtitle":5829,"summary":5830,"__hash__":5831},"cases_en\u002Fen\u002Fkeysy\u002Fvalera.md","Business-Card Site for a 3D Printing Engineer",{"type":18,"value":5602,"toc":5772},[5603,5605,5608,5610,5616,5637,5639,5643,5649,5653,5671,5675,5690,5694,5708,5710,5727,5729,5761,5763],[1426,5604,1429],{"id":1428},[21,5606,5607],{},"A 3D printing and reverse engineering engineer in Cheboksary gets orders from messengers and referrals. He needed a page to send a customer to instead of a long chat: who the specialist is, what he does, how the process works and how to get in touch. It is a business card for an individual specialist, not for a company.",[1426,5609,1439],{"id":1438},[21,5611,3563,5612,5615],{},[1450,5613,5614],{},"business-card site"," is a short page about a specialist: who he is, what he does and how to reach him. Here it is styled like engineering software, to suit the profession.",[1444,5617,5618,5621,5624,5631,5634],{},[1447,5619,5620],{},"The first screen is a 3D scanner viewport on canvas: three parts in rotation (a gear, a flange and a bracket), a scanner beam turns the point cloud into a wireframe, and the model rotates with a mouse or a finger.",[1447,5622,5623],{},"A «Process» section: scan, geometry, print, drawings. A mobile menu and a highlight for the current section in the header.",[1447,5625,5626,5627,5630],{},"A «Frequently asked questions» section on ",[1635,5628,5629],{},"\u003Cdetails>"," elements, with no JavaScript.",[1447,5632,5633],{},"A contact for the phone: a QR code pointing to the page and a vCard file; on a phone, a single «Add to contacts» button replaces the QR code.",[1447,5635,5636],{},"Own fonts, a link preview image and icons for search results.",[1426,5638,1517],{"id":1516},[1519,5640,5642],{"id":5641},"a-3d-scanner-on-canvas-with-no-libraries","A 3D scanner on canvas with no libraries",[21,5644,5645,5648],{},[1450,5646,5647],{},"Canvas"," is an area of the page where a script draws graphics. The scanner uses its own 3D projection on canvas 2D with no external libraries. Drawing runs only while the viewport is visible and the tab is active. Points are drawn in three brightness batches, the beam gradients are created once per size, and the pixel density is capped at 1.5. By the developer's measurements, script time on the first screen is about half of the first version's.",[1519,5650,5652],{"id":5651},"a-phone-contact-qr-and-vcard","A phone contact: QR and vCard",[21,5654,3563,5655,5658,5659,5662,5663,5666,5667,5670],{},[1450,5656,5657],{},"vCard"," is a business-card file that a phone opens as a new contact. Nginx serves ",[1635,5660,5661],{},".vcf"," as ",[1635,5664,5665],{},"text\u002Fvcard",", because the standard type list does not include it. On phones the ",[1635,5668,5669],{},"download"," attribute on the link is removed by a script: with it an iPhone puts the file into Files, without it the phone opens «Create contact» straight away. The vCard gives the city as Cheboksary.",[1519,5672,5674],{"id":5673},"caching-and-indexing","Caching and indexing",[21,5676,5677,5678,5681,5682,5685,5686,5689],{},"The site runs in its own ",[1635,5679,5680],{},"valera-web"," container with its own nginx config. The page, robots.txt and sitemap.xml are ",[1635,5683,5684],{},"no-cache","; CSS, JS and fonts with ",[1635,5687,5688],{},"?v="," are cached for a year and images for a week. A new version of the page is submitted to IndexNow, a protocol a site uses to tell search engines about changes.",[1519,5691,5693],{"id":5692},"animations-that-stay-light-on-a-phone","Animations that stay light on a phone",[21,5695,5696,5697,2055,5700,5703,5704,5707],{},"All animations except the scanner use only ",[1635,5698,5699],{},"transform",[1635,5701,5702],{},"opacity",": the name «decoding», the ticker, the skills strip, the drawing of icons and scroll progress. The ",[1635,5705,5706],{},"prefers-reduced-motion"," setting turns them off, and without JS the page stays static and readable.",[1426,5709,1578],{"id":1577},[1444,5711,5712,5715,5718,5721,5724],{},[1447,5713,5714],{},"The specialist's customer needs only one link: services, process and contacts are on a single page.",[1447,5716,5717],{},"The contact is saved to a phone with one tap or by scanning the QR code.",[1447,5719,5720],{},"The page is open to search engines and marked up: Person with services, WebSite, ProfilePage and FAQPage.",[1447,5722,5723],{},"The fonts are our own, so there are no requests to Google Fonts.",[1447,5725,5726],{},"The sources contain no PageSpeed measurements, so we give no scores.",[1426,5728,1602],{"id":1601},[1444,5730,5731,5737,5743,5751,5756],{},[1447,5732,5733,5736],{},[1450,5734,5735],{},"HTML, CSS and JavaScript with no frameworks"," — the page is light and needs no build step.",[1447,5738,5739,5742],{},[1450,5740,5741],{},"Canvas 2D"," — the scanner and the point cloud without external libraries.",[1447,5744,5745,5747,5748,5750],{},[1450,5746,119],{}," — serving static files with its own cache rules and the ",[1635,5749,5665],{}," type.",[1447,5752,5753,5755],{},[1450,5754,120],{}," — a separate site container on the studio's server.",[1447,5757,5758,5760],{},[1450,5759,4043],{}," — Person, WebSite, ProfilePage and FAQPage markup for search engines and AI assistants.",[1426,5762,1647],{"id":1646},[21,5764,5765,5766,5771],{},"In production, the site is public: ",[4129,5767,5770],{"href":5768,"rel":5769},"https:\u002F\u002Fvalera.devunit-lab.ru",[4133],"valera.devunit-lab.ru",". The latest edit, of 29 September 2026, corrected the city to Cheboksary in the title, the markup and the vCard.",{"title":28,"searchDepth":29,"depth":29,"links":5773},[5774,5775,5776,5782,5783,5784],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":5777},[5778,5779,5780,5781],{"id":5641,"depth":1661,"text":5642},{"id":5651,"depth":1661,"text":5652},{"id":5673,"depth":1661,"text":5674},{"id":5692,"depth":1661,"text":5693},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"Valery Afanasiev","🖨️",[5788,5791,5794,5797,5800,5803],{"q":5789,"a":5790},"Why does an individual specialist need a business-card site if messengers exist?","The page replaces a long chat: one place shows who the specialist is, what he does and how the process runs from scan to drawings. The link is easy to send to a customer, and the site can be found through queries about 3D printing, reverse engineering and 3D scanning.",{"q":5792,"a":5793},"How can a specialist's contact be saved to a phone from the site?","The site has a vCard contact and a QR code pointing to the page. On a phone, a «Add to contacts» button replaces the QR code: on an iPhone it opens the «Create contact» window at once instead of dropping a file into the Files app.",{"q":5795,"a":5796},"How is the business-card site set up for SEO?","The page has its own title and description aimed at queries about 3D printing, reverse engineering and 3D scanning, and the h1 carries the name and profession. It has JSON-LD markup for Person with services, WebSite, ProfilePage and FAQPage, robots.txt, sitemap.xml and icons for search results. After edits the address is submitted to IndexNow.",{"q":5798,"a":5799},"Does the animated 3D scanner slow the site down on a phone?","The scanner is drawn on canvas 2D with no libraries and draws only while it is on screen and the tab is active. The point cloud is lightened to 560 points per part at no more than 30 frames per second. With the browser's data-saver mode on, a single static frame is shown.",{"q":5801,"a":5802},"Does the site work without JavaScript?","Yes. Without the script the page stays static and readable. Animations are turned off by the prefers-reduced-motion setting, and apart from the scanner they use only the transform and opacity properties.",{"q":5804,"a":5805},"Where is the site hosted?","In its own nginx container on the studio's web server, behind an entry nginx. The page, robots.txt and sitemap.xml are always revalidated with the server, while styles, scripts and fonts with a version in the URL are cached for a year.",[5807],{"label":5808,"url":5768,"note":5809},"Open the site","valera.devunit-lab.ru (Russian)",{},"\u002Fen\u002Fkeysy\u002Fvalera",[],[5814,5817,5820],{"value":5815,"label":5816},"1 page","profile, process, services, FAQ and contacts with no extra clicks",{"value":5818,"label":5819},"560 points","per part in the lightened canvas 3D scanner, at most 30 frames per second",{"value":5821,"label":5822},"107 KB","own fonts instead of Google Fonts, the two main ones preloaded",{"title":5600,"description":28},"\u002Fshots\u002Fdevunit-lab-sajt-valeriya-afanaseva.jpg","One-page website of 3D printing engineer Valery Afanasiev: first screen with a canvas 3D scanner",[5827,5647,119,120,121],"HTML \u002F CSS \u002F JS","en\u002Fkeysy\u002Fvalera","for an additive-manufacturing engineer in Cheboksary: one page instead of a long chat","A one-page business-card site for a 3D printing and reverse engineering engineer in Cheboksary: a canvas scanner with no libraries, Person and FAQPage markup, a QR code and vCard, static files on nginx.","lOkQ9tUy2Q8Wy6fBXVDp56TwZwe0vZNFXHy9sX_Ejwk",{"id":5833,"title":5834,"body":5835,"client":28,"cover":28,"date":5989,"demoUrl":28,"description":28,"direction":1207,"emoji":5990,"extension":33,"faq":5991,"featured":94,"links":6010,"meta":6011,"navigation":79,"nda":79,"order":6012,"own":94,"path":6013,"related":6014,"results":6015,"seo":6025,"shot":28,"shotAlt":28,"stack":6026,"status":3228,"stem":6028,"subtitle":6029,"summary":6030,"__hash__":6031},"cases_en\u002Fen\u002Fkeysy\u002Fobsledovanie-seti-muzeya.md","Network and CCTV survey for a museum complex",{"type":18,"value":5836,"toc":5975},[5837,5839,5842,5852,5854,5857,5877,5879,5883,5886,5890,5893,5897,5900,5904,5907,5911,5918,5920,5937,5939,5967,5969,5972],[1426,5838,1429],{"id":1428},[21,5840,5841],{},"The management of a museum complex in Moscow has to decide what to do with an old network and its surveillance cameras. The decision needs more than an estimate by eye: it needs a picture of the current state that can be checked, showing what exactly has been established, where that is known from and what remains unconfirmed. After that it needs a proposal for the work: stages, timing and order of action.",[21,5843,3563,5844,5847,5848,5851],{},[1450,5845,5846],{},"LAN"," is a local area network: the cables, switches and addresses through which the site's devices exchange data. An ",[1450,5849,5850],{},"inventory"," is a record of what is on site, stating where each piece of data comes from. The work began on management's instruction in September 2026.",[1426,5853,1439],{"id":1438},[21,5855,5856],{},"A set of documents that can be signed and handed to management, plus the sources it is built from. What is done:",[1444,5858,5859,5862,5865,5868,5871,5874],{},[1447,5860,5861],{},"A fact base of the survey in which every statement names its source: video surveillance, the network address plan, measurements of the commercial department's workstations.",[1447,5863,5864],{},"Five internal memos of the site's document flow, each in two formats: a Markdown source and a signature-ready docx.",[1447,5866,5867],{},"A commercial proposal for network works: risks, scope, six stages and timing.",[1447,5869,5870],{},"A working document of decisions and open questions: what has been decided and why, what has not and who decides.",[1447,5872,5873],{},"A video-surveillance section obtained by a remote survey of the running server on 7 September 2026.",[1447,5875,5876],{},"A script that builds docx from Markdown in the accepted layout: A4, Times New Roman 12, 1.25 cm first-line indent.",[1426,5878,1517],{"id":1516},[1519,5880,5882],{"id":5881},"the-fact-base-every-statement-with-a-source","The fact base: every statement with a source",[21,5884,5885],{},"The fact base has three documents: video surveillance, the address plan \"as is\" and the commercial department's measurements. Every statement carries a source: a server log, a settings file, measurements and a calculation. Anything not confirmed by a source is moved into \"needs checking on site\" lists. This lets management see the line between what is established and what is assumed.",[1519,5887,5889],{"id":5888},"internal-memos-report-instruction-interim-report","Internal memos: report, instruction, interim report",[21,5891,5892],{},"The documents follow the ordinary document flow of an institution: a system administrator's report to the head of department, the managing director's instruction to run an inventory, the head of department's memo with the breakdown, and an interim report. A separate memo raises the question of processing employees' personal data, which lies outside the department's technical competence. The report was filed as interim, ahead of the instruction's deadline, because part of what was found cannot wait.",[1519,5894,5896],{"id":5895},"the-commercial-proposal-six-stages-of-work","The commercial proposal: six stages of work",[21,5898,5899],{},"The work is split into six stages with a total duration of 11 to 18 weeks. The first priority is stages 1 and 2: a survey with documentation plus urgent video-surveillance measures, over three to five weeks. Both stages run on the existing equipment and replace nothing. This way the site gets a result before any procurement decision is taken.",[1519,5901,5903],{"id":5902},"remote-survey-of-video-surveillance","Remote survey of video surveillance",[21,5905,5906],{},"The video-surveillance section was obtained by administering the running server on 7 September 2026: the state of the archive, the second stream, the backup, the list of changes and the planned work window are described. This departs from the decision to survey on site, so it is set out as a separate section, and whether to record the departure is decided on site.",[1519,5908,5910],{"id":5909},"documents-as-code-markdown-git-and-docx-builds","Documents as code: Markdown, git and docx builds",[21,5912,5913,5914,5917],{},"A document's source is Markdown: people can read it directly and its edit history lives in git. The docx version is built by a Python script (the python-docx library). The ",[1635,5915,5916],{},".gitignore"," file blocks keys, access configurations, dumps and exports, and service passwords are described in words in the documents, never as values. The repository holds no secrets, no product code and no copies of the site's data.",[1426,5919,1578],{"id":1577},[1444,5921,5922,5925,5928,5931,5934],{},[1447,5923,5924],{},"A set is assembled: the commercial proposal, a working document with open questions, five internal memos and three fact-base documents.",[1447,5926,5927],{},"Every fact-base statement is tied to a source, and the unconfirmed is kept apart.",[1447,5929,5930],{},"Discrepancies in the data are recorded: 68 cameras according to a copy of the server catalogue and 70 as of 7 September 2026.",[1447,5932,5933],{},"The interim report was filed within the instruction's deadline.",[1447,5935,5936],{},"Documents build into docx with one command and stay editable as Markdown.",[1426,5938,1602],{"id":1601},[1444,5940,5941,5947,5953,5959],{},[1447,5942,5943,5946],{},[1450,5944,5945],{},"Markdown"," — the document source: readable without special software and easy to keep in git.",[1447,5948,5949,5952],{},[1450,5950,5951],{},"Word (docx)"," — the format for signing and handing over to management.",[1447,5954,5955,5958],{},[1450,5956,5957],{},"Python and python-docx"," — building docx from Markdown in a consistent layout.",[1447,5960,5961,5964,5965,1638],{},[1450,5962,5963],{},"Git"," — edit history and protection against leaking secrets through ",[1635,5966,5916],{},[1426,5968,1647],{"id":1646},[21,5970,5971],{},"In progress. The repository is versioned by date: the document set was assembled on 5 September 2026 and the video-surveillance section was added on 7 September 2026. There are no version numbers.",[21,5973,5974],{},"Next: an on-site survey. Until it is done, the subnet masks and connectivity between them, the active equipment, the list of ports open to the external network, the mapping of cameras to switch addresses and ports, the video archive depth and the state of the face-recognition subsystem remain unestablished. A final report follows the survey, and the dates of the memos are to be checked against the actual document flow. Other details about the client are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":5976},[5977,5978,5979,5986,5987,5988],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":5980},[5981,5982,5983,5984,5985],{"id":5881,"depth":1661,"text":5882},{"id":5888,"depth":1661,"text":5889},{"id":5895,"depth":1661,"text":5896},{"id":5902,"depth":1661,"text":5903},{"id":5909,"depth":1661,"text":5910},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-07","📹",[5992,5995,5998,6001,6004,6007],{"q":5993,"a":5994},"What does a survey of the local network and video surveillance include?","An inventory of three subsystems: the local area network, video surveillance and remote access. Each gets a fact base: the address plan as it is, the composition of the video system, measurements of workstations. Every statement has a source, and anything unconfirmed goes into separate lists to be checked on site.",{"q":5996,"a":5997},"How do I show management that the network needs upgrading?","With a set of documents: the system administrator's report, the manager's instruction, a department-level breakdown and an interim report in which every conclusion rests on the fact base. A commercial proposal for the network works, with risks, scope and timing, goes with them.",{"q":5999,"a":6000},"How is a commercial proposal for a network upgrade structured?","The work is split into six stages with a total duration of 11 to 18 weeks. The first priority is stages 1 and 2: a survey with documentation plus urgent video-surveillance measures, three to five weeks in all. Both stages run on the existing equipment and replace nothing.",{"q":6002,"a":6003},"Can video surveillance be surveyed remotely?","Partly: remote access to the running server made it possible to describe the state of the archive, the streams and the backup. An on-site survey is still needed: without it the subnet masks, the active equipment, the video archive depth and the mapping of cameras to switch ports are not established.",{"q":6005,"a":6006},"What if data from different sources does not match?","The discrepancy is recorded, not smoothed over. For example, a copy of the server catalogue showed 68 cameras, while the remote survey on 7 September 2026 showed 70. Both values are recorded with the date and the source.",{"q":6008,"a":6009},"How are the survey documents stored and updated?","The source of each document is a Markdown file in git, and the signature-ready docx version is built from it by a Python script in the accepted layout. Only the source is edited: an edit made in Word is overwritten by the next build. Passwords, keys and access configurations never enter the repository.",[],{},45,"\u002Fen\u002Fkeysy\u002Fobsledovanie-seti-muzeya",[],[6016,6019,6022],{"value":6017,"label":6018},"6 stages","in the commercial proposal for network works, 11 to 18 weeks in total",{"value":6020,"label":6021},"5 memos","internal documents: report, instruction, breakdown, interim report and a personal-data memo",{"value":6023,"label":6024},"3 subsystems","network, video surveillance and remote access described in a fact base with sources",{"title":5834,"description":28},[5945,293,6027,5951,5963],"python-docx","en\u002Fkeysy\u002Fobsledovanie-seti-muzeya","for a museum complex in Moscow: a document set and a phased commercial proposal","A survey of the local network, video surveillance and remote access of a museum complex in Moscow: a fact base with a source for every finding, internal memos and a six-stage commercial proposal for network works.","LVS3tHMTCz3hClIYrTTBTOm7f7N5zVIhriH-24xzxHA",{"id":6033,"title":6034,"body":6035,"client":28,"cover":28,"date":1672,"demoUrl":28,"description":28,"direction":1180,"emoji":6236,"extension":33,"faq":6237,"featured":94,"links":6256,"meta":6257,"navigation":79,"nda":79,"order":6012,"own":94,"path":6258,"related":6259,"results":6260,"seo":6270,"shot":28,"shotAlt":28,"stack":6271,"status":3228,"stem":6273,"subtitle":6274,"summary":6275,"__hash__":6276},"cases_en\u002Fen\u002Fkeysy\u002Fsajt-muzeya-demo.md","Museum Website and Hosting Audit with a Demo of the New Site",{"type":18,"value":6036,"toc":6221},[6037,6039,6042,6045,6059,6061,6068,6071,6091,6093,6097,6103,6107,6110,6114,6117,6121,6135,6139,6149,6153,6156,6158,6178,6180,6216,6218],[1426,6038,1429],{"id":1428},[21,6040,6041],{},"The management of a museum complex in Moscow needed to understand the state of the live website, its risks, what to do and in what order, and what the result would look like. The contract is not yet signed, so promises on paper are not enough: the client wants to see and try the outcome in advance.",[21,6043,6044],{},"Further conditions:",[1444,6046,6047,6050,6053,6056],{},[1447,6048,6049],{},"three businesses under one roof: a museum, a restaurant with banquets and a games department, and each needs a site;",[1447,6051,6052],{},"foreign tourists matter only to the museum, the restaurant needs two languages and the games department one;",[1447,6054,6055],{},"the site must open under any access restrictions, with no calls to foreign services;",[1447,6057,6058],{},"the demo must not reach search results in place of the live site, and it must not be handed over for wholesale copying.",[1426,6060,1439],{"id":1438},[21,6062,6063,6064,6067],{},"The work has three parts: an audit, a document package and a demo stand. A ",[1450,6065,6066],{},"demo stand"," is a working sample of the new site that accepts no requests or personal data and serves only for showing.",[21,6069,6070],{},"What was done:",[1444,6072,6073,6076,6079,6082,6085,6088],{},[1447,6074,6075],{},"A technical audit of the site from a backup copy: 11 sections, 31 items.",[1447,6077,6078],{},"An audit of hosting and domain: what is paid for and what actually works.",[1447,6080,6081],{},"A document package for management: a commercial proposal of 8 sections and 6 stages, six appendices, decisions and open questions, a re-check protocol, and 11 templates and reference sheets.",[1447,6083,6084],{},"Internal memos and reports: seven DOCX documents, with the same documents in interactive HTML with light and dark themes and in PDF.",[1447,6086,6087],{},"A demo stand of four pages: an overall review and three areas, museum, restaurant and games.",[1447,6089,6090],{},"A presentation panel: five ready scenarios, seven looks, eleven fonts, light and dark themes, three languages, twelve switchable modules, an automatic tour and a QR code for a phone.",[1426,6092,1517],{"id":1516},[1519,6094,6096],{"id":6095},"three-sites-on-a-shared-core","Three sites on a shared core",[21,6098,4301,6099,6102],{},[1450,6100,6101],{},"shared core"," is code that describes once the grid, header, cards, forms, cookie notice, panel and accessibility. It contains no colours, no fonts and no hall or programme names: the areas differ only in variables and data. The core is 2,081 lines, the banquet area adds 361 and the games area 535. The museum build is older than the core and lives on its own, with the area switcher, the group strip and the fonts moved onto the core.",[1519,6104,6106],{"id":6105},"a-presentation-panel-that-needs-no-explaining","A presentation panel that needs no explaining",[21,6108,6109],{},"The panel slides in from the right, and on a phone as a sheet from the bottom. Five scenarios assemble a combination of settings in one tap: «Foundation only», «Everything on», «On a phone», «Showcase», and one of its own for each area. A module that is switched off is removed from the accessibility tree entirely instead of being faded with transparency, so you can see how the site looks without it. The «Show in detail» tables explain what a search engine sees, which external loads a page has and where every figure comes from.",[1519,6111,6113],{"id":6112},"a-demo-you-can-try-without-personal-data","A demo you can try without personal data",[21,6115,6116],{},"The museum demo has sessions for today in Moscow time and a three-step ticket purchase with no payment and no personal-data fields. The restaurant shows a menu of eight sections with a table estimate and an «Add to request» button. The games area calculates a booking total for groups of up to 15 people and gives every programme its own link. Names, durations and some prices are taken from the live site unchanged, while session times and seat availability are made up.",[1519,6118,6120],{"id":6119},"protecting-the-demo-stand-from-indexing-and-copying","Protecting the demo stand from indexing and copying",[21,6122,6123,6124,6126,6127,6130,6131,6134],{},"Indexing is closed by a meta tag, ",[1635,6125,2336],{}," and an ",[1635,6128,6129],{},"X-Robots-Tag"," header, the last of which covers files too. A gate admits no one past its own page without JavaScript and a cookie. A closed list of downloaders and language-model crawlers gets a 403 refusal, request frequency is limited and embedding the page in a foreign frame is forbidden. So a ",[1635,6132,6133],{},"curl"," request without a browser gets 302 or 403 for the page itself, which is not a fault. It is a barrier, not a lock: a person with developer tools can see the markup. For real closure the configuration includes a ready-made password-protection block.",[1519,6136,6138],{"id":6137},"a-content-security-policy-without-a-single-external-address","A Content-Security-Policy without a single external address",[21,6140,6141,6144,6145,6148],{},[1450,6142,6143],{},"Content-Security-Policy (CSP)"," is a header that lists where the browser may load resources from. Here it is ",[1635,6146,6147],{},"default-src 'self'",": every foreign address is forbidden. The fonts (24 families, 218 styles under a free licence) and the map snapshot are stored on our own server. In the demo, the Yandex Metrica counter runs in «showcase» mode and only writes events to the panel's log. In live mode the counter stays silent until cookie consent, and session replay is off because it records what is typed into form fields.",[1519,6150,6152],{"id":6151},"a-document-package-for-management","A document package for management",[21,6154,6155],{},"The documents are written for people outside IT. The «Before and after» appendix has 12 comparisons without technical terms, the translation-fix register checks the English and Chinese versions against the Russian original across 44 items, and the data register notes 22 remarks on prices, formats and headings. The main documents come in Markdown, HTML and PDF, and the proposal is printed for signature.",[1426,6157,1578],{"id":1577},[1444,6159,6160,6163,6166,6169,6172,6175],{},[1447,6161,6162],{},"Management received a technical audit in which each of 31 items points to a file or a database record.",[1447,6164,6165],{},"The client sees the future site before the contract and decides for themselves which modules are needed.",[1447,6167,6168],{},"The demo opens on a phone like an ordinary site, checked at the sizes of three iPhones.",[1447,6170,6171],{},"The demo sends no data outside: there are no personal-data forms or payments, and no external requests from the pages.",[1447,6173,6174],{},"Shared maintainability: a change to the grid, accessibility or cookie notice is made once for all three sites.",[1447,6176,6177],{},"The repository has no automated tests, and checking is manual against the acceptance criteria.",[1426,6179,1602],{"id":1601},[1444,6181,6182,6188,6193,6199,6204,6210],{},[1447,6183,6184,6187],{},[1450,6185,6186],{},"HTML, CSS and JavaScript with no build step or frameworks"," — the demo opens on any web server and pulls in no dependencies.",[1447,6189,6190,6192],{},[1450,6191,119],{}," — serving static files, the gate, security headers, the CSP policy and request rate limiting.",[1447,6194,6195,6198],{},[1450,6196,6197],{},"Docker and docker-compose"," — starting the stand with one command on the studio's web server, in its own container and network.",[1447,6200,6201,6203],{},[1450,6202,5156],{}," — HTTPS on the entry nginx that holds ports 80 and 443.",[1447,6205,6206,6209],{},[1450,6207,6208],{},"Markdown, HTML, PDF, DOCX"," — the document package in formats for print, on-screen showing and internal paperwork.",[1447,6211,6212,6215],{},[1450,6213,6214],{},"Real-ESRGAN and AVIF"," — restoring the first-screen frame with a neural network and converting it to a modern format: 136 KB on a large screen and 84 KB on a phone, at twice the resolution of the previous frame.",[1426,6217,1647],{"id":1646},[21,6219,6220],{},"Demo stand version 3.5.0 of 22 September 2026, work in progress. Since 18 September 2026 the stand has been hosted on the studio's web server in Moscow and closed to search engines. The document package was prepared and re-checked in early September. Open: the client's decision on the commercial proposal, since the repository holds no status marks for the work. A live chatbot on a language model is not part of the demo: its answers are prerecorded, and a live bot is estimated separately. Other client details are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":6222},[6223,6224,6225,6233,6234,6235],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":6226},[6227,6228,6229,6230,6231,6232],{"id":6095,"depth":1661,"text":6096},{"id":6105,"depth":1661,"text":6106},{"id":6112,"depth":1661,"text":6113},{"id":6119,"depth":1661,"text":6120},{"id":6137,"depth":1661,"text":6138},{"id":6151,"depth":1661,"text":6152},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"🏛️",[6238,6241,6244,6247,6250,6253],{"q":6239,"a":6240},"How do you show a client the result of a new website before work begins?","You build a demonstration stand where the look, font, language and theme can be changed and twelve modules switched on and off. That shows what each item of the proposal delivers and how the site looks without it. The stand accepts no requests, payments or personal data, and it is shown separately from the live site.",{"q":6242,"a":6243},"Can one site serve a museum, a restaurant and a games department?","In this project they are three sites on a shared core. The grid, header, forms, cookie notice and accessibility are written once, and everything that differs between the areas is moved into variables and data for each one. A fix to the core applies to all sites at once, so maintenance costs less than three separate copies.",{"q":6245,"a":6246},"How do you close a demo site to indexing and automatic copying?","Indexing is closed on three levels: a meta tag, robots.txt and an X-Robots-Tag header that also covers images, video and fonts. In addition there is a gate that admits no one without JavaScript and a cookie, a refusal for known site downloaders and language-model crawlers, and request rate limiting. It is a barrier, not a safe: a person with developer tools can see the markup.",{"q":6248,"a":6249},"How do you build a site that makes no requests to foreign servers?","Fonts, images, video and a map snapshot are stored in the repository and served from the same server. A Content-Security-Policy with default-src 'self' forbids the browser any external address. The «External loads» button in the presentation panel counts requests right in the viewer's browser.",{"q":6251,"a":6252},"What does the audit of a museum's site and hosting include?","A technical audit of 11 sections and 31 items, each pointing to a file or a database record. A translation-fix register of 44 items, a register of data remarks of 22 items and 12 «now and after» comparisons written without technical terms. Hosting and domain were checked separately: what is paid for and what actually works.",{"q":6254,"a":6255},"How do you present technical findings to people outside IT?","The demo has an automatic tour of 22 steps in plain words: the «now» steps show a real snapshot of the live site and the «after» steps show the live page. The card says where each fact comes from. A separate note explains the Lighthouse scores: what the check is and where the line between «good» and «bad» lies.",[],{},"\u002Fen\u002Fkeysy\u002Fsajt-muzeya-demo",[],[6261,6264,6267],{"value":6262,"label":6263},"31 items","in the technical site audit across 11 sections, each pointing to a file or a database record",{"value":6265,"label":6266},"3 sites","museum, restaurant and games on a shared core of 2,081 lines",{"value":6268,"label":6269},"0 external","requests from the demo pages: fonts, images and the map are on our own server",{"title":6034,"description":28},[5827,119,120,6272],"CSP","en\u002Fkeysy\u002Fsajt-muzeya-demo","for a museum complex in Moscow: three sites on one core, shown before the contract","A technical audit of a Moscow museum complex's website and hosting, a document package and a demo stand for a new site: three sites on a shared core, closed to indexing and copying.","Fkug772NoxqzNmWf3-Q-wAN_Q-a8BJkaIRb1TQw4Ixk",{"id":6278,"title":6279,"body":6280,"client":28,"cover":28,"date":3184,"demoUrl":28,"description":28,"direction":1180,"emoji":6495,"extension":33,"faq":6496,"featured":94,"links":6515,"meta":6516,"navigation":79,"nda":79,"order":773,"own":94,"path":6517,"related":6518,"results":6519,"seo":6529,"shot":28,"shotAlt":28,"stack":6530,"status":2249,"stem":6533,"subtitle":6534,"summary":6535,"__hash__":6536},"cases_en\u002Fen\u002Fkeysy\u002Fjoomla-to-wordpress.md","Joomla to WordPress Migration Without Losing Rankings",{"type":18,"value":6281,"toc":6480},[6282,6284,6287,6290,6304,6306,6312,6314,6343,6345,6349,6352,6355,6359,6369,6373,6378,6382,6385,6389,6392,6396,6399,6401,6421,6423,6472,6474],[1426,6283,1429],{"id":1428},[21,6285,6286],{},"The client, a cardboard packaging manufacturer, ran three sites on Joomla 3.10 with a Gantry 5 template and the RSForm form component. The CMS was outdated with nothing to update to. Content could not be edited without a developer, and the layout fell apart on a phone.",[21,6288,6289],{},"The conditions of the move:",[1444,6291,6292,6295,6298,6301],{},[1447,6293,6294],{},"content, page URLs and search rankings must not be lost;",[1447,6296,6297],{},"the client edits texts, photos, cards and contacts on their own;",[1447,6299,6300],{},"the site works on a phone as well as on a computer;",[1447,6302,6303],{},"the client has two legal entities, so two sites remain, and they must not overlap in links, contacts or admin panels.",[1426,6305,1439],{"id":1438},[21,6307,3563,6308,6311],{},[1450,6309,6310],{},"CMS migration"," is not «rebuilding the site»; it is moving content, URLs and SEO data into a new system while keeping what search engines already know. We parsed the dumps of three Joomla databases with Python scripts, cleaned the content and loaded it into WordPress.",[21,6313,6070],{},[1444,6315,6316,6319,6322,6325,6328,6331,6334,6337,6340],{},[1447,6317,6318],{},"Transfer of texts, nested categories, dates, page URLs and attachments; RSForm requests exported to an archive CSV.",[1447,6320,6321],{},"An idempotent import: a repeat run updates posts and creates no duplicates.",[1447,6323,6324],{},"A map of old URLs and permanent 301 redirects to the new ones.",[1447,6326,6327],{},"A catalogue of 325 FEFCO designs with die-line drawings, groups, similar designs and a call to action under every card.",[1447,6329,6330],{},"Site search: FEFCO code, everyday names through a synonym dictionary, dimensions such as «300x200x150», live suggestions.",[1447,6332,6333],{},"A child theme of Astra with the palette taken from the originals, a dark mode with a switch and a responsive layout.",[1447,6335,6336],{},"Quote-request and call-back forms on Contact Form 7 with spam protection that needs no captcha.",[1447,6338,6339],{},"A «Site» panel in the admin: more than 60 fields, separate texts, the FEFCO reference and a built-in guide.",[1447,6341,6342],{},"Login protection and one-command deployment on the server.",[1426,6344,1517],{"id":1516},[1519,6346,6348],{"id":6347},"moving-from-joomla-to-wordpress-from-a-database-dump","Moving from Joomla to WordPress from a database dump",[21,6350,6351],{},"The pipeline: SQL dumps are loaded into MySQL, Python scripts with BeautifulSoup pull out the posts and clean the HTML, the result is saved as JSON, and PHP scripts create the posts in WordPress through WP-CLI. The cleaning rules live in a single script that the export imports rather than copies. Joomla shortcodes are stripped, drafts arrive as drafts, and informational and legal posts become pages.",[21,6353,6354],{},"In Joomla, tables were used for layout as well as for price lists, including «picture and text» blocks. The parsing threshold was derived from the real data: a table is broken into blocks if it contains an image, has at most two rows and fewer than half of its cells are numeric. Of 98 tables across the three databases, 95 layouts were unpacked, and the one real 53-row price list stayed a table.",[1519,6356,6358],{"id":6357},"_301-redirects-when-changing-cms","301 redirects when changing CMS",[21,6360,3563,6361,6364,6365,6368],{},[1450,6362,6363],{},"301 redirect"," is a permanent redirection: it tells a search engine that the page has moved and passes the old address's weight to the new one. Joomla has a quirk: with friendly URLs on, an article's address is built from the menu item. So the map was checked against the live original: an address built from the category returned 404, while the one built from the menu item opened. The finished rules are kept as ",[1635,6366,6367],{},".htaccess"," files and as a CSV for human review.",[1519,6370,6372],{"id":6371},"the-fefco-catalogue-in-wordpress","The FEFCO catalogue in WordPress",[21,6374,6375,6377],{},[1450,6376,4759],{}," is the European classification of corrugated-board packaging designs. The catalogue of 325 designs was restored with die-line drawings and sorted into nine groups. The entry point is a showcase of groups, then a section, then a card. A card has a block of similar designs and «Back» and «Next» links. The filter by design type and assembly method works as an ordinary link with no JavaScript: the result can be forwarded, and pagination keeps the chosen conditions.",[1519,6379,6381],{"id":6380},"isolating-two-sites-of-one-client","Isolating two sites of one client",[21,6383,6384],{},"The sites run in separate Docker containers with separate databases, and share no links, contacts or admin panel. The absence of cross-links was verified on the assembled pages: zero in both directions.",[1519,6386,6388],{"id":6387},"admin-login-protection","Admin login protection",[21,6390,6391],{},"A protection module counts wrong passwords per address: after three errors sign-in is closed for 15 minutes, on a repeat within a day for an hour, then for a day. The login error is the same for a foreign username and a wrong password, so the response does not reveal a valid username. A second line sits on the front nginx: no more than 20 requests a minute to the login page, and WordPress service files are closed.",[1519,6393,6395],{"id":6394},"a-settings-panel-the-client-edits-the-site","A settings panel: the client edits the site",[21,6397,6398],{},"All management is gathered in the «Site» menu: an overview with launch readiness, settings, texts, the FEFCO reference and a built-in guide. The rule is simple: an empty field means «do not use», so an unfilled Metrica counter does not load its script. The settings pages end with a change history and a «Restore as it was» button.",[1426,6400,1578],{"id":1577},[1444,6402,6403,6406,6409,6412,6415,6418],{},[1447,6404,6405],{},"Three Joomla 3.10 sites are being replaced by two WordPress sites that the client manages on their own.",[1447,6407,6408],{},"The catalogue of 325 FEFCO designs is restored with drawings, search and a filter.",[1447,6410,6411],{},"Responsiveness was measured at 375 and 1280 pixels: no overflow, the menu expands and price tables turn into cards.",[1447,6413,6414],{},"Titles and descriptions are filled in for every post and category, with a sitemap, breadcrumbs, and organisation and FAQ markup.",[1447,6416,6417],{},"The migration is reproducible by script: containers, dumps, import, styling and redirects are deployed with one command.",[1447,6419,6420],{},"The sources contain no page-speed measurements, so we give no scores.",[1426,6422,1602],{"id":1601},[1444,6424,6425,6431,6437,6443,6449,6455,6461,6466],{},[1447,6426,6427,6430],{},[1450,6428,6429],{},"Python, sqlparse, BeautifulSoup, pymysql"," — parsing Joomla SQL dumps, cleaning HTML, exporting to JSON.",[1447,6432,6433,6436],{},[1450,6434,6435],{},"WordPress 6.7 and PHP 8.2"," — a new CMS with an admin the client already knows; the import, the theme and the «Site» panel are written in PHP.",[1447,6438,6439,6442],{},[1450,6440,6441],{},"MySQL 8.0"," — the sites' database and a staging database for dump parsing.",[1447,6444,6445,6448],{},[1450,6446,6447],{},"Astra and a child theme"," — the base of the design, so updates to the parent theme do not break our changes.",[1447,6450,6451,6454],{},[1450,6452,6453],{},"Rank Math SEO"," — titles, descriptions, sitemap, breadcrumbs, markup.",[1447,6456,6457,6460],{},[1450,6458,6459],{},"Contact Form 7 and Flamingo"," — request forms and their archive in the admin.",[1447,6462,6463,6465],{},[1450,6464,3648],{}," — the same environment locally and on the server, one container per site.",[1447,6467,6468,6471],{},[1450,6469,6470],{},"Let's Encrypt with DNS-01 validation"," — certificates for a demo host that cannot answer HTTP checks because of server-side restrictions.",[1426,6473,1647],{"id":1646},[21,6475,6476,6477,1638],{},"Version 1.41.0 of 28 September 2026, on a demo environment at the client. The sites are shown to the client on a demo environment with working HTTPS; search indexing is deliberately off there and gets switched on at the move to the production domain. Still open: outgoing e-mail (waiting for mailbox access), analytics counters, photos for 23 posts without a cover image, a review of the FEFCO reference by the client's technologist, and scheduled backups. Other client details are not disclosed. How we build new sites from scratch is described in ",[4129,6478,6479],{"href":81},"Websites",{"title":28,"searchDepth":29,"depth":29,"links":6481},[6482,6483,6484,6492,6493,6494],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":6485},[6486,6487,6488,6489,6490,6491],{"id":6347,"depth":1661,"text":6348},{"id":6357,"depth":1661,"text":6358},{"id":6371,"depth":1661,"text":6372},{"id":6380,"depth":1661,"text":6381},{"id":6387,"depth":1661,"text":6388},{"id":6394,"depth":1661,"text":6395},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"🏭",[6497,6500,6503,6506,6509,6512],{"q":6498,"a":6499},"How do you migrate from Joomla to WordPress without losing search rankings?","Content, page URLs, dates and attachments are moved by scripts working from the Joomla database dump. A map of old-to-new URLs drives permanent 301 redirects, and every post and category gets a title and description so search engines see the same content at the new addresses.",{"q":6501,"a":6502},"Why do old URLs stop working after a move from Joomla?","With friendly URLs on, Joomla builds an article's address from the menu item, not from the category. Here the catalogue category is called «fefco» while the menu item has another name, so an address that looks right returns 404 and the real one opens. The redirect map is built from real addresses on the original site: 24, 684 and 270 rules across the three sites.",{"q":6504,"a":6505},"What is migrated from Joomla and what stays in an archive?","Texts, nested categories, dates, page URLs and images are migrated. Requests from the RSForm form are exported to an archive CSV: there are 2,423 of them and they do not go into WordPress. 131 archived posts of one of the sites were left out of the migration.",{"q":6507,"a":6508},"Can you re-run the import without getting duplicates?","Yes. The import is idempotent: a repeat run updates posts and images but creates no copies. A separate safeguard stops the import from overwriting pages that were assembled by hand.",{"q":6510,"a":6511},"Can the client edit the site on their own after the move?","Yes. The «Site» menu in the admin gathers contacts, legal details, the home-page cover, cookies, analytics and the search dictionary: more than 60 fields. A separate screen edits 27 labels that the theme draws. Every save is remembered and the previous values can be restored with a button.",{"q":6513,"a":6514},"How do you secure WordPress after a migration?","After three wrong passwords, sign-in from that address is blocked for 15 minutes, then for an hour and a day on repeats. XML-RPC and application passwords are closed, the user list in the REST API is hidden, and the front nginx limits requests to the login page.",[],{},"\u002Fen\u002Fkeysy\u002Fjoomla-to-wordpress",[],[6520,6523,6526],{"value":6521,"label":6522},"3 → 2","sites on outdated Joomla are moving to two independent WordPress sites",{"value":6524,"label":6525},"325","FEFCO designs in the catalogue with drawings, search and a filter",{"value":6527,"label":6528},"0 links","between the two sites: verified on the assembled pages",{"title":6279,"description":28},[293,118,6531,6532,120,119,121],"PHP","MySQL","en\u002Fkeysy\u002Fjoomla-to-wordpress","for a cardboard packaging manufacturer: three sites become two","Migration of three packaging-manufacturer sites from Joomla 3.10 to two independent WordPress sites: content, 301 redirects, a catalogue of 325 FEFCO designs, SEO markup and a settings panel the client runs.","sWTQvq-iCqJiE-pvirGyZ_1BDUFR1L9NrR6b7YuQNuk",{"id":6538,"title":6539,"body":6540,"client":28,"cover":28,"date":1911,"demoUrl":28,"description":28,"direction":1217,"emoji":1353,"extension":33,"faq":6731,"featured":94,"links":6750,"meta":6751,"navigation":79,"nda":94,"order":773,"own":79,"path":6752,"related":6753,"results":6754,"seo":6764,"shot":28,"shotAlt":28,"stack":6765,"status":1713,"stem":6768,"subtitle":6769,"summary":6770,"__hash__":6771},"cases_en\u002Fen\u002Fkeysy\u002Fserver-saitov.md","A private server for several websites on Docker and nginx",{"type":18,"value":6541,"toc":6717},[6542,6544,6547,6550,6552,6559,6589,6591,6595,6600,6604,6616,6620,6627,6631,6638,6642,6645,6647,6664,6666,6706,6708,6714],[1426,6543,1429],{"id":1428},[21,6545,6546],{},"Until 18 September 2026 the studio's websites were scattered across the VPN machines: the business card, the greeting-card site and a demo site on the Russian relay, client staging sites on the second Russian node, and the greeting-card service in Moldova. Every site edit risked taking the VPN down for all clients. The relay has 1.9 GB of memory and had already gone down once because of a neighbouring service. Certificates could not be issued for the second node, so one of the sites ran without HTTPS.",[21,6548,6549],{},"The studio needed a separate server where sites share neither memory, nor nginx, nor certificates with the VPN, and where each site is protected from its neighbours.",[1426,6551,1439],{"id":1438},[21,6553,6554,6555,6558],{},"A dedicated studio web server in Moscow, whose configuration is kept in the ",[1635,6556,6557],{},"lab-server"," repository. What is done:",[1444,6560,6561,6564,6571,6574,6577,6580,6583,6586],{},[1447,6562,6563],{},"One entry nginx accepts HTTP and HTTPS, terminates TLS and routes requests to the sites.",[1447,6565,6566,6567,6570],{},"Each site runs in its own container and its own Docker network, ",[1635,6568,6569],{},"edge-\u003Csite>","; sites cannot see one another.",[1447,6572,6573],{},"All sites open over HTTPS and HTTP is redirected. A bare IP address returns nothing: the handshake is rejected and the connection is closed.",[1447,6575,6576],{},"Let's Encrypt certificates renew themselves, and nginx reloads its configuration after renewal.",[1447,6578,6579],{},"The entry nginx writes its logs to a file with rotation, and Docker logs are size-capped. The server and containers run in the Europe\u002FMoscow time zone.",[1447,6581,6582],{},"Self-hosted mail for the studio's domain: receiving, sending and a web interface.",[1447,6584,6585],{},"Nightly snapshots of the mail and of the data of hosted sites, a watchdog for the VPN chain and a job that pulls a copy of the VPN database.",[1447,6587,6588],{},"Tunnels to services abroad, so the sites can reach bots and APIs without a direct connection.",[1426,6590,1517],{"id":1516},[1519,6592,6594],{"id":6593},"one-entry-nginx-for-all-sites","One entry nginx for all sites",[21,6596,6597,6599],{},[1450,6598,119],{}," is a web server that here acts as the single point of entry. It takes a request, looks at the site name and hands the request to the right site's container. The entry nginx configuration lives in a directory mounted into the container as a whole, so an edit is applied with a reload and the container does not have to be recreated. Unknown names are rejected.",[1519,6601,6603],{"id":6602},"site-isolation-with-docker-networks","Site isolation with Docker networks",[21,6605,3563,6606,6609,6610,6612,6613,1638],{},[1450,6607,6608],{},"Docker network"," is a virtual network inside which containers see only each other. Every site gets its own ",[1635,6611,6569],{}," network, and only the site's container and the entry nginx are on it. Site ports are not published to the outside. Databases are attached only to the internal networks of their own projects, so a break-in or failure in one site gives no path to a neighbour's database. A new site is added by one pattern: a network, a container, a name in the configuration, a certificate and a site file in ",[1635,6614,6615],{},"conf.d",[1519,6617,6619],{"id":6618},"https-certificates-auto-renewal-and-nginx-reload","HTTPS certificates: auto-renewal and nginx reload",[21,6621,6622,6623,6626],{},"Certificates are issued by certbot, with ownership checked through a webroot directory, so nginx never has to stop. Renewal is driven by the ",[1635,6624,6625],{},"certbot.timer"," timer. A separate hook after each renewal makes nginx re-read the certificate: without it, nginx would keep serving the old one until it expired. A check script walks through all the sites and shows each certificate's remaining lifetime.",[1519,6628,6630],{"id":6629},"self-hosted-mail-on-the-domain-docker-mailserver","Self-hosted mail on the domain: docker-mailserver",[21,6632,6633,6634,6637],{},"A single ",[1635,6635,6636],{},"mailserver"," container holds Postfix (sending and receiving), Dovecot (IMAP access), Rspamd (spam filtering) and fail2ban. The web interface is Roundcube. Antivirus is switched off: the ClamAV signature database takes more than a gigabyte out of four. Spam is not discarded but placed in the \"Junk\" folder, because at a small volume losing a client's message is worse than sorting a folder by hand. Mail to a non-existent address is rejected. A separate container forwards every new message to a service Telegram chat: subject, sender, text and attachments as files; spam arrives as a single line without a sound.",[1519,6639,6641],{"id":6640},"tunnels-to-services-abroad","Tunnels to services abroad",[21,6643,6644],{},"Some services, including the Telegram bots, stay abroad, and from Russia new TCP connections to them often do not get through the filter. So connections are opened in advance and from abroad: ten independent frp clients in one balancing group carry the sites' requests to the service. If one session drops, new connections go to the other nine. The studio's lead bot (site requests into Telegram) reaches Telegram and an AI API through a separate egress tunnel.",[1426,6646,1578],{"id":1577},[1444,6648,6649,6652,6655,6658,6661],{},[1447,6650,6651],{},"Sites and the VPN are separated: editing a site can no longer take down the VPN.",[1447,6653,6654],{},"Every site is isolated in its own Docker network, and a bare IP address returns no content.",[1447,6656,6657],{},"Certificates renew automatically, and nginx picks them up without a restart.",[1447,6659,6660],{},"Domain mail is received on the studio's own server, and messages also arrive in Telegram.",[1447,6662,6663],{},"Nightly mail snapshots are kept for two weeks.",[1426,6665,1602],{"id":1601},[1444,6667,6668,6673,6678,6684,6690,6695,6700],{},[1447,6669,6670,6672],{},[1450,6671,119],{}," — the entry point: TLS, request routing by site, logs.",[1447,6674,6675,6677],{},[1450,6676,650],{}," — a container and a Docker network for each site.",[1447,6679,6680,6683],{},[1450,6681,6682],{},"certbot (Let's Encrypt)"," — free certificates with auto-renewal through webroot.",[1447,6685,6686,6689],{},[1450,6687,6688],{},"docker-mailserver, Roundcube"," — self-hosted domain mail and its web interface.",[1447,6691,6692,6694],{},[1450,6693,663],{}," — tunnels through which the sites reach services abroad.",[1447,6696,6697,6699],{},[1450,6698,293],{}," — the studio's lead bot and the service that forwards mail to Telegram.",[1447,6701,6702,6705],{},[1450,6703,6704],{},"logrotate, cron"," — log rotation and nightly snapshots.",[1426,6707,1647],{"id":1646},[21,6709,6710,6711,1638],{},"Version 1.16.0, dated 29 September 2026. The server has been in production since 18 September 2026, when the sites moved off the VPN servers. Since 29 September 2026 it has also run the studio's main site on Nuxt, alongside the business-card site of engineer Valery Afanasyev. Because the server lacks the memory to build Nuxt, the image is built on a workstation and loaded with ",[1635,6712,6713],{},"docker load",[21,6715,6716],{},"Open items per the README: outgoing mail is blocked by the hosting provider and queues up; some WordPress demo sites have no scheduled backups; the server sits with the same provider as the VPN nodes, so the sites are not decoupled from provider problems; the Telegram bots remain abroad and are reached through tunnels.",{"title":28,"searchDepth":29,"depth":29,"links":6718},[6719,6720,6721,6728,6729,6730],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":6722},[6723,6724,6725,6726,6727],{"id":6593,"depth":1661,"text":6594},{"id":6602,"depth":1661,"text":6603},{"id":6618,"depth":1661,"text":6619},{"id":6629,"depth":1661,"text":6630},{"id":6640,"depth":1661,"text":6641},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},[6732,6735,6738,6741,6744,6747],{"q":6733,"a":6734},"How do I host several websites on one server so they do not interfere with each other?","One entry nginx receives every request, terminates TLS and routes it to the right site. Each site runs in its own container and its own Docker network, so sites cannot see one another. Databases are attached only to the internal networks of their own projects, and the entry nginx cannot reach them.",{"q":6736,"a":6737},"Why a separate server for websites when VPN servers already exist?","Sites used to live on the VPN machines, and every site edit risked taking down the VPN for all clients: the relay, with 1.9 GB of memory, once went down because of a neighbouring service. A separate server breaks that link: sites and the VPN no longer share memory, nginx or certificates.",{"q":6739,"a":6740},"How are HTTPS certificates renewed without manual work or downtime?","Let's Encrypt certificates are renewed by certbot on a timer. After each renewal a hook tells the entry nginx, which reloads its configuration without restarting the container. A check script shows the remaining lifetime of every certificate; the alert threshold is under 20 days.",{"q":6742,"a":6743},"Can I run mail on my own domain instead of the hosting provider's?","Yes. docker-mailserver, with Postfix, Dovecot, Rspamd and fail2ban, handles receiving and sending, and mail can be read in the Roundcube web interface or any mail client. DNS records are required: the server address, MX, SPF, DKIM, DMARC and a reverse PTR record for the IP address.",{"q":6745,"a":6746},"Are backups kept?","Nightly snapshots cover the mail (kept for two weeks) and the database and files of some hosted sites; a separate script pulls a copy of the VPN database. Some WordPress demo sites still have no scheduled backups, which is listed under limitations.",{"q":6748,"a":6749},"What does not work as well as we would like yet?","The hosting provider blocks outgoing connections on mail ports: receiving mail works, while outgoing messages queue in Postfix and will go out by themselves once a way out appears. The workarounds are a plan change, sending through an external relay over an HTTPS API, or a tunnel to a server abroad.",[],{},"\u002Fen\u002Fkeysy\u002Fserver-saitov",[],[6755,6758,6761],{"value":6756,"label":6757},"1 entry","one nginx serves HTTP and HTTPS for all sites, and a bare IP address returns nothing",{"value":6759,"label":6760},"1 site, 1 network","every site in its own Docker network, and sites cannot see each other",{"value":6762,"label":6763},"2 weeks","of nightly mail snapshots, including the mail-signing key, are kept",{"title":6539,"description":28},[119,650,655,6766,6767,663],"docker-mailserver","Roundcube","en\u002Fkeysy\u002Fserver-saitov","our own product: site isolation, HTTPS, self-hosted mail and backups","A dedicated server for the studio's websites: one entry nginx, a Docker network per site, auto-renewed certificates, self-hosted domain mail and nightly backups. Sites and the VPN no longer share memory.","NWrwkmIavDyVGnA1u0Kdkp6j-c77834K0OuSv_W8SdM",{"id":6773,"title":6774,"body":6775,"client":28,"cover":28,"date":6991,"demoUrl":28,"description":28,"direction":1193,"emoji":6992,"extension":33,"faq":6993,"featured":94,"links":7012,"meta":7013,"navigation":79,"nda":94,"order":773,"own":79,"path":7014,"related":7015,"results":7016,"seo":7026,"shot":28,"shotAlt":28,"stack":7027,"status":1713,"stem":7030,"subtitle":7031,"summary":7032,"__hash__":7033},"cases_en\u002Fen\u002Fkeysy\u002Ftoshamusic.md","ToshaMusic: Hi-Res music downloads with quality verification",{"type":18,"value":6776,"toc":6976},[6777,6779,6782,6785,6787,6790,6844,6846,6850,6853,6857,6860,6864,6871,6875,6878,6882,6885,6889,6892,6894,6914,6916,6968,6970,6973],[1426,6778,1429],{"id":1428},[21,6780,6781],{},"Streaming services promise Hi-Res but do not always deliver it. A release is labelled \"24-bit \u002F 192 kHz\" while inside it is an ordinary CD master stretched to those numbers. Catalogues also contain \"pseudo-lossless\": FLAC assembled from MP3, with a truncated spectrum. Ordinary downloaders trust the tags and the API response and put into your library something that is not Hi-Res by ear or by spectrum.",[21,6783,6784],{},"What was needed was a downloader that solves three problems. Get the maximum: automatically pick the highest format the source offers. Refuse to be fooled: measure bit depth, sample rate and spectral cut-off, compare them with the claim and report any mismatch honestly in the chat. Handle the load: a queue with limited concurrency so the streaming account does not get banned, and delivery of files up to 2 GB.",[1426,6786,1439],{"id":1438},[21,6788,6789],{},"ToshaMusic is an in-house asynchronous Telegram bot, version 1.1.2. Implemented:",[1444,6791,6792,6798,6804,6810,6816,6822,6828,6838],{},[1447,6793,6794,6797],{},[1450,6795,6796],{},"Link intake."," Qobuz, Deezer and Tidal: tracks, albums, playlists and artists; short links are expanded through their redirect.",[1447,6799,6800,6803],{},[1450,6801,6802],{},"Release card."," Cover art, artist, title, album, track count, duration, date, label, genre, explicit flag, claimed quality and the list of available formats. A \"Maximum\" button plus buttons for specific levels: FLAC 24\u002F192, 24\u002F96, 16\u002F44.1, MP3 320.",[1447,6805,6806,6809],{},[1450,6807,6808],{},"Graceful degradation."," If the source API does not respond, the card is still shown and the quality is determined from the file itself.",[1447,6811,6812,6815],{},[1450,6813,6814],{},"Verification."," ffprobe, mutagen and spectral analysis; the file name, tags and verification line come only from measurements.",[1447,6817,6818,6821],{},[1450,6819,6820],{},"Queue."," A worker pool with a limit on parallel downloads, a global queue limit and a per-user limit; cancellation while queued or running; clear messages for geo-blocking, expired tokens, rate limits, missing releases and timeouts.",[1447,6823,6824,6827],{},[1450,6825,6826],{},"Delivery."," Local Bot API, files up to 2000 MB, cover thumbnail, uncompressed ZIP for long releases, retries on flood control.",[1447,6829,6830,6832,6833,2055,6835,1638],{},[1450,6831,5306],{}," Statistics including the number of fakes caught and the distribution by real quality, users with bans and history, broadcasts, streaming tokens replaceable from the chat without a restart, settings, ",[1635,6834,3289],{},[1635,6836,6837],{},"\u002Fsysinfo",[1447,6839,6840,6843],{},[1450,6841,6842],{},"Headless mode."," The same engine as a separate service with an internal HTTP API for embedding into another bot.",[1426,6845,1517],{"id":1516},[1519,6847,6849],{"id":6848},"ffprobe-and-mutagen-verifying-bit-depth","ffprobe and mutagen: verifying bit depth",[21,6851,6852],{},"After download, ffprobe (a utility from FFmpeg) reads the codec, sample rate, bit depth, bitrate, channel count and duration. For FLAC, ffprobe often reports the container's 32 bits instead of the real 24, so the bit depth is refined by mutagen, an audio tag library. If 24\u002F192 was requested but 16\u002F44.1 arrived, the chat gets a warning that the release is available at most in CD quality.",[1519,6854,6856],{"id":6855},"lossless-cut-off-spectrum-how-a-fake-is-caught","Lossless cut-off spectrum: how a fake is caught",[21,6858,6859],{},"A fragment from the middle of the track is decoded to PCM (uncompressed audio samples), and an averaged FFT (fast Fourier transform, which breaks a signal down by frequency) finds the frequency above which the spectrum is empty. A drop in the 14 to 20.6 kHz range flags the file as re-encoded from a lossy source. Nothing above 24 kHz on a release claiming 96 or 192 kHz reveals a stretched CD master. A cut-off at 19 to 20.6 kHz also occurs in honest, dark masterings, so that warning is worded softly, and an unambiguous verdict is given only for a drop below 19.5 kHz. The analysis can be switched off in settings.",[1519,6861,6863],{"id":6862},"name-and-tags-from-measurements-only","Name and tags from measurements only",[21,6865,6866,6867,6870],{},"The file name is built from actual parameters: ",[1635,6868,6869],{},"Artist - Title [24bit-192kHz].flac",". A verification line with bit depth, sample rate, bitrate, cut-off frequency and the ToshaMusic version is written into the tags, along with a QUALITY_VERIFIED field. No file in the library ends up with numbers in its name that contradict its contents.",[1519,6872,6874],{"id":6873},"queue-with-limited-concurrency","Queue with limited concurrency",[21,6876,6877],{},"Downloads are performed by streamrip, launched as a child process with a config the bot generates itself. A worker pool on top of asyncio.Queue enforces a limit on simultaneous downloads (2 by default), a global queue limit and a per-user task limit: this protects the streaming account from a ban for downloading too aggressively. The status in the chat updates by stage: download, verify, tag, send. Each task has an isolated working directory, abandoned directories are cleaned at startup, and tasks left hanging after a crash are marked as interrupted.",[1519,6879,6881],{"id":6880},"local-bot-api-files-up-to-2-gb","Local Bot API: files up to 2 GB",[21,6883,6884],{},"The cloud Bot API caps file uploads at 50 MB. A local telegram-bot-api runs next to the bot in Docker in local mode: the file is passed as a reference to a path on disk without copying, with a 2000 MB limit. The data directory is mounted at the same path in both containers; otherwise the server would not see the file.",[1519,6886,6888],{"id":6887},"headless-mode-for-embedding","Headless mode for embedding",[21,6890,6891],{},"Since version 1.1.0 the project has two entry points. The regular one starts the aiogram bot. The second creates no bot at all: the same database, queue and engine sit behind an internal HTTP API with routes for the card, jobs, cancellation and acknowledgement. Finished files are handed over through a shared volume, and business errors are returned in a typed form so the consumer does not have to parse text. An engine crash cannot affect the consuming bot: they run in separate containers. This is how the Hi-Res music section works inside Downloader Bot.",[1426,6893,1578],{"id":1577},[1444,6895,6896,6899,6902,6905,6908,6911],{},[1447,6897,6898],{},"The best available quality: up to FLAC 24-bit \u002F 192 kHz.",[1447,6900,6901],{},"Fake Hi-Res is exposed by spectrum, any mismatch with the claimed quality is visible in the chat, and file names and tags match the contents.",[1447,6903,6904],{},"Files up to 2 GB go straight to Telegram with no size-limit errors.",[1447,6906,6907],{},"Source failures do not break the flow: the card is still shown and errors are explained in plain language.",[1447,6909,6910],{},"Streaming tokens are changed from the admin panel without restarting the container.",[1447,6912,6913],{},"The engine embeds into another bot over HTTP.",[1426,6915,1602],{"id":1601},[1444,6917,6918,6923,6929,6934,6940,6945,6951,6957,6963],{},[1447,6919,6920,6922],{},[1450,6921,1609],{},": an asynchronous bot, FSM for admin scenarios, inline keyboards only.",[1447,6924,6925,6928],{},[1450,6926,6927],{},"pydantic-settings",": type-checked configuration.",[1447,6930,6931,6933],{},[1450,6932,2917],{},": models for users, tasks, tracks, settings and broadcasts; migrations checked against the models.",[1447,6935,6936,6939],{},[1450,6937,6938],{},"streamrip",": the download engine for Qobuz, Deezer and Tidal, run as a child process.",[1447,6941,6942,6944],{},[1450,6943,3711],{},": metadata clients (Qobuz JSON API, Deezer public API, Tidal API v1), the headless-mode HTTP server, proxy support.",[1447,6946,6947,6950],{},[1450,6948,6949],{},"FFmpeg, ffprobe, mutagen, numpy",": measuring parameters, decoding a fragment, FFT for the cut-off search.",[1447,6952,6953,6956],{},[1450,6954,6955],{},"loguru",": rotated logs with a separate error file.",[1447,6958,6959,6962],{},[1450,6960,6961],{},"Local Bot API",": delivery of files up to 2000 MB.",[1447,6964,6965,6967],{},[1450,6966,650],{},": the bot and telegram-bot-api with a shared data directory, Moscow time zone.",[1426,6969,1647],{"id":1646},[21,6971,6972],{},"In-house product, in production. The current version is 1.1.2 from 8 September 2026: dependency pins were cleaned up, and streamrip is installed separately because of incompatible aiofiles ranges. Version 1.1.0 (3 September 2026) added the headless mode; 1.0.0 (19 August 2026) was the first working release.",[21,6974,6975],{},"Limitations: the bot works from the owner's active subscription and does not bypass service protection; Tidal tokens are obtained through an external OAuth flow and are not refreshed automatically; admin scenario state lives in memory and resets on restart. Planned: automatic Tidal token refresh, FSM storage in Redis, CSV export of statistics, catalogue search inside the bot, ALAC\u002FAAC conversion, saving a spectrogram as proof, pytest coverage.",{"title":28,"searchDepth":29,"depth":29,"links":6977},[6978,6979,6980,6988,6989,6990],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":6981},[6982,6983,6984,6985,6986,6987],{"id":6848,"depth":1661,"text":6849},{"id":6855,"depth":1661,"text":6856},{"id":6862,"depth":1661,"text":6863},{"id":6873,"depth":1661,"text":6874},{"id":6880,"depth":1661,"text":6881},{"id":6887,"depth":1661,"text":6888},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-08","🎵",[6994,6997,7000,7003,7006,7009],{"q":6995,"a":6996},"How do you check that a FLAC is real Hi-Res and not an MP3?","After download, ffprobe reads the codec, sample rate and bit depth, mutagen refines the FLAC bit depth, and a fragment from the middle of the track is decoded to PCM and averaged with an FFT to find the spectral cut-off. A drop in the 14 to 20.6 kHz range reveals a re-encode from a lossy source; nothing above 24 kHz on a release labelled 96\u002F192 kHz reveals a stretched CD master.",{"q":6998,"a":6999},"What is Hi-Res and why not take the streaming service's word for it?","Hi-Res is audio with a bit depth and sample rate above CD, for example FLAC 24-bit \u002F 192 kHz. Streaming services regularly label a release as 24\u002F192 while serving an ordinary CD master, and catalogues contain \"pseudo-lossless\" built from MP3. The bot builds the file name, tags and verification line only from measured parameters.",{"q":7001,"a":7002},"Which services are supported?","Qobuz, Deezer and Tidal: tracks, albums, playlists and artists; short links are expanded through their redirect. Deezer tops out at CD quality (16-bit \u002F 44.1 kHz), so a higher request is lowered automatically; FLAC there needs a HiFi subscription.",{"q":7004,"a":7005},"How does the bot deliver files over 50 MB?","Through a local telegram-bot-api in local mode: the file is passed as a path on disk without copying, with a 2000 MB limit instead of 50 MB. Audio is sent with cover art, duration, artist and title; releases longer than a threshold are packed into an uncompressed ZIP.",{"q":7007,"a":7008},"What happens if the requested quality is not available?","The bot says so plainly in the chat, for example that the release is available at most as 16-bit \u002F 44.1 kHz FLAC. The file is named after its actual quality, and a verification line with the measured parameters is written into the tags.",{"q":7010,"a":7011},"Can the downloader be embedded into another bot?","Yes. Since version 1.1.0 there is a headless mode: the same database, queue and engine run as a separate service with an internal HTTP API while another bot provides its own interface. That is how ToshaMusic is embedded in Downloader Bot.",[],{},"\u002Fen\u002Fkeysy\u002Ftoshamusic",[],[7017,7020,7023],{"value":7018,"label":7019},"24-bit \u002F 192 kHz","the highest FLAC quality the bot delivers; the file name reflects what was measured",{"value":7021,"label":7022},"2 GB","files via the local Bot API instead of the 50 MB limit",{"value":7024,"label":7025},"2 modes","a regular Telegram bot and a headless engine with an HTTP API for embedding",{"title":6774,"description":28},[293,294,3004,6938,312,7028,7029,6961,120],"mutagen","numpy","en\u002Fkeysy\u002Ftoshamusic","in-house Telegram bot: FLAC 24-bit \u002F 192 kHz from Qobuz, Deezer and Tidal","A Telegram bot that downloads Hi-Res music from a Qobuz, Deezer or Tidal link, up to FLAC 24-bit \u002F 192 kHz, and verifies that the FLAC is genuine: ffprobe, mutagen and spectral analysis instead of the streaming API's promises.","4stXngfKkSwWUMvL4dlholiFN8TxclDQ15iUJ2nePj0",{"id":7035,"title":7036,"body":7037,"client":28,"cover":28,"date":7203,"demoUrl":7204,"description":28,"direction":1217,"emoji":7205,"extension":33,"faq":7206,"featured":94,"links":7225,"meta":7229,"navigation":79,"nda":94,"order":7230,"own":79,"path":7231,"related":7232,"results":7233,"seo":7242,"shot":28,"shotAlt":28,"stack":7243,"status":1713,"stem":7247,"subtitle":7248,"summary":7249,"__hash__":7250},"cases_en\u002Fen\u002Fkeysy\u002Finstrukcii-vpn.md","VPN setup guides for Android, iPhone and desktop",{"type":18,"value":7038,"toc":7189},[7039,7041,7044,7047,7049,7052,7072,7074,7078,7081,7085,7103,7107,7110,7114,7132,7136,7143,7145,7159,7161,7181,7183,7186],[1426,7040,1429],{"id":1428},[21,7042,7043],{},"A person with no technical background who has just received a VPN subscription needs a clear “what to tap” guide for their own device. Without one, they write to support with the same questions every time: where to get the app, how to add the subscription, how to switch the VPN on. Each such question is a load on whoever supports the service.",[21,7045,7046],{},"Users' devices vary: phones on Android, iPhone and iPad, computers on Windows, macOS and Linux. Each platform has its own client and its own menu items. The guide has to be separate for each, short, and available where the person already is: in the chat with the service's bot in Telegram.",[1426,7048,1439],{"id":1438},[21,7050,7051],{},"A set of five static HTML pages, one per platform. The pages are designed to open inside a Telegram Mini App from the service's bot. What is implemented, per the README:",[1444,7053,7054,7057,7060,7063,7066,7069],{},[1447,7055,7056],{},"Five self-contained pages: Android, iPhone and iPad, Windows, macOS and Linux, each with steps, hints and a questions block.",[1447,7058,7059],{},"A path from installing the app to switching the VPN on, and a “Not working? Check this” section.",[1447,7061,7062],{},"A link to the service's bot at the start and at the end of every page.",[1447,7064,7065],{},"A “Done, close” button: the page closes and returns the person to the chat.",[1447,7067,7068],{},"Styling that follows the Telegram theme: colors come from the client's variables.",[1447,7070,7071],{},"Publication on GitHub Pages and an open repository on GitHub.",[1426,7073,1517],{"id":1516},[1519,7075,7077],{"id":7076},"static-pages-with-no-build-step","Static pages with no build step",[21,7079,7080],{},"Each page is one HTML file: the styles and scripts are embedded, and there is no bundler, Docker or environment variables. Such files can be opened with a double-click or served by any static host. A simple server is enough for local viewing. The only external dependency is the Telegram Web Apps script, loaded from telegram.org.",[1519,7082,7084],{"id":7083},"a-telegram-mini-app-a-page-inside-the-chat","A Telegram Mini App: a page inside the chat",[21,7086,7087,7088,2055,7091,7094,7095,7098,7099,7102],{},"A Mini App (called a WebApp in the source) is a web page that Telegram shows inside the messenger itself. On load the page calls ",[1635,7089,7090],{},"Telegram.WebApp.ready()",[1635,7092,7093],{},"expand()",": it tells Telegram it is ready and expands to full screen. The “Done, close” button calls ",[1635,7096,7097],{},"Telegram.WebApp.close()"," and returns the person to the chat. Telegram requires an HTTPS address for a Mini App, which is why the pages are hosted on GitHub Pages. Outside Telegram the ",[1635,7100,7101],{},"Telegram.WebApp"," object is unavailable and the close button doesn't work there, as expected.",[1519,7104,7106],{"id":7105},"the-telegram-theme-and-a-phone-layout","The Telegram theme and a phone layout",[21,7108,7109],{},"The pages take their colors from CSS variables set by the Telegram client, so in both dark and light themes the guide looks like part of the messenger. The layout is designed for a phone screen: per the acceptance criteria, it has to read well on a phone and the colors must follow the theme.",[1519,7111,7113],{"id":7112},"steps-for-specific-clients","Steps for specific clients",[21,7115,7116,7117,2051,7120,2051,7123,2051,7126,2051,7129,1638],{},"For Android, Windows and Linux the guide walks through the NekoBox client, for macOS it walks through V2Box, and iPhone and iPad have a page of their own. Each page has one scenario: install the app, add the subscription, switch the VPN on, and check if it doesn't work. The file name tells you the platform: ",[1635,7118,7119],{},"android.html",[1635,7121,7122],{},"ios.html",[1635,7124,7125],{},"windows.html",[1635,7127,7128],{},"macos.html",[1635,7130,7131],{},"linux.html",[1519,7133,7135],{"id":7134},"publishing-and-checking-the-pages","Publishing and checking the pages",[21,7137,7138,7139,7142],{},"No build is needed: the files are placed on any static host over HTTPS and the page addresses are set in the bot. Locally, a page can be opened in a browser or served with a simple ",[1635,7140,7141],{},"python -m http.server",". The README's acceptance criteria check five things: the pages open with no console errors, they expand to full screen inside Telegram, the bot link leads to Telegram, the layout reads well on a phone, and the steps match the client's interface. The last item is checked by hand on a device.",[1426,7144,1578],{"id":1577},[1444,7146,7147,7150,7153,7156],{},[1447,7148,7149],{},"Five platforms are covered by five pages, each independent of the others.",[1447,7151,7152],{},"The pages are designed to be opened from the bot inside Telegram, without switching to a browser.",[1447,7154,7155],{},"The project needs no build step, Docker or environment variables: editing a page means editing one HTML file.",[1447,7157,7158],{},"The repository is open, and the live version is published on GitHub Pages.",[1426,7160,1602],{"id":1601},[1444,7162,7163,7169,7175],{},[1447,7164,7165,7168],{},[1450,7166,7167],{},"HTML5, CSS, JavaScript",": pages with no build step and no dependencies; styles and scripts are embedded in each file.",[1447,7170,7171,7174],{},[1450,7172,7173],{},"Telegram Web Apps API",": full-screen expansion, the close button and the Telegram theme colors.",[1447,7176,7177,7180],{},[1450,7178,7179],{},"GitHub Pages",": static hosting over the HTTPS that a Telegram Mini App requires.",[1426,7182,1647],{"id":1646},[21,7184,7185],{},"In production, an open project of our own. There are no explicit versions: the latest log entry is dated July 28, 2026, when the files were updated through the GitHub web interface. The first upload was on January 21, 2026, and on January 23 the Android, iOS, Linux and macOS pages were expanded and reworked.",[21,7187,7188],{},"Known limitations per the README: the guides are tied to the client menu item names and go stale when the clients update; the bot's address is hardcoded in each of the five files and there is no shared template; there are no automated tests or link checks. The pages also carry copy protection (blocked text selection, context menu and inspector hotkeys): the README calls it decorative, says it hurts accessibility and doesn't protect the content, and suggests removing it or keeping it deliberately.",{"title":28,"searchDepth":29,"depth":29,"links":7190},[7191,7192,7193,7200,7201,7202],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":7194},[7195,7196,7197,7198,7199],{"id":7076,"depth":1661,"text":7077},{"id":7083,"depth":1661,"text":7084},{"id":7105,"depth":1661,"text":7106},{"id":7112,"depth":1661,"text":7113},{"id":7134,"depth":1661,"text":7135},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-07-28","https:\u002F\u002Ftoshatoshiba.github.io\u002Finstructions\u002F","📖",[7207,7210,7213,7216,7219,7222],{"q":7208,"a":7209},"How do I set up a VPN step by step if I've never done it?","That is what the guides are for: one page per platform. Each leads from installing the app to switching the VPN on and ends with a “Not working? Check this” block. A link to the service's bot, where the subscription is issued, appears at the start and at the end of every page.",{"q":7211,"a":7212},"Which platforms and clients are covered?","Five pages: Android, iPhone and iPad, Windows, macOS and Linux. The guides for Android, Windows and Linux cover the NekoBox client, and the one for macOS covers V2Box. A separate page describes iPhone and iPad.",{"q":7214,"a":7215},"What is a Telegram Mini App and why put a guide in one?","A Mini App is a web page that opens right inside Telegram, without switching to a browser. A person taps a button in the bot, sees the guide full-screen and closes it with the “Done, close” button. The page takes its colors from the Telegram theme, so it doesn't clash with the interface.",{"q":7217,"a":7218},"Do you need a server or a build step to host pages like these?","No. Each page is a single self-contained HTML file with embedded styles and scripts, with no build step and no external libraries, apart from the Telegram Web Apps script loaded from telegram.org. The pages can be hosted on any static host over HTTPS, which a Telegram Mini App requires.",{"q":7220,"a":7221},"What if the steps don't match the client's interface?","The guides are tied to specific clients and the names of their menu items, so they can go out of date when a client updates and need a manual check on a device. There are no tests or link checks in the project, and the pages are edited by hand.",{"q":7223,"a":7224},"Can these pages be reused for our own VPN service?","The scheme carries over: it is plain HTML with no dependencies. But the current pages have the address of one service's bot hardcoded, so all five files need editing for another service. There is no shared template yet, and styles and scripts are duplicated. The repository doesn't specify a license.",[7226],{"label":7227,"url":7228},"GitHub","https:\u002F\u002Fgithub.com\u002FToshaToshiba\u002Finstructions",{},60,"\u002Fen\u002Fkeysy\u002Finstrukcii-vpn",[],[7234,7237,7239],{"value":7235,"label":7236},"5 platforms","Android, iPhone and iPad, Windows, macOS and Linux, each with its own page",{"value":4974,"label":7238},"plain HTML, CSS and JavaScript with no build step, Docker or .env file",{"value":7240,"label":7241},"1 file","per platform: styles and scripts are embedded, so a page opens immediately",{"title":7036,"description":28},[7244,7245,7246,7173,7179],"HTML5","CSS","JavaScript","en\u002Fkeysy\u002Finstrukcii-vpn","open pages for a Telegram Mini App: five platforms, no build step, no dependencies","Step-by-step VPN setup guides for Android, iPhone and iPad, Windows, macOS and Linux as static pages. They open inside a Telegram Mini App from a bot, so a beginner doesn't have to contact support.","Ye9i0y6t4qE5tRrIk9O0bPP7d3nVgd_yzKJBuSnSnzg",{"id":7252,"title":7253,"body":7254,"client":28,"cover":28,"date":7460,"demoUrl":28,"description":28,"direction":1193,"emoji":7461,"extension":33,"faq":7462,"featured":94,"links":7481,"meta":7482,"navigation":79,"nda":79,"order":7230,"own":94,"path":7483,"related":7484,"results":7485,"seo":7495,"shot":28,"shotAlt":28,"stack":7496,"status":4540,"stem":7500,"subtitle":7501,"summary":7502,"__hash__":7503},"cases_en\u002Fen\u002Fkeysy\u002Fkompyuternyi-klub.md","Loyalty card for a gaming club in Telegram",{"type":18,"value":7255,"toc":7445},[7256,7258,7261,7264,7266,7269,7272,7287,7290,7304,7307,7330,7332,7336,7343,7347,7350,7354,7367,7371,7374,7378,7381,7385,7388,7390,7409,7411,7437,7439],[1426,7257,1429],{"id":1428},[21,7259,7260],{},"A gaming club ran its loyalty program on paper cards. Paper yields no numbers: how many players there are, how often they come, who is due a reward and what each staff member has credited.",[21,7262,7263],{},"The club needs a digital replacement: the administrator scans the client's QR code and credits a visit with one tap, the bot itself tracks when a reward is due (every sixth night or a free hookah) and keeps a log of staff actions. The owners need statistics and a daily copy of the database.",[1426,7265,1439],{"id":1438},[21,7267,7268],{},"A Telegram bot replaces paper cards with a digital loyalty system: visit tracking (“nights”), reward issuing, a player database and a staff action log. What is implemented.",[21,7270,7271],{},"For the client:",[1444,7273,7274,7281,7284],{},[1447,7275,7276,7277,7280],{},"registration with the ",[1635,7278,7279],{},"\u002Fstart"," command;",[1447,7282,7283],{},"“My card (QR)”: the night balance, a six-cell progress bar, the reward status and a personal QR code;",[1447,7285,7286],{},"a notification when a reward becomes available: a free night or a hookah.",[21,7288,7289],{},"For the administrator:",[1444,7291,7292,7295,7298,7301],{},[1447,7293,7294],{},"opening a client's profile from the QR code; the bot refuses everyone else;",[1447,7296,7297],{},"player search by phone number, and a list of all players with nickname, phone, nights and rewards spent;",[1447,7299,7300],{},"profile buttons: “Add night”, and when a reward is available, “Redeem: FREE NIGHT” and “Redeem: HOOKAH”;",[1447,7302,7303],{},"protection against inflation: a 70-second pause between credits and a ban on crediting yourself.",[21,7305,7306],{},"For the main administrator:",[1444,7308,7309,7312,7315,7325],{},[1447,7310,7311],{},"club statistics: players, admins, total nights, rewards issued;",[1447,7313,7314],{},"a log of the last 30 actions;",[1447,7316,7317,7318,2055,7321,7324],{},"admin list management with the ",[1635,7319,7320],{},"\u002Fadd_admin",[1635,7322,7323],{},"\u002Fdel_admin"," commands;",[1447,7326,7327,7328,3340],{},"several main administrators, a daily database copy and a hidden ",[1635,7329,3289],{},[1426,7331,1517],{"id":1516},[1519,7333,7335],{"id":7334},"the-clients-qr-card-qrcode-and-pillow","The client's QR card: qrcode and Pillow",[21,7337,7338,7339,7342],{},"The qrcode and Pillow libraries produce the QR image. The code contains a link that opens the bot with a ",[1635,7340,7341],{},"scan_\u003Cclient number>"," parameter. When the administrator scans the QR with the phone camera, Telegram opens the bot with exactly that parameter and the bot shows the client's profile. For other users the link gives nothing: the bot refuses.",[1519,7344,7346],{"id":7345},"the-reward-rule-every-sixth-night","The reward rule: every sixth night",[21,7348,7349],{},"The threshold is set by a constant: six nights. When the counter reaches six, the client is flagged as having a reward available and gets a notification. Redeeming reduces the balance by six and increases the counter of rewards spent. The reward type, night or hookah, is written to the log.",[1519,7351,7353],{"id":7352},"anti-fraud-a-pause-and-no-crediting-yourself","Anti-fraud: a pause and no crediting yourself",[21,7355,7356,7357,2051,7360,2051,7363,7366],{},"Abuse protection has two layers. First, 70 seconds must pass between credits from one administrator. Second, a regular administrator cannot credit a night to themselves. All credits and redemptions (",[1635,7358,7359],{},"ADD_NIGHT",[1635,7361,7362],{},"SPENT_NIGHT",[1635,7364,7365],{},"SPENT_HOOKAH",") are written to a log that the main administrator views from the bot.",[1519,7368,7370],{"id":7369},"sqlite-and-aiosqlite-a-database-with-no-separate-server","SQLite and aiosqlite: a database with no separate server",[21,7372,7373],{},"SQLite is a database in a single file and needs no separate server. The aiosqlite driver works with it asynchronously, so queries do not block the bot. The tables for players, administrators and the log are created on first start. For a small club this is enough: a backup is simply a file.",[1519,7375,7377],{"id":7376},"three-access-levels-client-administrator-main-administrator","Three access levels: client, administrator, main administrator",[21,7379,7380],{},"Rights are checked against the administrator list in the database and the main administrator IDs from the settings. A client sees only their own card. An administrator opens profiles by QR or by phone number, credits nights, redeems rewards and views the player list; regular administrators have access to that list too. The main administrator additionally sees the statistics and the log and manages the set of administrators. A user without rights who opens a QR link gets a refusal.",[1519,7382,7384],{"id":7383},"daily-database-copy","Daily database copy",[21,7386,7387],{},"A background job sends the database file to all main administrators in Telegram once a day. There can be several main administrators: their IDs are listed, separated by commas, in an environment setting.",[1426,7389,1578],{"id":1577},[1444,7391,7392,7395,7398,7401,7404,7407],{},[1447,7393,7394],{},"Paper cards are replaced by a QR card in Telegram: the client needs only a phone.",[1447,7396,7397],{},"The reward arrives automatically on the sixth night, and the client gets a notification.",[1447,7399,7400],{},"Crediting takes one tap, limited by a 70-second pause and a ban on crediting yourself.",[1447,7402,7403],{},"All staff actions are logged, and club statistics are available to the main administrator.",[1447,7405,7406],{},"A daily copy of the database reaches the main administrators as a file.",[1447,7408,3376],{},[1426,7410,1602],{"id":1601},[1444,7412,7413,7419,7425,7431],{},[1447,7414,7415,7418],{},[1450,7416,7417],{},"Python 3.10+ and aiogram 3"," — an asynchronous Telegram bot with menus, roles and dialog states.",[1447,7420,7421,7424],{},[1450,7422,7423],{},"SQLite and aiosqlite"," — storing players, administrators and the log in one file.",[1447,7426,7427,7430],{},[1450,7428,7429],{},"qrcode and Pillow"," — generating the client's personal QR code.",[1447,7432,7433,7436],{},[1450,7434,7435],{},"python-dotenv"," — the bot token and main administrator IDs from the environment.",[1426,7438,1647],{"id":1646},[21,7440,7441,7442,7444],{},"Version 2.2.1 of 11 January 2026. Since then the repository has gained a version constant, the hidden ",[1635,7443,3289],{}," command and documentation; these changes are not yet cut as a release. Known limitations from the README: the pause between credits and dialog states are kept in memory and reset on restart; the reward type is recorded only in the log; the check that a reward is available is done at the button level, not in the database; there are no Docker files or automated tests; time in the database and log is stored in UTC. Other details about the client are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":7446},[7447,7448,7449,7457,7458,7459],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":7450},[7451,7452,7453,7454,7455,7456],{"id":7334,"depth":1661,"text":7335},{"id":7345,"depth":1661,"text":7346},{"id":7352,"depth":1661,"text":7353},{"id":7369,"depth":1661,"text":7370},{"id":7376,"depth":1661,"text":7377},{"id":7383,"depth":1661,"text":7384},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-01-11","🎮",[7463,7466,7469,7472,7475,7478],{"q":7464,"a":7465},"How do I build a loyalty card for customers without a mobile app?","The client opens the bot in Telegram and taps “My card (QR)”. The bot shows the night balance, a six-cell progress bar and a personal QR code. No separate app or plastic card is needed: Telegram on the phone is enough.",{"q":7467,"a":7468},"How does an administrator record a visit?","The administrator scans the client's QR code or looks the player up by phone number, opens the profile and taps “Add night”. The bot increases the balance, and when six nights are reached it sends the client a notification about the reward.",{"q":7470,"a":7471},"What is the reward and how is it redeemed?","For every sixth night the client gets a free night or a free hookah. The profile shows “Redeem: FREE NIGHT” and “Redeem: HOOKAH” buttons. Redeeming reduces the balance by six nights, and the reward type is written to the log.",{"q":7473,"a":7474},"How do you prevent staff from inflating visit counts?","There is a 70-second pause between credits from one administrator, and a regular administrator cannot credit a night to themselves. Every credit and redemption goes into a staff action log.",{"q":7476,"a":7477},"Where is customer data stored and what does the owner see?","Data lives in a SQLite database next to the bot. Main administrators get club statistics (players, nights and rewards issued), a log of the last 30 actions, admin list management and a daily copy of the database as a file in Telegram.",{"q":7479,"a":7480},"Can a loyalty system like this be built for another business?","The mechanic of visits by QR and a reward at a threshold suits any business with repeat visits. The threshold and reward types are set in the code, so the bot is configured separately for another venue.",[],{},"\u002Fen\u002Fkeysy\u002Fkompyuternyi-klub",[],[7486,7489,7492],{"value":7487,"label":7488},"6 nights","to a reward: a free night or a hookah, the bot tracks the threshold itself",{"value":7490,"label":7491},"70 seconds","pause between credits from one administrator to prevent inflation",{"value":7493,"label":7494},"Once a day","a copy of the database goes to the main administrators as a file in Telegram",{"title":7253,"description":28},[293,294,1366,7497,7498,7499],"aiosqlite","qrcode","Pillow","en\u002Fkeysy\u002Fkompyuternyi-klub","a QR code instead of a paper card: visit tracking and a reward for the sixth night","A Telegram loyalty bot for a gaming club that replaces paper cards: the client shows a QR code, the admin logs a night in one tap, and on the sixth night the bot unlocks a reward. Python, aiogram 3, SQLite.","_oyPYDd9tdPoda1HUowth9ypH2nhjG4trvgYsRdtIlk",{"id":7505,"title":7506,"body":7507,"client":28,"cover":28,"date":7811,"demoUrl":28,"description":28,"direction":1193,"emoji":7812,"extension":33,"faq":7813,"featured":94,"links":7832,"meta":7833,"navigation":79,"nda":94,"order":7230,"own":79,"path":7834,"related":7835,"results":7836,"seo":7846,"shot":28,"shotAlt":28,"stack":7847,"status":1713,"stem":7848,"subtitle":7849,"summary":7850,"__hash__":7851},"cases_en\u002Fen\u002Fkeysy\u002Fstream-bot.md","Streaming service access bot: code login from Telegram",{"type":18,"value":7508,"toc":7795},[7509,7511,7514,7517,7519,7522,7527,7596,7602,7652,7655,7657,7661,7670,7674,7677,7681,7684,7688,7691,7695,7698,7702,7711,7715,7722,7724,7744,7746,7775,7777,7792],[1426,7510,1429],{"id":1428},[21,7512,7513],{},"A service with chat, voice and streaming for a private team (this is Peregovorka, also called ToshaStream, a separate case) had registration open to anyone who was sent the link. The owner learned about a new account after the fact. Requests got lost, and even small things like changing a name or a password meant going to the owner or opening the web admin panel.",[21,7515,7516],{},"Every service account had to be tied to a real person in Telegram. For the participant: password-free login with a one-time code, and changing the password or name without the owner's help. For the owner: registration requests to approve, a list of accounts and notifications about service events. The original brief is kept in the repository as a separate specification document.",[1426,7518,1439],{"id":1438},[21,7520,7521],{},"Stream Bot is our own Telegram bot for managing accounts. Implemented in version 1.2.0:",[21,7523,7524],{},[1450,7525,7526],{},"For the participant",[1444,7528,7529,7539,7548,7561,7570,7579,7588],{},[1447,7530,7531,7534,7535,7538],{},[1450,7532,7533],{},"Account linking."," ",[1635,7536,7537],{},"\u002Flink",": the bot asks for a login (matched by login and by display name) and issues a code to enter on the site.",[1447,7540,7541,7534,7544,7547],{},[1450,7542,7543],{},"Password-free login.",[1635,7545,7546],{},"\u002Flogin"," sends a one-time code for logging in to the site.",[1447,7549,7550,7534,7553,7556,7557,7560],{},[1450,7551,7552],{},"Profile and name.",[1635,7554,7555],{},"\u002Fme"," shows the profile, ",[1635,7558,7559],{},"\u002Fname"," changes the display name; without an argument the bot asks for the name in a separate message.",[1447,7562,7563,7534,7566,7569],{},[1450,7564,7565],{},"Avatar.",[1635,7567,7568],{},"\u002Favatar"," takes the service avatar from the Telegram profile photo.",[1447,7571,7572,7534,7575,7578],{},[1450,7573,7574],{},"Password.",[1635,7576,7577],{},"\u002Fpassword"," issues a new web login password; the bot deletes that message after 120 seconds.",[1447,7580,7581,7534,7584,7587],{},[1450,7582,7583],{},"Log out everywhere.",[1635,7585,7586],{},"\u002Flogout"," closes all web sessions.",[1447,7589,7590,7534,7592,7595],{},[1450,7591,4587],{},[1635,7593,7594],{},"\u002Fnotify"," holds personal settings for stream alerts and owner broadcasts.",[21,7597,7598,7601],{},[1450,7599,7600],{},"For the owner"," (only the configured Telegram ID):",[1444,7603,7604,7629,7635,7641,7647],{},[1447,7605,7606,7609,7610,7613,7614,2051,7617,2051,7620,2051,7623,2051,7626,1638],{},[1635,7607,7608],{},"\u002Fusers"," lists accounts page by page; ",[1635,7611,7612],{},"\u002Fpending"," shows requests with Approve and Deny buttons; ",[1635,7615,7616],{},"\u002Fapprove",[1635,7618,7619],{},"\u002Fdeny",[1635,7621,7622],{},"\u002Frole",[1635,7624,7625],{},"\u002Fkick",[1635,7627,7628],{},"\u002Fsessions \u003Clogin>",[1447,7630,7631,7634],{},[1635,7632,7633],{},"\u002Fstats"," shows who is on the service now: viewers and voice channels.",[1447,7636,7637,7640],{},[1635,7638,7639],{},"\u002Fbroadcast"," sends any message (text, image, voice) to everyone who has not opted out, with a confirmation step.",[1447,7642,7643,7646],{},[1635,7644,7645],{},"\u002Fannounce"," is a global switch for stream-start alerts.",[1447,7648,7649,7651],{},[1635,7650,3289],{}," is a hidden command that shows the version of the running instance.",[21,7653,7654],{},"A permanent button menu is built by role: an unlinked person sees a single linking button, and the owner gets an extra row with accounts, requests and who is online.",[1426,7656,1517],{"id":1516},[1519,7658,7660],{"id":7659},"login-to-a-site-with-a-code-from-telegram","Login to a site with a code from Telegram",[21,7662,7663,7664,7666,7667,7669],{},"An account is linked to Telegram once, through ",[1635,7665,7537],{},". After that a login code is issued by ",[1635,7668,7546],{},", and the bot sends confirmations of linking and of login to the person. The code is a one-time string of characters typed on the site instead of a password. Login with a username and password remains a separate way in, so stopping the bot does not lock anyone out.",[1519,7671,7673],{"id":7672},"a-bot-with-no-database-an-internal-api-with-a-shared-secret","A bot with no database: an internal API with a shared secret",[21,7675,7676],{},"The bot stores no accounts and never reads the service's database directly. It calls the service's internal API over HTTP (an API is a set of addresses through which one program asks another to do something), and every request carries a shared secret in a header. There is a single owner of the data, so the bot and the service cannot drift apart. Even notification settings are kept by the service: its settings table holds the fields for stream alerts and broadcasts.",[1519,7678,7680],{"id":7679},"service-events-polling-the-queue-every-20-seconds","Service events: polling the queue every 20 seconds",[21,7682,7683],{},"Every 20 seconds the bot asks the service for its event queue. The owner receives registration requests, logins from a new address, a streak of failed logins, and the start and end of a stream. A person receives confirmation of linking and of code login. If the tunnel drops, the bot replies that the chat service is not responding, and after recovery it reads the queue from the previous cursor. At startup the bot skips the queue's tail so that it does not resend old events.",[1519,7685,7687],{"id":7686},"an-frp-tunnel-between-the-service-server-and-the-bot","An frp tunnel between the service server and the bot",[21,7689,7690],{},"The service runs on a server in Russia, from which Telegram is unreachable. So the bot runs on a foreign machine in a separate Docker Compose project. The link is built with frp, a reverse tunnel tool: the client on the service side makes an outbound connection to the server on the bot side and exposes the internal API over TLS. The control port on the bot side is open only to the address of the service server. An earlier variant, a reverse SSH tunnel, is kept in the repository as a fallback.",[1519,7692,7694],{"id":7693},"long-polling-a-bot-without-an-open-port","Long polling: a bot without an open port",[21,7696,7697],{},"The bot receives messages by long polling: it holds a request to Telegram itself and waits for the answer. So it needs no public address and no webhook. Dialog states (for example, \"the bot is waiting for a name\") are kept in memory, and a restart cuts off unfinished dialogs.",[1519,7699,7701],{"id":7700},"hidden-owner-commands","Hidden owner commands",[21,7703,7704,7705,7707,7708,7710],{},"Owner commands are not shown in the menu. For a foreign Telegram ID, ",[1635,7706,7608],{}," or ",[1635,7709,3289],{}," get the reply that a nonexistent command would get. Permissions come from a variable that holds the owner's ID: without it, owner commands and notifications are switched off.",[1519,7712,7714],{"id":7713},"what-else-is-in-the-repository","What else is in the repository",[21,7716,7717,7718,7721],{},"Besides the bot, the ",[1635,7719,7720],{},"deploy\u002F"," folder holds patches and new files for the chat service (Go and JS) used to extend it on the production server: emotes, replies and reactions, history search, message deletion, attachments, screen sharing, a connection-quality monitor, stream recordings, a watchdog and nightly backups. The tunnel configuration is there too. The full source of the service itself is not in the repository.",[1426,7723,1578],{"id":1577},[1444,7725,7726,7729,7732,7735,7738,7741],{},[1447,7727,7728],{},"Each account is tied to a real person in Telegram, and password-free login works with a one-time code.",[1447,7730,7731],{},"Registration requests reach the owner's chat and are resolved with buttons or commands.",[1447,7733,7734],{},"Changing a name or password, logging out of all sessions and setting roles are done from the chat, without the web admin.",[1447,7736,7737],{},"The bot has no database of its own: accounts live in one place, in the service.",[1447,7739,7740],{},"Stopping the bot does not stop the site, chat, voice or stream.",[1447,7742,7743],{},"The bot runs in Docker as a non-root user, and its version is written to the log at startup.",[1426,7745,1602],{"id":1601},[1444,7747,7748,7754,7759,7765,7770],{},[1447,7749,7750,7753],{},[1450,7751,7752],{},"Python 3.12 and aiogram 3.15"," — an asynchronous Telegram bot, with dialogs built on finite state machines.",[1447,7755,7756,7758],{},[1450,7757,3711],{}," — the client for the service's internal API and long polling.",[1447,7760,7761,7764],{},[1450,7762,7763],{},"The service's internal API with a shared secret"," — the single source of account data.",[1447,7766,7767,7769],{},[1450,7768,663],{}," — a tunnel between the service server in Russia and the bot's foreign server, over TLS.",[1447,7771,7772,7774],{},[1450,7773,120],{}," — the python:3.12-slim image, a non-root process, TZ=Europe\u002FMoscow.",[1426,7776,1647],{"id":1646},[21,7778,7779,7780,2051,7782,2051,7784,7786,7787,7789,7790,1638],{},"Our own product, part of the Peregovorka (ToshaStream) setup. The current version, 1.2.0 of 10 September 2026, added ",[1635,7781,7594],{},[1635,7783,7639],{},[1635,7785,7645],{}," and linking by display name. Since 14 September the code also contains, without a version bump, the ",[1635,7788,7568],{}," command and improvements to the chat service itself in ",[1635,7791,7720],{},[21,7793,7794],{},"Limitations: there are no automated tests, so checking is manual, and dialog states do not survive a restart. Not established from the code: whether mandatory Telegram linking for joining voice is enabled on the production server. No separate roadmap items are listed.",{"title":28,"searchDepth":29,"depth":29,"links":7796},[7797,7798,7799,7808,7809,7810],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":7800},[7801,7802,7803,7804,7805,7806,7807],{"id":7659,"depth":1661,"text":7660},{"id":7672,"depth":1661,"text":7673},{"id":7679,"depth":1661,"text":7680},{"id":7686,"depth":1661,"text":7687},{"id":7693,"depth":1661,"text":7694},{"id":7700,"depth":1661,"text":7701},{"id":7713,"depth":1661,"text":7714},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-10","🎟",[7814,7817,7820,7823,7826,7829],{"q":7815,"a":7816},"How do I let users log in to a website with a code from Telegram, without a password?","A participant links the account with \u002Flink: the bot asks for a login or display name and issues a code to enter on the site. After that, \u002Flogin sends a one-time code for password-free login. Confirmation of linking and of login arrives in the person's chat.",{"q":7818,"a":7819},"How can an owner approve new streaming-service accounts from Telegram?","A registration request lands in the owner's chat, and \u002Fpending lists all waiting requests with Approve and Deny buttons. The same can be done with \u002Fapprove and \u002Fdeny. The owner also gets alerts about a login from a new address, a streak of failed logins, and the start and end of a stream.",{"q":7821,"a":7822},"Where does the bot store accounts and passwords?","Nowhere: the bot has no database of its own. Every action goes over HTTP to the service's internal API with a shared secret in a header. Even notification settings are kept by the service, not by the bot.",{"q":7824,"a":7825},"What can an ordinary user do, and what is owner-only?","A participant manages their own account: \u002Flink, \u002Flogin, \u002Fme, \u002Fname, \u002Favatar, \u002Fpassword, \u002Flogout, \u002Fnotify. The owner commands (\u002Fusers, \u002Fpending, \u002Fapprove, \u002Fdeny, \u002Frole, \u002Fkick, \u002Fsessions, \u002Fstats, \u002Fbroadcast, \u002Fannounce) work only for the configured Telegram ID; for anyone else the bot replies as to a typo.",{"q":7827,"a":7828},"What happens to the site and voice chat if the bot stops?","The site, chat, voice and stream keep running. If the tunnel drops, the bot answers that the chat service is not responding, and once it recovers the bot reads the event queue from where it left off. Unfinished dialogs are cut off when the bot restarts, because their state is kept in memory.",{"q":7830,"a":7831},"How is the bot connected to the server if Telegram is unreachable from it?","The service runs on a server in Russia, from which Telegram is unreachable, so the bot runs on a foreign machine. The service itself makes an outbound connection and exposes its internal API to the bot through an frp tunnel over TLS. The bot needs no open port or public address: it polls Telegram itself (long polling).",[],{},"\u002Fen\u002Fkeysy\u002Fstream-bot",[707],[7837,7840,7843],{"value":7838,"label":7839},"No own database","the bot calls the service's internal API, so accounts live in one place",{"value":7841,"label":7842},"11 commands","open to every participant: linking, code login, profile, name, avatar, password, logout, notifications",{"value":7844,"label":7845},"120 seconds","after this time the bot deletes the message with an issued password from the chat",{"title":7506,"description":28},[293,294,3711,663,120],"en\u002Fkeysy\u002Fstream-bot","our own bot for Peregovorka (ToshaStream): code login, approvals, roles","A Telegram bot for managing streaming-service accounts: Telegram linking, one-time-code login, request approval and roles from the chat. Python, aiogram 3, no database of its own, frp tunnel to the service.","rjArVJbOSvxgBHW7oy9RgmBrFo9tfCkqYgSVj34shss",{"id":7853,"title":7854,"body":7855,"client":28,"cover":28,"date":8071,"demoUrl":28,"description":28,"direction":1193,"emoji":8072,"extension":33,"faq":8073,"featured":94,"links":8092,"meta":8097,"navigation":79,"nda":94,"order":8098,"own":79,"path":8099,"related":8100,"results":8101,"seo":8110,"shot":28,"shotAlt":28,"stack":8111,"status":1713,"stem":8112,"subtitle":8113,"summary":8114,"__hash__":8115},"cases_en\u002Fen\u002Fkeysy\u002Fsavebot.md","Downloader Bot: save TikTok and YouTube videos via Telegram",{"type":18,"value":7856,"toc":8056},[7857,7859,7862,7865,7867,7870,7929,7932,7934,7938,7941,7945,7948,7952,7955,7959,7962,7966,7969,7973,7976,7978,8001,8003,8048,8050,8053],[1426,7858,1429],{"id":1428},[21,7860,7861],{},"To keep a video you liked, you normally have to go to a download site: sit through ads, dodge redirects, save the file to your phone's downloads and then dig it out of a file manager to forward it to a friend. TikTok adds a watermark on top. Online tools break regularly, demand payment or funnel traffic into advertising.",[21,7863,7864],{},"What was needed was a bot for downloading videos from TikTok, YouTube and Instagram that lives where the conversation already is: drop a link into the chat and get the file back in the same chat. It can be forwarded to anyone immediately, because it is already inside Telegram.",[1426,7866,1439],{"id":1438},[21,7868,7869],{},"Downloader Bot is an in-house Telegram bot, version 2.6.3. Implemented:",[1444,7871,7872,7878,7884,7890,7896,7902,7908,7914,7924],{},[1447,7873,7874,7877],{},[1450,7875,7876],{},"Platforms."," TikTok without the watermark, YouTube and Shorts, Instagram Reels and posts, TikTok photo slideshows (stitched into mp4), VK Video, X (Twitter), Reddit, Pinterest, Twitch, SoundCloud, Vimeo, Coub, Yandex Video. Separately, for admins only, Hi-Res music from Qobuz, Deezer and Tidal through the ToshaMusic engine.",[1447,7879,7880,7883],{},[1450,7881,7882],{},"Quality choice."," From 240p to 4K; the file size is shown on the button in advance.",[1447,7885,7886,7889],{},[1450,7887,7888],{},"Audio extraction."," MP3 with cover art and metadata; the bitrate follows the best audio track of the video, from 192 to 320 kbps.",[1447,7891,7892,7895],{},[1450,7893,7894],{},"Instant repeats."," Anything already downloaded is served by file_id without re-downloading.",[1447,7897,7898,7901],{},[1450,7899,7900],{},"Live progress."," A progress bar, speed and time remaining.",[1447,7903,7904,7907],{},[1450,7905,7906],{},"Getting past the 50 MB limit."," Automatic ffmpeg compression or splitting into parts; up to 2000 MB with a local Bot API.",[1447,7909,7910,7913],{},[1450,7911,7912],{},"Cookies."," Age-restricted and private videos, YouTube's \"Sign in to confirm\" check; the file is uploaded through the admin panel.",[1447,7915,7916,7919,7920,7923],{},[1450,7917,7918],{},"Default format."," The ",[1635,7921,7922],{},"\u002Fformat"," command: download immediately, no questions asked.",[1447,7925,7926,7928],{},[1450,7927,5306],{}," Summary, analytics by platform and by day, users, moderation, broadcasts with a delivery report, cookies, settings on the fly, logs, maintenance.",[21,7930,7931],{},"The link does not have to be a separate message: the bot finds it inside any text.",[1426,7933,1517],{"id":1516},[1519,7935,7937],{"id":7936},"yt-dlp-as-the-universal-engine","yt-dlp as the universal engine",[21,7939,7940],{},"yt-dlp is an open-source library that can fetch video and audio from hundreds of sites. The bot probes a link once, gets the list of formats with sizes and reuses that result for the download, saving a network request. New platforms are added by recognising the domain: Vimeo and Coub went the same route as Reddit or Twitch, with no dedicated code. yt-dlp and ffmpeg run in separate threads and processes, so the bot keeps answering other users while someone's video is downloading; a semaphore caps the number of concurrent downloads.",[1519,7942,7944],{"id":7943},"tiktok-without-the-watermark","TikTok without the watermark",[21,7946,7947],{},"TikTok is fetched through a direct API that serves the video without the embedded mark. TikTok photo posts (slideshows) are something yt-dlp cannot handle at all: the bot itself stitches the images and the post's music into one mp4 with ffmpeg, giving each image an equal share of the total duration and normalising different aspect ratios to 1080×1920.",[1519,7949,7951],{"id":7950},"po-tokens-and-youtube-link-signing","PO tokens and YouTube link signing",[21,7953,7954],{},"YouTube protects its media links with two mechanisms, neither of which belongs to the bot's code. Link signatures are computed by YouTube's own obfuscated JavaScript, so a JS runtime is required: Deno, built into the image. The second mechanism is the PO token, a session token tied to a browser; it is issued by a separate service that runs alongside the bot in docker-compose and is not exposed externally. Without Deno, video fails; without tokens, audio fails, and in both cases metadata still loads fine, so the error only appears after the quality button is pressed. YouTube changes its protection regularly, so the first step on failures is to update yt-dlp.",[1519,7956,7958],{"id":7957},"getting-past-the-50-mb-limit-local-bot-api","Getting past the 50 MB limit: local Bot API",[21,7960,7961],{},"Telegram's cloud Bot API does not let a bot send files over 50 MB. The bot shows the size of every quality before downloading, flags what is over the limit but compressible and what cannot be saved at all, aborts a download that could never be sent, and recompresses through ffmpeg, lowering the resolution when needed. If the quality would become unacceptable, the bot offers to split the file into parts. For files up to 2000 MB, docker-compose describes a local Bot API server (the bigfiles profile): in local mode it returns a path on disk instead of a URL, and the bot gets access to the same files.",[1519,7963,7965],{"id":7964},"file_id-cache","file_id cache",[21,7967,7968],{},"Every file uploaded to Telegram once receives a file_id identifier. The bot stores it in SQLite and, on a repeated link, forwards the file instantly with no traffic. The cache lifetime is configurable, 30 days by default.",[1519,7970,7972],{"id":7971},"cookies-and-the-admin-panel","Cookies and the admin panel",[21,7974,7975],{},"Cookies are needed for age-restricted and private videos and for YouTube's \"Sign in to confirm\" check. A cookies.txt file is uploaded through the admin panel; the bot validates the Netscape format and shows how fresh each file is. The admin panel runs on inline buttons under the message: summary, analytics by platform and by day, top errors, users with search, bans with a reason, broadcasts with preview and report, limits and maintenance mode, logs, server status, cache cleanup, database export. Users who blocked the bot are removed from the database automatically after a broadcast.",[1426,7977,1578],{"id":1577},[1444,7979,7980,7983,7986,7989,7992,7995,7998],{},[1447,7981,7982],{},"Link in, file out: no download sites, ads or watermarks.",[1447,7984,7985],{},"12 platforms plus Hi-Res music for admins.",[1447,7987,7988],{},"Quality from 240p to 4K, MP3 with cover art, file size known before downloading.",[1447,7990,7991],{},"Files over 50 MB are compressed, split, or sent whole through the local Bot API up to 2000 MB.",[1447,7993,7994],{},"Repeated links are served from the cache without re-downloading.",[1447,7996,7997],{},"A stalled link is cut off by a timeout with a clear error rather than an endless \"Reading link\".",[1447,7999,8000],{},"The fast test suites (unit tests and routing with a fake Telegram) need no network; a separate flag runs a live download from YouTube.",[1426,8002,1602],{"id":1601},[1444,8004,8005,8010,8016,8022,8027,8032,8037,8043],{},[1447,8006,8007,8009],{},[1450,8008,5379],{},": an asynchronous bot, inline keyboards, anti-flood and maintenance mode in middleware.",[1447,8011,8012,8015],{},[1450,8013,8014],{},"yt-dlp",": the universal download engine; the bgutil-ytdlp-pot-provider plugin for YouTube PO tokens.",[1447,8017,8018,8021],{},[1450,8019,8020],{},"Deno",": the JS runtime for YouTube link signing, built into the image.",[1447,8023,8024,8026],{},[1450,8025,312],{},": merging, MP3, compression, splitting, thumbnails, slideshow assembly.",[1447,8028,8029,8031],{},[1450,8030,5397],{},": users, log, file_id cache, settings.",[1447,8033,8034,8036],{},[1450,8035,3711],{},": client for the TikTok API and the Hi-Res engine.",[1447,8038,8039,8042],{},[1450,8040,8041],{},"Local Bot API (telegram-bot-api)",": an optional profile for files up to 2000 MB.",[1447,8044,8045,8047],{},[1450,8046,650],{},": the bot and the PO token service; the bigfiles, hires and ruproxy profiles are enabled by flag.",[1426,8049,1647],{"id":1646},[21,8051,8052],{},"In-house product, in production. The current version is 2.6.3 from 27 September 2026. September additions: TikTok photo slideshows (2.3.0), a Hi-Res music section through the separate ToshaMusic service (2.4.0), honest MP3 bitrate based on the source track (2.4.1), accurate YouTube size estimates without HLS duplicates (2.4.2), Vimeo and Coub (2.5.0), the \"Made in DevUnit Lab\" signature (2.6.0), a metadata probe timeout (2.6.1), Yandex Video through a SOCKS5 tunnel with a Russian exit (2.6.2) and cookies for its anti-bot check (2.6.3).",[21,8054,8055],{},"Limitations: YouTube changes its protection regularly, so yt-dlp is updated first on failures; Instagram without cookies often returns an empty response; there are no guarantees for Yandex Video. Tests are run manually, there is no CI.",{"title":28,"searchDepth":29,"depth":29,"links":8057},[8058,8059,8060,8068,8069,8070],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":8061},[8062,8063,8064,8065,8066,8067],{"id":7936,"depth":1661,"text":7937},{"id":7943,"depth":1661,"text":7944},{"id":7950,"depth":1661,"text":7951},{"id":7957,"depth":1661,"text":7958},{"id":7964,"depth":1661,"text":7965},{"id":7971,"depth":1661,"text":7972},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-09-27","🎬",[8074,8077,8080,8083,8086,8089],{"q":8075,"a":8076},"How do I download a TikTok video without the watermark through Telegram?","Send the link to the bot; it can be inside any text. TikTok is fetched through a direct API without the watermark, and TikTok photo slideshows are stitched into a single mp4 with the post's music. The file arrives in the same chat and can be forwarded to anyone right away.",{"q":8078,"a":8079},"How do I download a YouTube video with a Telegram bot?","The bot shows quality buttons from 240p to 4K with the file size and delivers the video or an MP3 with cover art and metadata. YouTube requires two external components: the Deno JS runtime for link signing and a PO token service; in Docker both are already running.",{"q":8081,"a":8082},"How do you get around Telegram's 50 MB limit?","The cloud Bot API does not let a bot send files over 50 MB. The bot flags heavy qualities in advance, recompresses with ffmpeg or splits the file into parts. With a local Bot API server (the bigfiles profile in docker-compose) the ceiling rises to 2000 MB.",{"q":8084,"a":8085},"Why is a repeated link delivered instantly?","A file already uploaded to Telegram is cached by its file_id and forwarded without re-downloading or traffic. The cache lifetime is configurable, 30 days by default.",{"q":8087,"a":8088},"Are cookies needed?","For age-restricted and private videos and to get past YouTube's \"Sign in to confirm\" check. A cookies.txt file is uploaded through the admin panel without rebuilding the image, and the list shows how fresh each file is. Instagram without cookies often returns an empty response.",{"q":8090,"a":8091},"Can a similar bot be built for other platforms?","Yes. The yt-dlp engine is universal: Vimeo and Coub, for example, were added without any platform-specific code. The set of platforms, limits and the admin panel are configured for the task.",[8093],{"label":8094,"url":8095,"note":8096},"Try the bot in Telegram","https:\u002F\u002Ft.me\u002Ftiktokandyoutubedownload_bot","send a link, get the video",{},70,"\u002Fen\u002Fkeysy\u002Fsavebot",[],[8102,8105,8108],{"value":8103,"label":8104},"12 platforms","TikTok, YouTube, Instagram, VK Video, X, Reddit, Pinterest, Twitch, SoundCloud, Vimeo, Coub, Yandex Video",{"value":8106,"label":8107},"up to 4K","quality choice from 240p; the file size is shown on the button before downloading",{"value":7021,"label":8109},"file ceiling with the local Bot API instead of the cloud API's 50 MB",{"title":7854,"description":28},[293,294,8014,312,1366,6961,120],"en\u002Fkeysy\u002Fsavebot","in-house bot: no watermarks, quality up to 4K, MP3, files up to 2 GB","A Telegram bot that downloads video and music by link: TikTok without the watermark, YouTube and Shorts, Reels, VK Video and eight more platforms. yt-dlp, FFmpeg, a local Bot API for files up to 2 GB, a file_id cache.","-uS-A5CToeelI_ILyaFLIC1JlKQTfMHPdwtSFkADxDU",{"id":8117,"title":8118,"body":8119,"client":28,"cover":28,"date":3439,"demoUrl":28,"description":28,"direction":1193,"emoji":8315,"extension":33,"faq":8316,"featured":94,"links":8335,"meta":8336,"navigation":79,"nda":79,"order":8337,"own":94,"path":8338,"related":8339,"results":8340,"seo":8350,"shot":28,"shotAlt":28,"stack":8351,"status":4540,"stem":8352,"subtitle":8353,"summary":8354,"__hash__":8355},"cases_en\u002Fen\u002Fkeysy\u002Fqueue-bot.md","Electronic lab-submission queue in Telegram",{"type":18,"value":8120,"toc":8300},[8121,8123,8126,8129,8131,8134,8175,8177,8181,8188,8192,8195,8199,8202,8206,8209,8213,8220,8224,8227,8229,8245,8247,8283,8285],[1426,8122,1429],{"id":1428},[21,8124,8125],{},"Before lab submissions or exams, a group usually keeps a sign-up list on paper or in a chat. Someone signs up and does not come, someone loses their place, and the lecturer cannot tell who has already submitted. The group leader spends time on manual corrections and reminders.",[21,8127,8128],{},"The group needs a queue that behaves like a system, not like a conversation. The group leader creates an event, students of the group sign up themselves, and the bot keeps the order, calls the next person and marks no-shows. The lecturer needs a ready list in a convenient format.",[1426,8130,1439],{"id":1438},[21,8132,8133],{},"QueueBot is a Telegram bot for signing students up to electronic queues for submitting work and passing exams. What is implemented:",[1444,8135,8136,8139,8142,8145,8148,8151,8154,8161,8164,8167,8170],{},[1447,8137,8138],{},"registration with a shared invite code, then full name and group number; name uniqueness is checked within the group;",[1447,8140,8141],{},"three roles: student, group leader, administrator; blocked users are not allowed into the menu;",[1447,8143,8144],{},"several study groups: students see only their own group's queues;",[1447,8146,8147],{},"the queue itself: sign up, leave, skip a turn, rejoin at the end, view the queue, a “My status” screen;",[1447,8149,8150],{},"an “I'm here!” check-in when the event starts, and marking of no-shows;",[1447,8152,8153],{},"smart notifications when no more than three people remain ahead of a student;",[1447,8155,8156,8157,8160],{},"step-by-step queue creation by the group leader: subject, date, time, number of lecturers, confirmation; cancel with ",[1635,8158,8159],{},"\u002Fcancel",";",[1447,8162,8163],{},"manual order control: move up, move down, remove; announcements to the group as text or a photo;",[1447,8165,8166],{},"export of the queue to Excel;",[1447,8168,8169],{},"administrator broadcasts to everyone or to one group, with counters of delivered and blocked messages;",[1447,8171,3286,8172,8174],{},[1635,8173,3289],{}," command for the main administrator only.",[1426,8176,1517],{"id":1516},[1519,8178,8180],{"id":8179},"roles-through-middleware-in-aiogram","Roles through middleware in aiogram",[21,8182,8183,8184,8187],{},"Middleware in aiogram 3 is code that runs before every handler. Here it reads the user's role from the database and injects it into the request, so a student physically never sees the group leader's menu and a blocked user does not reach the menu at all. The main administrator is set by a separate setting and grants rights to others with the ",[1635,8185,8186],{},"\u002Fadmin_add"," command. There is also protection against rapid tapping: no more than one request per 0.5 seconds per user.",[1519,8189,8191],{"id":8190},"dialogs-on-a-finite-state-machine-fsm","Dialogs on a finite-state machine (FSM)",[21,8193,8194],{},"Registration and queue creation run step by step. An FSM (finite-state machine: the bot remembers which step a dialog is on) drives them. States are stored in process memory, so unfinished dialogs are lost on restart.",[1519,8196,8198],{"id":8197},"scheduled-check-in-apscheduler","Scheduled check-in: APScheduler",[21,8200,8201],{},"Background jobs are run by APScheduler. Every minute the bot checks whether any events have started (a window of plus or minus two minutes) and sends the check-in. Every five minutes the check-in timeout fires: those who have not confirmed their presence are marked as no-shows. Every two minutes the smart notifications for events already in progress are checked.",[1519,8203,8205],{"id":8204},"smart-notifications-about-the-approaching-turn","Smart notifications about the approaching turn",[21,8207,8208],{},"When no more than three people remain ahead of a student, the bot sends a notification. It is not repeated while the student stays at the same position. The next person in line receives a separate message with “Ready to go” and “Skip” buttons.",[1519,8210,8212],{"id":8211},"exporting-the-queue-to-excel","Exporting the queue to Excel",[21,8214,8215,8216,8219],{},"The export module builds an ",[1635,8217,8218],{},".xlsx"," file with the openpyxl library. The group leader exports the list with a button, and the lecturer receives a table with the sign-up order and statuses.",[1519,8221,8223],{"id":8222},"data-async-sqlalchemy-and-postgresql","Data: async SQLAlchemy and PostgreSQL",[21,8225,8226],{},"The database is PostgreSQL 15, accessed through SQLAlchemy 2.0 in async mode with the asyncpg driver. The main entities are user, subject, event and queue entry. Tables are created automatically on start. The bot and the database run through Docker Compose, and the containers' time zone is set explicitly, so the time in logs, check-ins and notifications matches Moscow time. Log files are rotated.",[1426,8228,1578],{"id":1577},[1444,8230,8231,8234,8237,8240,8243],{},[1447,8232,8233],{},"The queue is stored in a database rather than in a chat, and students see only their own group's queues.",[1447,8235,8236],{},"The bot sends the check-in, calls the next person and marks no-shows on its own.",[1447,8238,8239],{},"The group leader can adjust the order manually when needed; the administrator manages users and groups and sends broadcasts.",[1447,8241,8242],{},"The lecturer gets the queue as an Excel export.",[1447,8244,3376],{},[1426,8246,1602],{"id":1601},[1444,8248,8249,8255,8261,8267,8272,8278],{},[1447,8250,8251,8254],{},[1450,8252,8253],{},"Python 3.11 and aiogram 3"," — an asynchronous Telegram bot: menus, roles, a finite-state machine for dialogs.",[1447,8256,8257,8260],{},[1450,8258,8259],{},"SQLAlchemy 2.0 (async) and asyncpg"," — database access without blocking the bot.",[1447,8262,8263,8266],{},[1450,8264,8265],{},"PostgreSQL 15"," — storage for users, subjects, events and queue entries.",[1447,8268,8269,8271],{},[1450,8270,306],{}," — check-in, no-show timeout and smart notifications on a schedule.",[1447,8273,8274,8277],{},[1450,8275,8276],{},"openpyxl"," — exporting the queue to an Excel file.",[1447,8279,8280,8282],{},[1450,8281,650],{}," — starting the bot and the database with one command.",[1426,8284,1647],{"id":1646},[21,8286,8287,8288,8291,8292,8295,8296,8299],{},"Version 1.0.1 of 30 September 2026. The ",[1635,8289,8290],{},"queue-bot"," repository is declared the only current one; it grew out of the earlier ",[1635,8293,8294],{},"ocheredyV2"," repository (version 2.1.0, February 2026) and ",[1635,8297,8298],{},"botocheredy",", which are no longer developed and have been archived. Known limitations from the README: dialog states and throttling are kept in memory, the bot is designed to run as a single instance, the schema is created on start without migrations, and access is protected by one shared invite code. Other details about the client are not disclosed.",{"title":28,"searchDepth":29,"depth":29,"links":8301},[8302,8303,8304,8312,8313,8314],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":8305},[8306,8307,8308,8309,8310,8311],{"id":8179,"depth":1661,"text":8180},{"id":8190,"depth":1661,"text":8191},{"id":8197,"depth":1661,"text":8198},{"id":8204,"depth":1661,"text":8205},{"id":8211,"depth":1661,"text":8212},{"id":8222,"depth":1661,"text":8223},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"🎓",[8317,8320,8323,8326,8329,8332],{"q":8318,"a":8319},"How do I run a queue for lab defenses in a study group without a paper list?","The group leader creates an event in the bot: subject, date, start and end time and the number of lecturers. Students of that group sign up with one tap. The order is stored in a database rather than in a chat history, and the bot keeps track of it.",{"q":8321,"a":8322},"How do students find out their turn is coming?","When no more than three people remain ahead of a student, the bot sends a notification and does not repeat it at the same position. The next person in line gets a message with “Ready to go” and “Skip” buttons. A student can skip their turn or rejoin at the end.",{"q":8324,"a":8325},"What happens if a student signs up but does not show up?","When the event starts, the bot sends a check-in with an “I'm here!” button. Anyone who has not pressed it within 15 minutes (a configurable default) is marked as a no-show. The group leader can manually move entries up or down or remove them from the queue.",{"q":8327,"a":8328},"Who can do what in the bot?","There are three roles. A student signs up for their group's queue, leaves it and views their status. A group leader creates and closes queues, exports the list to Excel and posts announcements to the group. An administrator blocks users, changes groups and roles, and broadcasts messages to everyone or to a chosen group.",{"q":8330,"a":8331},"Can the lecturer get the queue as an Excel file?","Yes. The group leader exports the event's queue to an .xlsx file, and the lecturer sees who has submitted and who is still waiting.",{"q":8333,"a":8334},"Where is the data stored and what should I know about deployment?","Data lives in PostgreSQL, and the bot and the database run in Docker Compose. The schema is created automatically on start. Unfinished dialogs are kept in memory and lost on restart, so the bot is designed to run as a single instance.",[],{},80,"\u002Fen\u002Fkeysy\u002Fqueue-bot",[],[8341,8344,8347],{"value":8342,"label":8343},"3 roles","student, group leader, administrator: rights are looked up from the database on every request",{"value":8345,"label":8346},"≤ 3 people","ahead of a student: from that point the bot sends a notification that the turn is near",{"value":8348,"label":8349},"15 min","default check-in window after the event starts, then no-shows are marked",{"title":8118,"description":28},[293,294,3004,2426,296,306,8276,120],"en\u002Fkeysy\u002Fqueue-bot","QueueBot for a university: one-tap sign-up, reminders and an Excel export","A Telegram bot for an electronic lab-submission queue at a university: students sign up with one tap, the bot keeps the order and sends reminders, the group leader runs the group, and the list exports to Excel.","gPNSSHMC5v1fs7D74H4ZIPZ0wHgdF0DsEIQ8Y_I4D94",{"id":8357,"title":8358,"body":8359,"client":28,"cover":28,"date":8603,"demoUrl":28,"description":28,"direction":1193,"emoji":8604,"extension":33,"faq":8605,"featured":94,"links":8624,"meta":8625,"navigation":79,"nda":79,"order":8626,"own":94,"path":8627,"related":8628,"results":8629,"seo":8639,"shot":28,"shotAlt":28,"stack":8640,"status":2249,"stem":8642,"subtitle":8643,"summary":8644,"__hash__":8645},"cases_en\u002Fen\u002Fkeysy\u002Fbot-prodazhi-podpisok.md","Telegram bot for selling VPN subscriptions",{"type":18,"value":8360,"toc":8587},[8361,8363,8366,8369,8371,8374,8379,8426,8431,8458,8460,8464,8467,8471,8478,8482,8485,8489,8492,8496,8499,8503,8506,8510,8513,8515,8532,8534,8569,8571,8577],[1426,8362,1429],{"id":1428},[21,8364,8365],{},"For a closed circle of customers, VPN access had to be issued and renewed without manual work in the Marzban panel. Every step of such access needs an admin's attention: working out who has arrived, creating the user, sending the key, accepting payment, extending the term, reminding about renewal in time and explaining how to set up the app on a particular device.",[21,8367,8368],{},"What was needed was a Telegram bot in which the customer goes from invite-code registration to payment and receives a key on their own, while the admin only approves the payment and sees the overall picture. Payment by bank transfer with a human checking the receipt: no card acquiring to set up, and full control over whose payment is accepted.",[1426,8370,1439],{"id":1438},[21,8372,8373],{},"A Telegram bot for selling VPN subscriptions on top of Marzban, built for a private VPN service. Implemented in version 2.0.0:",[21,8375,8376],{},[1450,8377,8378],{},"For the user",[1444,8380,8381,8387,8393,8399,8405,8411,8417],{},[1447,8382,8383,8386],{},[1450,8384,8385],{},"Invite-code registration."," Without a code the bot gives no access; with a valid code it automatically issues 2 hours of trial access.",[1447,8388,8389,8392],{},[1450,8390,8391],{},"Connection profile."," Subscription status, remaining time and the VLESS key from Marzban.",[1447,8394,8395,8398],{},[1450,8396,8397],{},"Purchase."," 1, 6 and 12 month plans (prices are set by a setting). The bot shows the bank-transfer details and a link to the bank, and the user sends a screenshot of the receipt.",[1447,8400,8401,8404],{},[1450,8402,8403],{},"Bonuses for long plans."," +10 days for 6 months, +30 days for 12 months.",[1447,8406,8407,8410],{},[1450,8408,8409],{},"Instructions."," For iOS, Android, Windows, macOS, Linux, routers and Smart TV; they open as a WebApp, and for routers there is a button to contact a setup specialist.",[1447,8412,8413,8416],{},[1450,8414,8415],{},"Reminders."," 3 days and 1 day before the subscription ends, checked daily at 10:00.",[1447,8418,8419,8422,8423,3340],{},[1450,8420,8421],{},"A tidy chat."," The bot's service messages are deleted after an hour; there are an FAQ, support and the ",[1635,8424,8425],{},"\u002Fmyid",[21,8427,8428],{},[1450,8429,8430],{},"For the admin",[1444,8432,8433,8439,8445,8450],{},[1447,8434,8435,8438],{},[1450,8436,8437],{},"Payment approval."," Buttons under the receipt with protection against double processing by several admins; on approval the subscription is extended in Marzban.",[1447,8440,8441,8444],{},[1450,8442,8443],{},"The Root Access panel."," Statistics, search by ID, user lists (all, active, expired), deletion with confirmation, gifting hours or days, a broadcast with a delivery report, logs and a manual backup.",[1447,8446,8447,8449],{},[1450,8448,4587],{}," About new registrations, bot start and stop, and backup results.",[1447,8451,8452,8457],{},[1450,8453,8454,8455,3340],{},"A hidden ",[1635,8456,3289],{}," It shows the version of the running instance; non-admins are ignored.",[1426,8459,1517],{"id":1516},[1519,8461,8463],{"id":8462},"invite-code-registration-and-a-2-hour-trial","Invite-code registration and a 2-hour trial",[21,8465,8466],{},"An invite code is a shared password for entering a private service: without it the bot will not register a stranger. A valid code starts the automatic issue of 2 hours of trial access: the bot creates the user in Marzban itself, and the customer gets a key and can try the service before paying. An admin registers without a code, and their Marzban access lasts 24 hours.",[1519,8468,8470],{"id":8469},"marzban-api-creating-a-user-with-the-xtls-rprx-vision-flow","Marzban API: creating a user with the xtls-rprx-vision flow",[21,8472,8473,8474,8477],{},"Marzban is a control panel for a VPN server: it stores users and issues keys. The bot calls its API (a set of addresses through which programs send commands), obtains a token and creates a user named ",[1635,8475,8476],{},"user_"," plus the Telegram ID. The user gets the xtls-rprx-vision flow, a limit of one online device and no traffic limit. Subscriptions are built on VLESS and Reality: a protocol and a disguise method the client app uses to connect to the server. On renewal the new term is counted from the current end date, not from the payment day.",[1519,8479,8481],{"id":8480},"bank-transfer-payment-the-admin-approves-the-receipt","Bank-transfer payment: the admin approves the receipt",[21,8483,8484],{},"The customer picks a plan, the bot shows the details and a link to the bank, the customer pays and sends a screenshot of the receipt. Every admin receives the receipt with Approve and Deny buttons under the message. After approval the subscription is extended in Marzban (with bonus days for 6 and 12 months) and the user is notified. This is a manual check: there is no automatic payment-system integration in the project.",[1519,8486,8488],{"id":8487},"protection-against-double-approval-by-several-admins","Protection against double approval by several admins",[21,8490,8491],{},"The receipt goes to several admins at once, so two of them could press Approve and extend the subscription twice. The version history records a separate fix: payment handling is protected against repeated confirmation. The README does not describe how exactly the locking works.",[1519,8493,8495],{"id":8494},"reminders-and-maintenance-on-a-schedule","Reminders and maintenance on a schedule",[21,8497,8498],{},"The APScheduler scheduler runs in the Europe\u002FMoscow time zone. The check for expiring subscriptions starts at 10:00 and reminds 3 days and 1 day ahead. Old backups are cleaned at 00:30, and weekly database maintenance (removing old log records) runs on Mondays at 03:00.",[1519,8500,8502],{"id":8501},"backups-local-and-to-google-drive-from-the-bot","Backups: local and to Google Drive from the bot",[21,8504,8505],{},"The bot makes a JSON dump of users, admins and the last 100 log records and compiles a text report. The copy is saved to a local folder and uploaded to Google Drive through a service account (a system account meant for programs). The backup hour is configurable, by default 14:00 and 14:05. A final copy is made when the bot stops, and a manual backup is available from a button in the admin panel.",[1519,8507,8509],{"id":8508},"polling-or-webhook-in-aiogram","Polling or webhook in aiogram",[21,8511,8512],{},"By default the bot asks Telegram for updates itself (polling). For a server with a public address there is a webhook mode: Telegram delivers updates to a built-in web server. The mode is switched by a setting.",[1426,8514,1578],{"id":1577},[1444,8516,8517,8520,8523,8526,8529],{},[1447,8518,8519],{},"A customer goes from registration to a key without any admin involvement: invite code, a 2-hour trial, purchase, key.",[1447,8521,8522],{},"The admin does not need to open Marzban: payment approval, gifting time, and searching and deleting users are done in Telegram.",[1447,8524,8525],{},"The subscription is extended in Marzban right after a payment is approved, and bonus days for longer plans are added automatically.",[1447,8527,8528],{},"The customer gets a reminder before the subscription ends, and the admin gets notifications about registrations, start, stop and backups.",[1447,8530,8531],{},"The bot's state is kept in MariaDB, and backups go to Google Drive.",[1426,8533,1602],{"id":1601},[1444,8535,8536,8541,8547,8553,8558,8564],{},[1447,8537,8538,8540],{},[1450,8539,7417],{}," — the bot: dialogs on finite state machines, buttons under messages, a WebApp with instructions.",[1447,8542,8543,8546],{},[1450,8544,8545],{},"MariaDB (aiomysql)"," — users, admins, log and payments; tables are created at startup, connections come from a pool.",[1447,8548,8549,8552],{},[1450,8550,8551],{},"Marzban API (aiohttp)"," — creating users, extending subscriptions, fetching keys.",[1447,8554,8555,8557],{},[1450,8556,306],{}," — subscription reminders, backups, cleanup and database maintenance on a schedule.",[1447,8559,8560,8563],{},[1450,8561,8562],{},"Google Drive API (PyDrive2)"," — uploading backups through a service account.",[1447,8565,8566,8568],{},[1450,8567,3711],{}," — the built-in web server for webhook mode.",[1426,8570,1647],{"id":1646},[21,8572,8573,8574,8576],{},"A test version. The current version, 2.0.0 of 2 February 2026, brought bank-transfer payment with receipt approval by an admin, updated plans and WebApp instructions per platform. After that the code gained a version constant, the hidden ",[1635,8575,3289],{}," command and version output to the log at startup.",[21,8578,8579,8580,8582,8583,8586],{},"Not done yet: moving all settings out of the code into ",[1635,8581,1637],{}," and adding ",[1635,8584,8585],{},".env.example",", Docker, automated tests and rate limiting; the rate-limit and trial-duration settings are declared in the configuration but not applied yet. Payment remains manual, with no automatic payment-system integration.",{"title":28,"searchDepth":29,"depth":29,"links":8588},[8589,8590,8591,8600,8601,8602],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":8592},[8593,8594,8595,8596,8597,8598,8599],{"id":8462,"depth":1661,"text":8463},{"id":8469,"depth":1661,"text":8470},{"id":8480,"depth":1661,"text":8481},{"id":8487,"depth":1661,"text":8488},{"id":8494,"depth":1661,"text":8495},{"id":8501,"depth":1661,"text":8502},{"id":8508,"depth":1661,"text":8509},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"2026-02-02","🔑",[8606,8609,8612,8615,8618,8621],{"q":8607,"a":8608},"How can I sell VPN subscriptions through a Telegram bot without card acquiring?","The bot shows the bank-transfer details and a link to the bank, and the user sends a screenshot of the receipt. The receipt goes to all admins with Approve and Deny buttons. After approval the Marzban subscription is extended automatically. There is no automatic payment-system integration in the project: a person checks each payment.",{"q":8610,"a":8611},"How do the invite code and the trial period work?","A new user enters the invite code and gets trial access for 2 hours: the bot creates the user in Marzban itself and sends a VLESS key. A wrong code is rejected. An admin registers without a code and gets 24 hours of access.",{"q":8613,"a":8614},"How does the bot create a user in the Marzban panel?","Through the Marzban API it creates a user named user_ plus the Telegram ID, with the xtls-rprx-vision flow, a limit of one online device and no traffic limit. A renewal is counted from the current end date. The key for the profile screen is fetched from Marzban.",{"q":8616,"a":8617},"What can an admin do directly from Telegram?","In the Root Access panel: statistics (total, active, expired, expiring today), search by ID, user lists, deletion with confirmation, gifting hours or days with a comment, a broadcast to everyone with a delivery report, the latest logs and a manual backup.",{"q":8619,"a":8620},"Where do the bot's backups go?","The bot makes a JSON dump of users, admins and the last 100 log records, plus a text report. The copy is kept in a local folder and uploaded to Google Drive through a service account. A final copy is made when the bot stops, and old copies are cleaned up on a schedule.",{"q":8622,"a":8623},"How is the bot run, and what does it need?","It runs as an ordinary Python program (3.10 or newer) alongside a MariaDB or MySQL database and a Marzban panel. It creates its tables at startup. The default mode is polling, and webhook mode is available. The repository has no Docker or docker-compose files yet.",[],{},85,"\u002Fen\u002Fkeysy\u002Fbot-prodazhi-podpisok",[],[8630,8633,8636],{"value":8631,"label":8632},"2 hours","trial access is issued automatically after registering with an invite code",{"value":8634,"label":8635},"3 plans","1, 6 and 12 months; +10 days for 6 months and +30 days for 12 months",{"value":8637,"label":8638},"3 days and 1 day","the bot reminds the user to renew this long before the subscription ends",{"title":8358,"description":28},[293,294,297,3396,306,3711,8641],"Google Drive API","en\u002Fkeysy\u002Fbot-prodazhi-podpisok","for a private VPN service: invite code, trial period, admin-approved payment receipt","A Telegram bot for selling VPN subscriptions on Marzban: invite-code registration, a 2-hour trial, 1, 6 and 12 month plans, and bank-transfer payment with the receipt approved by an admin. Python, aiogram 3, MariaDB.","AuDWZ9Vo1JRB-cXO5-m2jy8m43g63TKG4-rBSHZ8zKY",{"id":8647,"title":8648,"body":8649,"client":28,"cover":28,"date":3439,"demoUrl":28,"description":28,"direction":1193,"emoji":8832,"extension":33,"faq":8833,"featured":94,"links":8852,"meta":8853,"navigation":79,"nda":94,"order":8854,"own":79,"path":8855,"related":8856,"results":8857,"seo":8866,"shot":28,"shotAlt":28,"stack":8867,"status":1713,"stem":8868,"subtitle":8869,"summary":8870,"__hash__":8871},"cases_en\u002Fen\u002Fkeysy\u002Fbirthday-bot.md","Birthday bot: reminders and a gift budget",{"type":18,"value":8650,"toc":8817},[8651,8653,8656,8659,8661,8664,8708,8710,8714,8717,8721,8728,8732,8738,8742,8745,8749,8752,8756,8762,8764,8780,8782,8809,8811],[1426,8652,1429],{"id":1428},[21,8654,8655],{},"Birthdays of loved ones are easy to forget, and gifts cost money that is better set aside in advance. Usually the dates live in one person's head or in scattered notes, and nobody adds up the gift budget: the expenses hit all in one month.",[21,8657,8658],{},"What is needed is a shared list that friends and family fill in themselves, and a clear calculation for the one person who takes care of it. The administrator needs reminders in advance rather than on the day, and a number: how much to set aside for the coming months.",[1426,8660,1439],{"id":1438},[21,8662,8663],{},"The birthday bot is the studio's own product: a Telegram bot that collects dates from any users, sends the administrator reminders and calculates a gift budget. What is implemented:",[1444,8665,8666,8673,8676,8682,8688,8694,8697,8705],{},[1447,8667,8668,8669,8672],{},"step-by-step entry creation with the ",[1635,8670,8671],{},"\u002Fadd"," command: name, a date as DD.MM or DD.MM.YYYY (the year is optional), a budget and a note;",[1447,8674,8675],{},"a check that the date exists, including the number of days in the month;",[1447,8677,8678,8681],{},[1635,8679,8680],{},"\u002Flist",": all entries ordered by how soon the date is, with age (if a year is given), budget and note, with “Budget” and “Delete” buttons under each;",[1447,8683,8684,8687],{},[1635,8685,8686],{},"\u002Fcalendar",": an interactive month calendar with birthdays marked and month navigation;",[1447,8689,8690,8693],{},[1635,8691,8692],{},"\u002Fbudget",", for the administrator only: totals for 30, 90 and 365 days, a month-by-month breakdown for a year ahead and a grand total;",[1447,8695,8696],{},"a daily scheduled check and reminders to the administrator a configurable number of days ahead;",[1447,8698,8699,8701,8702,8704],{},[1635,8700,8159],{}," to abort a dialog, and a hidden ",[1635,8703,3289],{}," command for the administrator;",[1447,8706,8707],{},"Docker deployment with the database stored on the host's disk.",[1426,8709,1517],{"id":1516},[1519,8711,8713],{"id":8712},"step-by-step-entry-on-an-fsm-in-aiogram","Step-by-step entry on an FSM in aiogram",[21,8715,8716],{},"An entry is created through a four-step dialog. It runs on an FSM (finite-state machine: the bot remembers which step a dialog with a particular person is on). The bot accepts a date with or without a year, checks that it exists, and the budget and note can be skipped with a “-”. Dialog states are kept in memory, so an unfinished dialog starts over after a bot restart.",[1519,8718,8720],{"id":8719},"daily-reminders-apscheduler-and-a-cron-schedule","Daily reminders: APScheduler and a cron schedule",[21,8722,8723,8724,8727],{},"APScheduler is a task scheduling library. Once a day at a set time (9:00 Moscow time by default) it runs a check: if a date is 14, 7, 3 or 1 days away, or is today, the administrator gets a reminder. The list of days is set through an environment variable, where ",[1635,8725,8726],{},"0"," means the day itself. The schedule's time zone is configured separately.",[1519,8729,8731],{"id":8730},"the-gift-budget-by-month","The gift budget by month",[21,8733,8734,8735,8737],{},"Each entry can carry its own budget. The ",[1635,8736,8692],{}," command adds up the budgets of dates that fall within a 30-, 90- or 365-day window (the window is switched with buttons) and spreads the total across the months of the coming year. The breakdown shows the “expensive” months at once, when the most will go on gifts.",[1519,8739,8741],{"id":8740},"a-calendar-on-inline-buttons","A calendar on inline buttons",[21,8743,8744],{},"The calendar is built from inline buttons right inside the message. Days with birthdays are marked with a cake icon, you can page through months, and tapping a number shows whose birthday it is. A 29 February birthday is marked on 1 March in a non-leap year.",[1519,8746,8748],{"id":8747},"sqlite-in-docker-data-survives-a-rebuild","SQLite in Docker: data survives a rebuild",[21,8750,8751],{},"SQLite is a database in a single file with no separate server. In Docker the database file is placed on the host's disk (a volume), so it survives a container rebuild. The container publishes no ports because the bot polls Telegram itself (long polling), has a 256 MB memory limit and log rotation. A backup is a copy of the file, preferably taken while the container is stopped.",[1519,8753,8755],{"id":8754},"permissions-who-can-do-what","Permissions: who can do what",[21,8757,8758,8759,8761],{},"Any user of the bot can add entries. The budget can be changed or an entry deleted by the administrator or by whoever added it. Reminders and ",[1635,8760,8692],{}," are seen only by the administrator, who is identified by an ID from the settings.",[1426,8763,1578],{"id":1577},[1444,8765,8766,8769,8772,8775,8778],{},[1447,8767,8768],{},"Birthdays are collected by friends and family, not by a single person.",[1447,8770,8771],{},"The administrator gets reminders in advance rather than on the day.",[1447,8773,8774],{},"The gift budget is visible in numbers for 30, 90 and 365 days, broken down by month.",[1447,8776,8777],{},"Data is kept in one file and survives a container restart.",[1447,8779,3376],{},[1426,8781,1602],{"id":1601},[1444,8783,8784,8789,8794,8799,8804],{},[1447,8785,8786,8788],{},[1450,8787,300],{}," — an asynchronous Telegram bot: commands, dialogs, inline buttons.",[1447,8790,8791,8793],{},[1450,8792,7423],{}," — storing entries in one file, with async access that does not block the bot.",[1447,8795,8796,8798],{},[1450,8797,306],{}," — the daily check and reminder delivery on a schedule.",[1447,8800,8801,8803],{},[1450,8802,7435],{}," — the bot token, the administrator ID and reminder settings from the environment.",[1447,8805,8806,8808],{},[1450,8807,650],{}," — running on a server with the database on the host's disk and a Moscow time zone in the container.",[1426,8810,1647],{"id":1646},[21,8812,8813,8814,8816],{},"Version 1.0.0 of 30 September 2026. The version is printed to the log on every start, and there is a hidden ",[1635,8815,3289],{}," command. Known limitations: only the administrator receives reminders; the name, date and note cannot be edited after adding (only the budget and deletion); the list has to be reopened after deleting an entry; there are no schema migrations.",{"title":28,"searchDepth":29,"depth":29,"links":8818},[8819,8820,8821,8829,8830,8831],{"id":1428,"depth":29,"text":1429},{"id":1438,"depth":29,"text":1439},{"id":1516,"depth":29,"text":1517,"children":8822},[8823,8824,8825,8826,8827,8828],{"id":8712,"depth":1661,"text":8713},{"id":8719,"depth":1661,"text":8720},{"id":8730,"depth":1661,"text":8731},{"id":8740,"depth":1661,"text":8741},{"id":8747,"depth":1661,"text":8748},{"id":8754,"depth":1661,"text":8755},{"id":1577,"depth":29,"text":1578},{"id":1601,"depth":29,"text":1602},{"id":1646,"depth":29,"text":1647},"🎂",[8834,8837,8840,8843,8846,8849],{"q":8835,"a":8836},"How do I stop forgetting the birthdays of friends and family?","Friends and family add their dates themselves through a link to the bot, and the administrator gets reminders in advance. By default the bot writes 14, 7, 3 and 1 days before the date and on the day itself, at 9 a.m. Moscow time. The set of days and the time are configurable.",{"q":8838,"a":8839},"How can I plan a gift budget a year ahead?","When adding an entry you can set a gift budget. The \u002Fbudget command, available to the administrator, totals the budgets for the next 30, 90 or 365 days and shows a month-by-month breakdown for a year ahead. That shows which months will cost the most.",{"q":8841,"a":8842},"Does the administrator have to enter dates manually?","No. Entries are added by anyone the administrator has given the bot link to. Adding goes step by step: name, date (the year is optional), budget and a note. The bot checks that the date exists and rejects, for example, 31.02.",{"q":8844,"a":8845},"Who sees the entries, and who sees the reminders and the budget?","All entries are visible to every user of the bot. Reminders and the \u002Fbudget command are available only to the administrator. An entry can be deleted, or its budget changed, by the administrator or by whoever added it.",{"q":8847,"a":8848},"How is a birthday on 29 February handled?","In a non-leap year the bot marks that date on 1 March. There is also a month calendar: days with birthdays carry a cake icon, and tapping a number shows whose day it is.",{"q":8850,"a":8851},"What does the bot run on and where is the data stored?","The bot is written in Python with aiogram 3, data is kept in a SQLite file, and APScheduler runs the reminder schedule. For a server it is set up to run in Docker: the database is stored on the host's disk and survives a container rebuild.",[],{},90,"\u002Fen\u002Fkeysy\u002Fbirthday-bot",[],[8858,8861,8864],{"value":8859,"label":8860},"14 \u002F 7 \u002F 3 \u002F 1","days ahead plus the day itself: reminders come in advance, and the set of days is configurable",{"value":8862,"label":8863},"30 \u002F 90 \u002F 365","days ahead: how much to set aside for gifts, broken down by month for a year",{"value":7240,"label":8865},"the whole database is a single SQLite file: a backup is a copy of the file",{"title":8648,"description":28},[293,294,1366,7497,306,120],"en\u002Fkeysy\u002Fbirthday-bot","our own product: friends and family add the dates, the admin gets the reminders","A Telegram bot where friends and family enter birthdays themselves, while the admin gets reminders 14, 7, 3 and 1 days ahead, a calendar and a gift budget forecast for 30, 90 and 365 days. Python, aiogram 3, SQLite.","9jv_mH8PMWy8l7rgeyCPBFFGQ4hYUzftzeZ2t5KmkfU",{"id":8873,"extension":8874,"groups":8875,"meta":9545,"stem":9546,"__hash__":9547},"glossary_en\u002Fen\u002Fglossary.json","json",[8876,8959,8977,9072,9127,9219,9298,9327,9341,9379,9409,9459,9486],{"group":8877,"terms":8878},"Business terms",[8879,8884,8889,8894,8899,8904,8909,8914,8919,8924,8929,8934,8939,8944,8949,8954],{"id":8880,"title":4559,"what":8881,"how":8882,"howLabel":8883},"crm","Software for tracking customers and deals — who reached out, what stage the deal is at, who should call and when. A replacement for the Excel sheet that sooner or later falls apart.","We connect bots and automation to it through its API — requests from the website and Telegram land in the CRM straight away, no manual copying.","How we use it",{"id":8885,"title":8886,"what":8887,"how":8888,"howLabel":8883},"saas","SaaS","\"Software as a service\" — a product you use by subscription in the browser, with no installation and no server of your own (Zapier, Notion, most CRMs).","We often offer a self-hosted alternative to a SaaS product when a client wants to stop depending on someone else's platform and pricing.",{"id":4619,"title":8890,"what":8891,"how":8892,"howLabel":8893},"Webhook","An automatic notification one service sends to another when something happens — \"payment received\" or \"new order\" — instead of asking \"anything new?\" every minute.","The backbone of most of our n8n automations: a marketplace order triggers the chain of actions immediately rather than waiting for someone to start it by hand.","How we apply it",{"id":8895,"title":8896,"what":8897,"how":8898,"howLabel":8883},"deploy","Production \u002F Deploy","Production is the live version of a service that real people use (as opposed to a test one). Deploying is the process of shipping a new version of the code to the server.","We deploy through CI\u002FCD — automatically, with no manual file copying and no risk of shipping the wrong version.",{"id":8900,"title":8901,"what":8902,"how":8903,"howLabel":8883},"hosting","Hosting and domain","Hosting is the server where your website physically lives. A domain is its address on the internet (for example, devunit-lab.ru), registered separately and renewed every year.","We help choose both for the task at hand and move websites between hosts with no downtime and no lost search rankings.",{"id":8905,"title":8906,"what":8907,"how":8908,"howLabel":8883},"backup","Backup","A spare copy of your data that saves the day when the server breaks, files get deleted by mistake or ransomware encrypts everything.","We set up automatic backups with rotation (old copies delete themselves) — we never rely on \"I'll do it by hand later\".",{"id":8910,"title":8911,"what":8912,"how":8913,"howLabel":8883},"dns","DNS","The \"phone book\" of the internet — it turns a readable site name (devunit-lab.ru) into the numeric server address the request actually goes to.","We configure DNS records when moving a site or setting up email on a domain — without it, the right address simply leads nowhere.",{"id":8915,"title":8916,"what":8917,"how":8918,"howLabel":8883},"ssh","SSH","A secure way to connect to a server remotely and manage it through commands — how servers are administered without physical access.","Our main channel to client servers and our own — by key, not by password; it is far safer.",{"id":8920,"title":8921,"what":8922,"how":8923,"howLabel":8893},"sla","SLA","A written commitment on response and recovery times — for example, \"we answer within an hour and fix within a day\". It protects both sides from vague expectations.","We write it into the support contract after handover — specific deadlines, not \"as soon as we can\".",{"id":8925,"title":8926,"what":8927,"how":8928,"howLabel":8883},"nda","NDA","A non-disclosure agreement — a legal obligation not to reveal project details and client data to third parties.","We sign one on request — which is why some case studies in the portfolio show no company name, only the substance and the stack.",{"id":8930,"title":8931,"what":8932,"how":8933,"howLabel":8893},"ltv","LTV","Customer lifetime value — how much money a customer brings the company on average over the whole relationship, not from a single purchase.","A metric we grow with loyalty programmes and automatic customer win-back — keeping an existing customer is cheaper than acquiring a new one.",{"id":8935,"title":8936,"what":8937,"how":8938,"howLabel":8883},"tz","Technical specification","A document stating exactly what we build, what the client receives and how to check it is done. Without it the \"I thought that was included\" argument is inevitable.","We write the specification after a free audit and set timelines once it is approved. The same document becomes the project README on delivery.",{"id":8940,"title":8941,"what":8942,"how":8943,"howLabel":8883},"kp","Proposal (quote)","A letter with the scope, price and timeline for a specific task. Prices on our site are starting prices; the exact figure comes in the proposal after the audit.","The proposal is free. No hourly billing: the price is fixed for the result, and payment is usually by stage.",{"id":8945,"title":8946,"what":8947,"how":8948,"howLabel":8883},"mvp","MVP","The minimum working version of a product: only what is impossible to launch without. It tests the idea on real customers in weeks rather than months and grows from their feedback.","The studio's own products started as MVPs: the VPN service with a bot, the player, the laser tag system. We suggest the same to clients: launch small, measure, extend.",{"id":8950,"title":8951,"what":8952,"how":8953,"howLabel":8883},"maintenance","Maintenance (support plan)","A monthly fee for keeping a site, bot or server running: updates, backups, monitoring, small fixes. Cheaper than repairing after a failure.","The first month after delivery is free, then from $100 a month. This is what makes keeping everything on your own server possible without hiring a sysadmin.",{"id":8955,"title":8956,"what":8957,"how":8958,"howLabel":8883},"git","Git and repositories","A system that stores every version of the code and lets you roll back to any of them. A repository is a project folder under its control, usually on GitHub, private.","Every studio project is a private repository with a README written as a specification and a CHANGELOG. On delivery we hand access to the client: the code is theirs.",{"group":8960,"terms":8961},"General terms",[8962,8965,8969,8973],{"id":1338,"title":4725,"what":8963,"how":8964,"howLabel":8883},"In plain words — your own private cloud server where you are the only owner, rather than a third-party service like Zapier or Make. Nobody can restrict your access, raise the price retroactively or shut the service down one morning.","We deploy your automation, bots and internal services on it. The downside: full control means full responsibility — the server needs maintenance, updates and monitoring. Usually we take care of that 🙂",{"id":8966,"title":4441,"what":8967,"how":8968,"howLabel":8883},"api","The way one program asks another for data or actions — for example, your website asks Telegram to send a message, or a marketplace for order data.","If a service (CRM, warehouse, marketplace, bank) has an API, we plug it into your automation directly — no manual work and no copying data by hand.",{"id":8970,"title":198,"what":8971,"how":8972,"howLabel":8893},"telegram-bots","A program inside Telegram or VK that can answer customers, take requests, notify about orders or keep records — with no human involved, 24\u002F7.","We build bots on the official Telegram Bot API and the VK community API, with inline buttons and command menus instead of bulky keyboards. We connect them to your CRM, warehouse or payments. Prices are on the “Bots” service page.",{"id":8974,"title":6479,"what":8975,"how":8976,"howLabel":8883},"websites","A website is a page or a set of pages on the internet that tell people about your business, take requests or sell goods. Unlike a social media account or a marketplace listing, a website is fully yours: the address, the design and the data belong to you, not the platform.","We build websites end to end — from a simple one-page landing with a single goal (a request, a sale, a booking) to an online store with its own admin panel. Unlike site builders (Tilda, Wix and the like), there is no monthly platform rent, no third-party limits, and any automation can be built in — a chat bot, request intake or a warehouse integration.",{"group":1256,"terms":8978},[8979,8984,8988,8992,8997,9002,9007,9010,9015,9019,9024,9029,9034,9039,9043,9048,9053,9058,9062,9067],{"id":8980,"title":8981,"what":8982,"how":8983,"howLabel":8883},"python","Python (FastAPI, Django)","One of the most widespread programming languages — used for simple scripts and large backends alike (the server side: the part users don't see directly, but which handles data and logic).","Our main language for bots and backends. FastAPI is a fast, modern framework (a ready-made toolkit so nothing is written from scratch) for APIs; Django is the \"heavier\" option for complex admin panels and business logic.",{"id":8985,"title":1263,"what":8986,"how":8987,"howLabel":8893},"go","A compiled programming language from Google — as fast as C++, but noticeably simpler to develop in, with excellent built-in support for parallel work.","For services where performance under load matters — for example, media servers streaming video to many viewers at once.",{"id":8989,"title":1267,"what":8990,"how":8991,"howLabel":8883},"cpp","A low-level programming language with direct hardware access — maximum performance at the cost of more complex development.","Firmware for microcontrollers (ESP32) and calculations where microseconds count. We keep behaviour identical to the Python version through a shared protocol and automatic cross-checks in CI.",{"id":8993,"title":8994,"what":8995,"how":8996,"howLabel":8883},"nodejs","Node.js (Express, NestJS)","A runtime that lets JavaScript run on the server instead of in the browser.","We reach for it when a project needs to handle many simultaneous connections quickly — for example, chats or real-time webhooks.",{"id":8998,"title":8999,"what":9000,"how":9001,"howLabel":8883},"google-ai-api","Google AI API (Gemini)","Programmatic access to Google's neural networks (the Gemini model family) — your code can \"ask\" the AI something and get an answer automatically.","The AI assistant in the chat on this website runs on it — it answers visitors' questions and gathers the basics about their task before a call with you.",{"id":9003,"title":9004,"what":9005,"how":9006,"howLabel":8883},"sqlalchemy","SQLAlchemy and Alembic (database access and migrations)","SQLAlchemy is the library a Python program uses to work with a database without hand-writing SQL for every step. Alembic is its companion for migrations: it changes the table structure step by step without losing the data already collected.","The IT help desk bot has 20 tables and 16 Alembic migrations, and the online store applies its migrations automatically when the API starts. Taking a ticket locks the row in the database, so two administrators cannot take the same one.",{"id":3711,"title":3711,"what":9008,"how":9009,"howLabel":8883},"A Python library for network calls that do not block: a bot talks to other services' APIs and keeps answering people at the same time. It works both as a client and as a small web server.","The subscription-sales bot calls the Marzban panel API with it, the «Дыхание Поволжья» community bot reads the catalogue from the website, and the UNLOCK card service serves card settings and receives the recipients' replies.",{"id":9011,"title":9012,"what":9013,"how":9014,"howLabel":8883},"apscheduler","APScheduler (scheduled jobs)","A scheduler inside a Python program: it runs actions at the right time — a reminder at 9 a.m., a check every minute, a report on the 1st. Unlike cron, it lives inside the bot and sees its data.","It sends birthday and subscription-expiry reminders, escalations and the monthly report in the help desk, and marks no-shows in the electronic queue. The time zone is always set explicitly to Europe\u002FMoscow.",{"id":7497,"title":9016,"what":9017,"how":9018,"howLabel":8883},"aiosqlite (non-blocking SQLite)","Asynchronous access to an SQLite database from Python: a query to the database file does not stall the bot while it answers other people.","In the birthday bot, the gaming-club loyalty card and the voice transcription bot the whole database is one SQLite file, and aiosqlite reads and writes it without holding up message handling. A backup is a copy of the file.",{"id":9020,"title":9021,"what":9022,"how":9023,"howLabel":8883},"pymupdf-pdfplumber","PyMuPDF and pdfplumber (working with PDF)","Python libraries for PDF. pdfplumber carefully extracts text and tables; PyMuPDF can also change a page: erase and draw blocks without touching the rest.","pdfplumber reads the PDF statements of four banks in the finance assistant. PyMuPDF stamps an accident commissioner's contacts onto an electronic car insurance policy: the form number, the data and the QR code stay untouched, and the file grows from 380 to 520 KB rather than to 6.5 MB.",{"id":9025,"title":9026,"what":9027,"how":9028,"howLabel":8883},"python-docx-openpyxl","python-docx and openpyxl (Word and Excel files)","Python libraries that build real .docx and .xlsx files with formatting, tables and sheets. People open them in the Word or Excel they know and never notice that a program made the file.","python-docx builds documents for signature from Markdown in the accepted layout (A4, Times New Roman 12) — that is how the network survey set for a museum complex is produced. openpyxl exports the electronic queue to Excel for the lecturer and the finance assistant's operations to XLSX.",{"id":9030,"title":9031,"what":9032,"how":9033,"howLabel":8883},"pillow","Pillow (image processing)","A Python library for images: open, rotate, shrink, save in another format, draw on top.","In the gaming-club loyalty card it works with qrcode to draw the client's personal QR code, which the administrator scans to credit a visit.",{"id":9035,"title":9036,"what":9037,"how":9038,"howLabel":8883},"qr-codes","qrcode and segno (QR codes)","Python libraries that turn a link or text into a QR code. A phone scans it with the camera and opens the right thing at once: a bot, a client profile, a page.","qrcode produces the QR card of a gaming-club client: the code opens the bot with the client's number, and for anyone else the link gives nothing. segno draws the QR code on the accident commissioner's card on an insurance policy.",{"id":7029,"title":9040,"what":9041,"how":9042,"howLabel":8883},"numpy (fast numerical computing)","A Python library for calculations over large arrays of numbers: what ordinary code would take minutes to compute, numpy does in a fraction of a second.","In ToshaMusic numpy breaks a fragment of a track down by frequency (FFT) and finds the spectral cut-off. That is how the bot catches a FLAC rebuilt from an MP3 and a CD master stretched to 24-bit \u002F 192 kHz.",{"id":9044,"title":9045,"what":9046,"how":9047,"howLabel":8883},"cmake-ctest","CMake and CTest (builds and tests)","CMake builds a C++ program with one command on Linux, macOS and Windows, and CTest runs its tests. For the client this means the code builds on more than the developer's machine and the formulas are checked.","The FEFCO box die-line library for a packaging manufacturer is built with CMake and plugs into production software as an ordinary library. Tests for the formulas, nesting and input validation run through CTest.",{"id":9049,"title":9050,"what":9051,"how":9052,"howLabel":8883},"rest-api","REST API","The most common way to build an API: every entity has its own address, and actions are ordinary HTTP requests to get, create or change it. The answer usually comes in JSON.","The online store's REST API is written in FastAPI: catalogue, orders, site settings. The Tech Poly VPN bot drives the Marzban panel through its REST API and keeps money, terms and discounts itself.",{"id":9054,"title":9055,"what":9056,"how":9057,"howLabel":8883},"websocket","WebSocket","A persistent connection between a browser or app and a server over which data flows both ways without new requests. Chats, live updates and some VPN tunnels need it.","On the VPN route, the entry node in Russia proxies the VLESS WebSocket tunnel; a path without the Upgrade header answers 404, like a page that does not exist. A WS+TLS entry point is what the Windows 7 client uses.",{"id":8874,"title":9059,"what":9060,"how":9061,"howLabel":8883},"JSON","A text data format almost every program understands: services pass orders and statuses to each other in it, and settings and exports live in files a person can read.","n8n workflows are exported as JSON files and kept in git, the subscription-sales bot makes its backup as a JSON dump, and the help desk PC program writes a computer's passport into a JSON report.",{"id":9063,"title":9064,"what":9065,"how":9066,"howLabel":8883},"rate-limit","Rate limiting","A rule of \"no more than N requests per period\". It protects a site or bot from password guessing and spam, and an integration from being banned by someone else's service for calling it too often.","After the move from Joomla, the WordPress login page accepts at most 20 requests a minute, the brief on the studio site at most three submissions per 10 minutes from one address, and the electronic queue one request per 0.5 seconds. ToshaMusic caps parallel downloads so the streaming account does not get banned.",{"id":9068,"title":9069,"what":9070,"how":9071,"howLabel":8883},"idempotency","Idempotency","A property of an operation: running it again does not change the result. Confirm pressed twice, a repeated webhook, the same file uploaded again — no duplicate appears.","In Tech Poly VPN a payment is confirmed under a row lock in the database: the second administrator sees who already processed the request, and the subscription is not extended twice. The Joomla import can be re-run without duplicates, and uploading the same statement to the finance assistant again changes nothing.",{"group":9073,"terms":9074},"Servers & OS",[9075,9080,9085,9090,9093,9098,9103,9108,9112,9117,9122],{"id":9076,"title":9077,"what":9078,"how":9079,"howLabel":8883},"ubuntu","Ubuntu and Linux","One of the most widespread Linux-based operating systems — free, stable, with a long support cycle for server releases (LTS).","The standard OS for our servers — Docker containers, bots and VPN nodes run on it.",{"id":9081,"title":9082,"what":9083,"how":9084,"howLabel":8893},"windows-server","Windows Server","The server edition of Windows from Microsoft. Office infrastructure usually lives on it: employee accounts (Active Directory), accounting systems, shared folders and remote desktops (RDP).","We set it up and maintain it when a business already runs on Windows: access rights, backups, secure remote login through VPN instead of RDP exposed to the internet.",{"id":9086,"title":9087,"what":9088,"how":9089,"howLabel":8893},"kali-linux","Kali Linux","A specialised Linux distribution with preinstalled tools for security testing (penetration testing).","For network audits and checking our own infrastructure for vulnerabilities before handing a project over.",{"id":119,"title":119,"what":9091,"how":9092,"howLabel":8883},"A web server and reverse proxy — the part that accepts incoming requests and decides which service handles them.","As the entry point of almost every project: it serves static files, proxies requests to the right containers and checks access before a request goes any further.",{"id":9094,"title":120,"what":9095,"how":9096,"howLabel":9097},"docker","A \"box\" for a program. Any program needs an environment to run: the right library versions, settings, helper files. Docker packs the program and that whole environment into one box — a container. The box can be moved to any server and will work there exactly as it did with us: nothing needs reinstalling or reconfiguring by hand. Much like a shipping container: any port crane can lift it, no matter what's inside.","Moving to another server takes hours, not weeks; one program can't break another on the same server; if something crashes, the container restarts itself. In almost every project the bot, the database and the website live in separate containers. Compose is the \"manifest\" of all a project's boxes so they start with one command; Swarm spreads them across several servers.","Why it matters for business",{"id":9099,"title":9100,"what":9101,"how":9102,"howLabel":8883},"kubernetes","Kubernetes (K3s, Helm)","A system that looks after Docker containers on its own: restarts crashed ones, balances load, scales with traffic.","For projects where load spikes or downtime is critical. K3s is a lightweight version for small servers; Helm is a way to roll out ready-made configurations quickly.",{"id":9104,"title":9105,"what":9106,"how":9107,"howLabel":8883},"mail-server","Your own mail on the domain (Postfix, docker-mailserver, DKIM)","A mail server on your own VPS: addresses like name@your-domain with no monthly fee per mailbox. DKIM, SPF and DMARC are DNS records without which mail lands in spam.","We install Postfix and Dovecot or Mailcow in containers, set up the records and check deliverability to Gmail and Outlook. Important mail can be forwarded to Telegram.",{"id":9109,"title":650,"what":9110,"how":9111,"howLabel":8883},"docker-compose","A file describing which containers make up a project (site, database, bot) and how they connect. One command brings everything up, identically on any server.","Every studio project is a folder with docker-compose.yml and .env on the server. Moving to another server means copying the folder and running one command.",{"id":9113,"title":9114,"what":9115,"how":9116,"howLabel":8883},"voice-server","Your own voice and chat server","A replacement for Discord or Zoom on your own server: voice rooms, chats, screen streaming. Keeps working when public services are blocked, and data never leaves for a third-party cloud.","Our own \"Peregovorka\": voice over WebRTC, chats and streaming with a custom client. We also deploy ready solutions: Mumble, Matrix, Jitsi, depending on the task.",{"id":9118,"title":9119,"what":9120,"how":9121,"howLabel":8883},"cron","Cron and task scheduling","The server's built-in alarm clock: run a backup at night, check slots every five minutes, clean old files weekly. Works with no human involved.","Database and site backups, monitoring, bot broadcasts. Container time is always set to the client's timezone so \"3 a.m.\" means 3 a.m. locally.",{"id":9123,"title":9124,"what":9125,"how":9126,"howLabel":8883},"roundcube","Roundcube (webmail)","A web interface for mail: messages on your own domain are read in a browser, like a familiar mail service, with nothing to install.","It runs next to docker-mailserver on the studio's web server. Mail can be read in Roundcube or in any mail client, and new messages also arrive in a service Telegram chat.",{"group":9128,"terms":9129},"Web & CMS",[9130,9134,9139,9143,9148,9152,9157,9162,9167,9172,9177,9182,9187,9191,9195,9199,9204,9209,9214],{"id":9131,"title":118,"what":9132,"how":9133,"howLabel":8883},"wordpress","The world's most popular content management system (CMS) — lets you edit content without a developer, through an admin panel in the browser.","We build showcases and sites with a familiar admin panel on it, but with a custom theme and no third-party plugins or builders: fewer updates and vulnerabilities, light pages. The “Dykhanie Povolzhya” showcase is built this way. We also migrate and maintain existing sites: three sites moved from the outdated Joomla 3.10 to WordPress, keeping the catalogue, forms and search rankings.",{"id":9135,"title":9136,"what":9137,"how":9138,"howLabel":8893},"html-css-js","HTML, CSS and JavaScript","The basic technologies of any web page: HTML is the structure, CSS the styling, JavaScript the interactivity.","For landing pages and small business sites without a CMS — when load speed matters most and there is no need for an admin panel to edit content regularly.",{"id":9140,"title":121,"what":9141,"how":9142,"howLabel":8883},"seo","A set of measures so that people find your website in search (Google, Yandex) for the right queries, without paid ads.","Semantic markup, Schema.org structured data, a sitemap and correct meta tags — built in during development, not patched afterwards.",{"id":9144,"title":9145,"what":9146,"how":9147,"howLabel":8883},"nuxt","Nuxt (Vue)","A toolkit for building fast websites: pages are served as ready HTML, so they open instantly and are fully readable by search engines. Vue is the interface language inside it.","The studio site and an online store with an admin panel are built on Nuxt. Content lives in files in git or in a database, the design is custom, with no builder templates.",{"id":9149,"title":117,"what":9150,"how":9151,"howLabel":8883},"fastapi","A fast way to write a server side in Python: order intake, cart calculation, an admin panel. Works the same with websites and with bots.","The online store server, the crash-report receiver for apps, APIs for Telegram cabinets. Runs in a container next to the site.",{"id":9153,"title":9154,"what":9155,"how":9156,"howLabel":8883},"ssr","SSR and static (SSG)","Two ways of serving pages. SSR: the server builds the page on every request, handy for stores with live stock. Static: pages are built in advance and stored as files, the server has almost nothing to do and nothing to hack.","The studio's Russian site runs as SSR in one container; the international one is built to static files on a server in Moldova. One project, two build modes.",{"id":9158,"title":9159,"what":9160,"how":9161,"howLabel":8883},"vps","VPS (virtual private server)","Your own slice of a data-centre server with full access: install what you like, pay a fixed monthly fee (from $5–15). Unlike \"website hosting\" it can hold a site, a bot, a database and mail at once.","All studio projects live on VPS: sites, bots, n8n, VPN nodes. One properly configured server comfortably runs several sites in containers.",{"id":9163,"title":9164,"what":9165,"how":9166,"howLabel":8883},"domain","Domain and DNS records","The domain is the site address (devunit-lab.business). DNS records say which server it points to, where to receive mail and who may send mail on its behalf. The domain is always registered to the client, never to the contractor.","We check the domain is owned by the client and set up A, MX, SPF, DKIM, DMARC records and the verification records for search-engine dashboards.",{"id":9168,"title":9169,"what":9170,"how":9171,"howLabel":8883},"ssl","HTTPS and certificates (SSL\u002FTLS, certbot)","The padlock in the address bar. It encrypts what a visitor types on the site and confirms the site is genuine. Without it browsers say \"not secure\" and search engines rank lower.","Let's Encrypt certificates are issued and renewed automatically on the edge nginx; nobody has to remember them.",{"id":9173,"title":9174,"what":9175,"how":9176,"howLabel":8883},"website-builder","Website builder","A service where a site is assembled from ready blocks and lives on the service's servers for a monthly fee (Wix, Squarespace, Tilda and the like). Quick to start, but the site cannot be taken away and features are capped by the plan.","We do not use them. We build sites on the client's own server: code and data belong to the client, and the only fee is the VPS. Why — in the article \"Your own server or a website builder\".",{"id":9178,"title":9179,"what":9180,"how":9181,"howLabel":8883},"cms","CMS and admin panel","A system through which the owner changes texts, prices and products without a developer. WordPress is the best-known ready CMS; a custom admin panel is written separately and does exactly what is needed.","For showcases we install WordPress with a custom theme; for stores with stock we write a FastAPI panel: prices and availability are edited in one table, orders arrive in Telegram.",{"id":9183,"title":9184,"what":9185,"how":9186,"howLabel":8883},"cdn","CDN","A worldwide network of servers that serves site images and files from the point nearest the visitor. Needed when visitors are spread across countries or traffic is high; a small site usually does fine with one server.","We connect one when needed for international sites; for Russian sites a server in Russia and light pages matter more.",{"id":9188,"title":6531,"what":9189,"how":9190,"howLabel":8883},"php","The language WordPress and most of its themes and plugins are written in. Not the trendiest, but every host and every web developer understands it.","We write custom WordPress themes in PHP without third-party builders: pages stay light, with fewer updates and vulnerabilities.",{"id":9192,"title":5945,"what":9193,"how":9194,"howLabel":8883},"markdown","A simple way to mark up text: a hash for a heading, asterisks for bold, a dash for a list. The file is readable without any software, and the site turns it into a page.","Pages, case studies and articles of the studio site live in markdown files in git: editing the site means editing a text file. From the same files we build documentation in Word.",{"id":9196,"title":116,"what":9197,"how":9198,"howLabel":8883},"typescript","JavaScript with strict types: errors show up at build time, not in a visitor's browser. The standard for Nuxt and Vue sites.","The studio site and the online store are written in it: types check that a product or case card is filled in correctly before the deploy.",{"id":9200,"title":9201,"what":9202,"how":9203,"howLabel":8883},"canvas","Canvas (browser graphics)","An area of a web page where a script draws graphics itself: animations, charts, 3D scenes. It needs no external libraries, but has to be done carefully so a phone is not overloaded.","On Valery Afanasiev's business-card site a 3D scanner is drawn on canvas 2D: up to 560 points per part, at most 30 frames per second, drawing only while it is visible, and a single static frame in data-saver mode.",{"id":9205,"title":9206,"what":9207,"how":9208,"howLabel":8883},"csp","CSP (Content-Security-Policy)","A site header that tells the browser where fonts, scripts and images may be loaded from. The browser blocks everything else: foreign code cannot be slipped into the page, and the page does not call other people's servers.","On the demo stand for a museum complex website the default-src 'self' policy forbids every external address: fonts, images and the map snapshot are on our own server, and the number of external loads can be checked right in the viewer's browser.",{"id":9210,"title":9211,"what":9212,"how":9213,"howLabel":8883},"i18n","Multilingual sites (i18n, @nuxtjs\u002Fi18n)","How a site works in several languages: each version has its own URL, the language is detected from the browser, and search engines understand that it is one page in different languages, not duplicates.","The studio site on Nuxt: Russian with no prefix, English at \u002Fen. The @nuxtjs\u002Fi18n module detects the language on the first visit and remembers the choice in a cookie, every page has hreflang, and the English texts live in a separate folder with the same file names.",{"id":9215,"title":9216,"what":9217,"how":9218,"howLabel":8883},"cookie","Cookie","A small record a site leaves in the browser: remember the language, keep you signed in, count visits. Analytics counters on our sites switch on only after the visitor agrees, hence the cookie notice.","On the studio site a cookie stores the chosen language, the online store's admin panel has its own session cookie on a separate subdomain, and on the «Дыхание Поволжья» site analytics loads only after consent. The video downloader bot uses a cookies.txt file for restricted YouTube videos.",{"group":1335,"terms":9220},[9221,9224,9229,9233,9238,9243,9247,9252,9257,9262,9265,9270,9275,9280,9285,9289,9294],{"id":468,"title":468,"what":9222,"how":9223,"howLabel":8883},"An automation builder: it connects services to each other (CRM, spreadsheets, messengers, warehouse) without writing code from scratch for every link.","We deploy it self-hosted — on your server, not in someone else's cloud — and build chains like \"website request → Telegram notification → spreadsheet row\" on it.",{"id":9225,"title":9226,"what":9227,"how":9228,"howLabel":8893},"vpn","VPN","A secure private \"tunnel\" over the internet: it connects your devices and servers as if they were on one local network, even when they are physically in different cities.","We build private VPN networks for secure access to work services and for bypassing blocks — with no public exposure of your infrastructure to the internet.",{"id":9230,"title":643,"what":9231,"how":9232,"howLabel":8883},"wireguard","A modern VPN protocol — fast, simple to configure and light on server resources.","Our default choice for most private VPN networks unless there is a reason to pick something else.",{"id":9234,"title":9235,"what":9236,"how":9237,"howLabel":8883},"tailscale","Tailscale","A ready-made layer on top of WireGuard that removes manual key and route management — all devices find each other in a single mesh network.","When many devices need connecting quickly without a dedicated VPN administrator — the team just installs the app and connects.",{"id":9239,"title":9240,"what":9241,"how":9242,"howLabel":8893},"openvpn","OpenVPN","An older but time-tested VPN protocol.","We use it when a client needs compatibility with legacy corporate equipment or internal policies.",{"id":9244,"title":644,"what":9245,"how":9246,"howLabel":8883},"xray","A protocol and tool for bypassing internet blocks — it disguises VPN traffic as ordinary encrypted web traffic (HTTPS), so it is much harder to block by its \"fingerprint\".","For clients for whom a regular VPN is no longer enough — we build an \"entry node in Russia → exit node in the EU\" scheme with a permanent tunnel and route health monitoring.",{"id":9248,"title":9249,"what":9250,"how":9251,"howLabel":8893},"frpc","frp \u002F frpc","A tool for forwarding ports through an intermediate server — it lets you reach a device behind NAT or a home router without opening dangerous \"holes\" to the internet directly.","For a permanent tunnel between a node in Russia and a standalone server abroad — traffic exits through a trusted point instead of being exposed on every node separately.",{"id":9253,"title":9254,"what":9255,"how":9256,"howLabel":8883},"split-tunneling","Split tunneling","A VPN mode where only part of the traffic goes through the secure channel and the rest goes direct. Games and local sites stay fast, blocked services open.","In the OshaVPN client local sites, games and anti-cheat go direct, everything else through the VPN. For gaming cafés we set up a private VPN with the same rules on every machine.",{"id":9258,"title":9259,"what":9260,"how":9261,"howLabel":8883},"vless-reality","VLESS and Reality","A modern way to disguise a VPN connection as ordinary HTTPS traffic to a well-known site, so it cannot be told apart and blocked. A protocol of the Xray core.","On the studio's VPN routes the entry node accepts VLESS with Reality or WebSocket+TLS and the exit node is abroad. The client picks a live entry point itself.",{"id":2158,"title":2158,"what":9263,"how":9264,"howLabel":8883},"The second engine of a VPN client, an alternative to Xray: the same protocols, a different implementation. Two engines let the client switch when one has problems.","In OshaVPN both engines are connected through a thin layer, so an engine can be swapped without rewriting the app. On Windows 7 only sing-box works.",{"id":9266,"title":9267,"what":9268,"how":9269,"howLabel":8883},"marketplace-api","Marketplace APIs (Wildberries, Ozon)","Programmatic access to a seller account: stock, prices, orders, delivery slots. Through it a bot or workflow does what a manager would do by hand in the dashboard.","Wildberries delivery-slot monitoring with Telegram alerts, stock and price sync, order import. Runs around the clock on the client's server.",{"id":9271,"title":9272,"what":9273,"how":9274,"howLabel":8883},"bitrix24-amocrm","Bitrix24 and amoCRM","The two most widespread CRMs in Russia. Both have APIs and webhooks, so leads from a site or a bot land in them without manual copying, and a deal status change can trigger notifications.","An n8n workflow takes a lead from a bot, creates a deal in the CRM and appends a row to Google Sheets; on a status change the CRM itself messages the manager in Telegram.",{"id":9276,"title":9277,"what":9278,"how":9279,"howLabel":8883},"marzban","Marzban and Remnawave (VPN panels)","Control panels for a VPN service: users, subscriptions, traffic limits, entry nodes. The client gets one subscription link, and the panel serves it the current settings.","Our VPN services run on them: the sales bot creates a user in the panel, and the OshaVPN client reads the subscription and picks a live entry point itself.",{"id":9281,"title":9282,"what":9283,"how":9284,"howLabel":8883},"1c","1C (1C:Enterprise)","The most widespread accounting system in Russia: bookkeeping, warehouse, sales, payroll. Stock and orders usually live in it, so integrations often start there.","In automation projects we connect 1C with marketplaces and a CRM in one chain: stock, orders, prices. In the IT help desk, the PC program also lists the 1C databases on every computer.",{"id":9286,"title":2626,"what":9287,"how":9288,"howLabel":8883},"cloudflare-warp","A free encrypted route to the internet through the Cloudflare network. For a server it is a way to send part of the traffic so the hosting provider sees only a connection to Cloudflare.","On the VPN node in the Netherlands, torrents and 44 AI-service domains exit through WARP: the hosting provider sees only an encrypted connection to Cloudflare, while ordinary web traffic goes direct, with no captchas.",{"id":9290,"title":9291,"what":9292,"how":9293,"howLabel":8883},"fefco","FEFCO (box style catalogue)","The European catalogue of standard corrugated-board box designs: each has its own code — 0201, 0203 and so on. A shared language between the packaging customer, the technologist and the program.","For a cardboard packaging manufacturer we built a die-line library for styles 0200, 0201 and 0203 with blank nesting on a sheet, and restored a catalogue of 325 FEFCO designs with drawings, search and a filter on its website.",{"id":9295,"title":483,"what":9296,"how":9297,"howLabel":8883},"google-sheets","An online spreadsheet people work on together with nothing to install. In a small business it is often the main record, so automation writes there instead of forcing everyone into a CRM.","In lead automation, n8n appends every lead from the Telegram bot as a row in the sheet alongside Bitrix24. The sheet remains a fallback record if the CRM is unavailable.",{"group":1352,"terms":9299},[9300,9304,9309,9314,9318,9323],{"id":9301,"title":296,"what":9302,"how":9303,"howLabel":8883},"postgresql","A reliable relational database — it stores data in strict, linked tables.","Our main choice for production where data integrity matters: orders, customers, payments.",{"id":9305,"title":9306,"what":9307,"how":9308,"howLabel":8893},"mariadb","MariaDB (MySQL)","A relational database forked from MySQL — fully compatible, but more open and actively developed.","In projects with billing and financial records — where transactional integrity matters: a double charge must be impossible even when several admins work at once.",{"id":9310,"title":9311,"what":9312,"how":9313,"howLabel":8883},"mongodb","MongoDB","A NoSQL database that stores data as flexible documents rather than rigid tables.","We use it when the data structure changes often or isn't fully defined up front — no need to rework tables with every change.",{"id":9315,"title":1362,"what":9316,"how":9317,"howLabel":8883},"redis","An ultra-fast data store that lives in the server's memory.","For caching (so the main database isn't hit on every request) and task queues — for example, sending messages in the background.",{"id":9319,"title":9320,"what":9321,"how":9322,"howLabel":8893},"clickhouse","ClickHouse","A database built for analytics and large data volumes — it computes aggregates over millions of rows quickly.","When you need reports and dashboards over large event volumes, not just record storage.",{"id":9324,"title":1366,"what":9325,"how":9326,"howLabel":8883},"sqlite","A compact database that lives in a single file — no separate database server required.","For small self-hosted services with moderate load where a separate database would be overkill — easier to deploy, easier to back up.",{"group":1370,"terms":9328},[9329,9333,9337],{"id":9330,"title":1374,"what":9331,"how":9332,"howLabel":8883},"esp32","An inexpensive microcontroller with Wi-Fi and Bluetooth on board — the basis for smart devices, sensors and other \"smart\" electronics.","Firmware for a client's specific task — for example, a device that counts hits in a game system by itself and keeps working even when the server connection drops.",{"id":9334,"title":1378,"what":9335,"how":9336,"howLabel":8893},"mqtt","A lightweight messaging protocol for devices with limited resources — designed for IoT, where low load on the communication channel matters.","For communication between devices and a central server in embedded projects — devices publish events, the server receives and processes them.",{"id":9338,"title":1382,"what":9339,"how":9340,"howLabel":8883},"lora","A long-range, low-power radio protocol — it works where there is no Wi-Fi or mobile network, over distances of several kilometres.","As a backup channel for emergency commands in devices at venues where the regular network is unreliable or absent.",{"group":9342,"terms":9343},"Media & Communications",[9344,9348,9352,9356,9361,9366,9370,9375],{"id":9345,"title":1321,"what":9346,"how":9347,"howLabel":8883},"streaming","Video transport protocols: SRT delivers a stream to the server reliably over an unstable link; HLS and WHEP let viewers watch in the browser with low latency.","We build our own streaming servers — a replacement for solutions that drop the broadcast on a poor link or let anyone in to watch.",{"id":9349,"title":1324,"what":9350,"how":9351,"howLabel":8893},"webrtc","A technology for real-time voice and video in the browser, directly between participants, with no page reloads.","For voice inside our own services — for example, mesh voice chat where audio doesn't pass through an intermediate server at all.",{"id":9353,"title":312,"what":9354,"how":9355,"howLabel":8883},"ffmpeg","The Swiss army knife for video and audio — it converts, cuts and analyses media files in practically any format.","We check the real parameters of an audio or video stream (not what the description says) and process media in bots' background jobs.",{"id":9357,"title":9358,"what":9359,"how":9360,"howLabel":8893},"asterisk","Asterisk (IP telephony)","An open-source software PBX (telephone exchange) — it turns an internet connection into full telephony: calls, queues, forwarding, call recording, with no landline.","For a company's internal communications and customer lines — with custom call-handling scenarios instead of the rigid settings of boxed PBX systems.",{"id":9362,"title":9363,"what":9364,"how":9365,"howLabel":8883},"yt-dlp-streamrip","yt-dlp and streamrip (media downloading)","Download engines: yt-dlp fetches video and audio from hundreds of sites (YouTube, TikTok, VK Video), and streamrip fetches music from Qobuz, Deezer and Tidal. A bot hands them a link and gets a file back.","The video downloader bot runs on yt-dlp: 12 platforms, quality up to 4K, and Vimeo and Coub were added with no platform-specific code. In ToshaMusic, streamrip downloads Hi-Res with a cap on parallel downloads, so the streaming account does not get banned.",{"id":7028,"title":9367,"what":9368,"how":9369,"howLabel":8883},"mutagen (audio file tags)","A Python library for reading and writing audio tags: artist, album, cover art and, for FLAC, the real bit depth.","In ToshaMusic it refines the FLAC bit depth when ffprobe reports the container's 32 bits instead of the real 24. The file name and tags are built from measured parameters only.",{"id":9371,"title":9372,"what":9373,"how":9374,"howLabel":8883},"mediamtx","MediaMTX (media server)","A streaming server: it receives the stream from the streamer's PC and hands it to viewers in different forms — to a browser, to VLC, with under a second of latency or in a way that survives a mobile network.","In Peregovorka, MediaMTX takes the stream in over SRT and delivers it over HLS, WHEP and RTSP. The server does not re-encode video: the streamer's graphics card encodes both qualities, so a powerful machine is not needed.",{"id":1953,"title":9376,"what":9377,"how":9378,"howLabel":8883},"coturn (TURN server)","A relay for browser calls: when two participants cannot connect directly because of a router or the network, the audio goes through a TURN server. It cannot decrypt the conversation.","Peregovorka runs its own coturn next to the WebRTC voice: credentials are temporary and live for an hour, and the secret never leaves the server.",{"group":1386,"terms":9380},[9381,9385,9389,9394,9399,9404],{"id":9382,"title":647,"what":9383,"how":9384,"howLabel":8893},"grafana","Dashboards with real-time charts of server and service health.","To see the load, the error count and whether everything is alive — on one screen, without logging into every server separately.",{"id":9386,"title":646,"what":9387,"how":9388,"howLabel":8883},"prometheus","A metrics collection system — it gathers numbers about how servers and services are doing.","It feeds Grafana with data and can send an alert itself when something goes wrong — before the client notices.",{"id":9390,"title":9391,"what":9392,"how":9393,"howLabel":8883},"cicd","CI\u002FCD (GitLab CI, GitHub Actions)","An automatic pipeline: as soon as code changes, it is checked and shipped to the server on its own — no manual steps.","In almost every project — faster releases, less risk of forgetting or breaking something by hand during deployment.",{"id":9395,"title":9396,"what":9397,"how":9398,"howLabel":8883},"health-check","Health check","A regular automatic check that a service is alive and answering: not just that the process runs, but that the database reads and the page opens. It is what restarts a container and alerts the administrator.","In Peregovorka the chat answers \u002Fhealthz by reading from and writing to the database, and a watchdog restarts a stuck container at most three times an hour. The finance assistant is checked from outside every 5 minutes: containers, database, the \"bot is alive\" mark and the dashboard address.",{"id":9400,"title":9401,"what":9402,"how":9403,"howLabel":8883},"secrets-env","Secrets and .env","Bot tokens, database passwords and API keys are kept apart from the code: in a .env file on the server or inside the service itself. The code can then be shown or handed over without exposing access.","Gemini keys and bot tokens are set in .env, and in n8n workflows the keys live in n8n itself, not in the workflow file. Our apps mask subscription links, keys and tokens on the device before a log is sent to the developer.",{"id":9405,"title":9406,"what":9407,"how":9408,"howLabel":8883},"rollback","Rollback","A return to the previous working state when a new version or an operation goes wrong: an app update, a deploy, a payment, a database change.","The OshaVPN updater on Windows rolls back the file replacement if it fails. In Tech Poly VPN, if the Marzban panel does not answer after a payment, the request goes back to pending and the discount is not burned. Where there are no migrations, as in the finance assistant, a version is rolled back from a database copy.",{"group":9410,"terms":9411},"Search & AI",[9412,9417,9422,9426,9429,9434,9439,9444,9449,9454],{"id":9413,"title":9414,"what":9415,"how":9416,"howLabel":8883},"indexing","Indexing","The process in which a search engine (Google, Bing, Yandex) finds a page, reads it and stores it in its database. Until a page is in that database, it appears neither in search nor in AI assistants' answers.","After every deploy the site itself tells Bing and Yandex which pages changed (IndexNow), and we submit important addresses for recrawl in the webmaster tools by hand. Bing picks a page up in 1–3 days, Google in 1–3 weeks.",{"id":9418,"title":9419,"what":9420,"how":9421,"howLabel":8883},"sitemap","Sitemap (sitemap.xml)","A file listing every address on the site that search engines read. It tells them which pages exist and in which languages, so nothing gets missed.","The sitemap is built automatically from the site content: a new case study, article or glossary term lands in it by itself. Product screenshots go in too, for image search.",{"id":9423,"title":2336,"what":9424,"how":9425,"howLabel":8883},"robots-txt","A small file at the site root that tells robots what they may and may not read. One wrong line in it can accidentally hide the whole site from search.","Our robots.txt opens the site to all search engines and AI bots (GPTBot, ClaudeBot, Bingbot and others), closing only service addresses. In staging mode one variable hides the site from indexing.",{"id":4009,"title":4009,"what":9427,"how":9428,"howLabel":8883},"A tag in the page code saying \"the same page in another language lives over there\". Thanks to it a search engine shows Russian users the Russian version and English speakers the English one, and does not treat them as duplicates.","Every page on the studio's Russian and international sites knows its pair: \u002Fuslugi\u002Fboty ↔ \u002Fservices\u002Fbots. The pairs are declared both in the page code and in the sitemap.",{"id":9430,"title":9431,"what":9432,"how":9433,"howLabel":8883},"json-ld","Structured data (JSON-LD, schema.org)","A block of data on the page invisible to visitors that spells out what the page is: a service with a price, an article with an author, a question with an answer, an organisation with an address. Search engines and AI read it directly, with no guessing.","Every page of the site carries its own markup: Service and Offer with prices on services, Article on case studies and posts, FAQPage on questions, DefinedTerm on glossary terms, BreadcrumbList on breadcrumbs, Organization on the home page. This is what lets an assistant answer \"a bot from $80\" with a link.",{"id":9435,"title":9436,"what":9437,"how":9438,"howLabel":8883},"llms-txt","llms.txt","A file for AI assistants: a short description of the site and a list of pages in plain text, so a model can read the gist in one request without parsing design and menus. A sitemap, but for AI.","Both studio sites serve \u002Fllms.txt (a catalogue) and \u002Fllms-full.txt (all services with prices, case studies, articles and questions). Both are generated from content automatically; nothing to edit by hand.",{"id":9440,"title":9441,"what":9442,"how":9443,"howLabel":8883},"indexnow","IndexNow","A protocol by which a site itself tells search engines \"these pages changed\" without waiting for a crawl. Supported by Bing (which ChatGPT and Copilot search through), Yandex and others; Google does not support it.","Submission is built into the deploy scripts: at the end of every deploy all sitemap addresses go to api.indexnow.org and to Yandex. Nothing to click separately.",{"id":9445,"title":9446,"what":9447,"how":9448,"howLabel":8883},"ai-search","AI search and assistant answers","A mode where a question is answered not by a list of links but by an assistant: ChatGPT with search, Gemini, Google AI Overview, Copilot, Perplexity. The assistant takes the answer from a regular search index, so the same pages get in as in search, only later.","We prepare pages for such answers: questions and answers in the client's words, prices and timelines in structured data, llms.txt, an About page with facts. Showing up in answers takes 2–8 weeks after indexing, and nothing speeds that up.",{"id":9450,"title":9451,"what":9452,"how":9453,"howLabel":8883},"webmaster-tools","Webmaster tools (Search Console, Bing Webmaster, Yandex Webmaster)","Free search-engine dashboards where the owner verifies the site, submits the sitemap, asks for pages to be recrawled and sees which queries show the site and which pages dropped out.","Both studio sites are verified in all three. After a major change we submit the changed addresses for recrawl; once a month we check queries and errors. The procedure is written down so the owner can do it alone.",{"id":9455,"title":9456,"what":9457,"how":9458,"howLabel":8883},"llm","Language model (LLM)","A program that understands and writes text: ChatGPT, Gemini, Claude, DeepSeek. On its own it knows nothing about your site until it reads it through search or by link.","We use them in bots through APIs: voice transcription, parsing bank statements and receipts, a site chat consultant with facts from the price list. The model answers only from what is in the prompt so it does not invent prices.",{"group":9460,"terms":9461},"Bots & Telegram",[9462,9466,9471,9476,9481],{"id":9463,"title":9463,"what":9464,"how":9465,"howLabel":8883},"aiogram","A Python library for Telegram bots. It handles incoming messages, buttons and flows; the bot itself, its database and integrations are written on top of it.","The base of all our Telegram bots: subscription sales, the ticket system, the finance assistant. The bot runs in a container on the client's server.",{"id":9467,"title":9468,"what":9469,"how":9470,"howLabel":8883},"telegram-mini-app","Telegram Mini App","A full web page that opens inside Telegram from a bot button: a catalogue, a cabinet, a form. The user is already signed in, nothing to install.","We build cabinets inside Telegram: plans, client roles, invitation codes. Example: the web service with a Telegram cabinet from the UNLOCK case.",{"id":9472,"title":9473,"what":9474,"how":9475,"howLabel":8883},"telegram-login","Telegram Login","A way to sign in to a site or service with one \"Log in with Telegram\" button, no password or registration. The service receives a verified user account.","We use it on service cabinets and on VPN access delivery: the user is recognised by Telegram, and the config and subscription are tied to them.",{"id":9477,"title":9478,"what":9479,"how":9480,"howLabel":8883},"sbp","SBP (Russian instant payments)","Payment by QR code or link from a phone through the bank, without a card and with a low merchant fee. Suited to subscriptions and one-off payments in bots.","Our subscription-sales bot accepts SBP payments, extends access by itself and keeps the books, with no manual reconciliation.",{"id":9482,"title":9483,"what":9484,"how":9485,"howLabel":8883},"vk-bots","VK bots","The same bot, but in a VK community: it answers messages, takes orders, hands out files. Needed as the main channel for a VK audience and as a backup when Telegram is unavailable.","We build VK bots on a shared database with Telegram: one dataset, two entrances. That is how access delivery in the VPN service and ordering in the \"Dykhanie Povolzhya\" showcase work.",{"group":1290,"terms":9487},[9488,9493,9498,9503,9508,9513,9517,9521,9525,9530,9535,9540],{"id":9489,"title":9490,"what":9491,"how":9492,"howLabel":8883},"kotlin","Kotlin (Android)","The main language of Android apps. A native Kotlin app runs faster and more reliably than a \"wrapper\" around a website and has access to all the phone hardware.","The OshaPlay audio player with USB DAC output and the OshaVPN client are written in Kotlin. Builds are signed and update over the air.",{"id":9494,"title":9495,"what":9496,"how":9497,"howLabel":8883},"dotnet",".NET, WinForms and WPF (Windows)","Microsoft's platform for Windows programs. A .NET Framework 4.8 app runs even on Windows 7 with nothing extra installed.","The OshaVPN client for Windows 7 is built on .NET Framework 4.8 and WinForms with logic shared with the main client.",{"id":9499,"title":9500,"what":9501,"how":9502,"howLabel":8883},"ota-update","Over-the-air updates (OTA)","The app checks by itself whether a new version is out, downloads and installs it, so the user never hunts for a file. The version list is signed with a key so it cannot be spoofed.","OshaVPN checks hourly: Android downloads an APK for its architecture, Windows downloads only changed files and rolls back on failure. There is a tester channel.",{"id":9504,"title":9505,"what":9506,"how":9507,"howLabel":8883},"crash-report","Crash reports","When an app crashes or cannot connect, it sends a log, its version and the network state to the developer's server. The developer sees the problem before the user writes in.","Our own Python receiver in Docker on the client's server: secrets are masked on the device, and when the connection drops the report waits in a queue. No dependence on foreign services.",{"id":9509,"title":9510,"what":9511,"how":9512,"howLabel":8883},"app-stores","RuStore and Google Play","Android app stores. RuStore is Russian and free to publish in; Google Play is worldwide with a one-off $25 fee and developer verification. A first install can also be distributed directly as an APK file.","We prepare signing, the build, the store page and moderation replies. For internal apps a direct APK link with over-the-air updates is often enough.",{"id":9514,"title":804,"what":9515,"how":9516,"howLabel":8883},"jetpack-compose","The modern way to build Android app interfaces in Kotlin: screens are described in code, and the layout and theme adapt to the phone and the system settings.","The Android interfaces of OshaVPN and the OshaPlay audio player are built with Compose and Material 3: an adaptive layout and a theme that follows the system.",{"id":9518,"title":3227,"what":9519,"how":9520,"howLabel":8883},"media3-exoplayer","Google's library for playing audio and video in Android apps: background playback, a notification with buttons, lock-screen controls.","The OshaPlay player is built on Media3: background playback and the playback service. Our own C++ output to a USB DAC plugs into Media3 as a custom AudioSink, which is why a track reaches the DAC bit for bit.",{"id":3150,"title":9522,"what":9523,"how":9524,"howLabel":8883},"libtorrent4j (built-in torrent client)","A library that embeds a full torrent client right into an Android app, with no separate program.","In OshaPlay it is the built-in torrent client: whatever is found in the combined search across four sources downloads straight into the library.",{"id":9526,"title":9527,"what":9528,"how":9529,"howLabel":8883},"android-ndk","Android NDK (C++ inside an app)","Tools for writing part of an Android app in C++. They are needed where timing and precision matter: audio, video, work with hardware.","In OshaPlay the audio stream to a USB DAC with a ring buffer and our own resampler with distortion below −130 dB are written in C++ through the NDK.",{"id":9531,"title":9532,"what":9533,"how":9534,"howLabel":8883},"apk","APK (install from a file)","The install file of an Android app. It can be handed out as a link or from a bot, with no app store: a person downloads the file and installs the app themselves.","OshaVPN and OshaPlay are first installed from an APK link and then update themselves. For the first install we hand out a universal APK, and self-updates then download the build for the specific phone's architecture.",{"id":9536,"title":9537,"what":9538,"how":9539,"howLabel":8883},"push-notifications","Push notifications","Notifications that reach a phone or computer even when the app or tab is closed: a new message, a stream starting, an order.","In Peregovorka pushes reach each device without duplicates: while a person reads a chat on one device, the others get nothing. Sound can be muted per conversation.",{"id":9541,"title":9542,"what":9543,"how":9544,"howLabel":8883},"deep-link","Deep link (a link to the right place)","A link that opens not just an app or a bot but a specific screen with the right data: a client profile, a subscription, an order.","A gaming-club client's QR card is a link that opens the bot with the client's number. An oshavpn:\u002F\u002F link from the bot adds the subscription to the VPN client at once, and the «Дыхание Поволжья» cart opens the VKontakte community chat with the order code.",{},"en\u002Fglossary","s2r9_-cuuQYAyfwfWqQKgBmOXd3BjfN4NrqPIlKkW6k",1790875025906]