惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

www.infosecurity-magazine.com
www.infosecurity-magazine.com
D
DataBreaches.Net
T
Tailwind CSS Blog
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
F
Full Disclosure
V2EX - 技术
V2EX - 技术
N
News and Events Feed by Topic
Help Net Security
Help Net Security
L
LangChain Blog
Y
Y Combinator Blog
宝玉的分享
宝玉的分享
Google Online Security Blog
Google Online Security Blog
P
Proofpoint News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
The Register - Security
The Register - Security
B
Blog RSS Feed
N
Netflix TechBlog - Medium
N
News | PayPal Newsroom
TaoSecurity Blog
TaoSecurity Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Vulnerabilities – Threatpost
B
Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
I
Intezer
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园_首页
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
AI
AI
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cyberwarzone
Cyberwarzone
P
Proofpoint News Feed
Google DeepMind News
Google DeepMind News
G
GRAHAM CLULEY
Vercel News
Vercel News
罗磊的独立博客
MyScale Blog
MyScale Blog
Last Week in AI
Last Week in AI
博客园 - 司徒正美
C
CERT Recently Published Vulnerability Notes
GbyAI
GbyAI
Scott Helme
Scott Helme
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Troy Hunt's Blog
A
About on SuperTechFans
P
Privacy International News Feed

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
The Evolution of a C++ Telegram Bot: From "Spaghetti" in main() to OOP, In-Memory Cache, and Fibonacci Mutes
H-D-OWL · 2026-05-18 · via DEV Community

Hi everyone!

In this article, I'll talk about the evolution of my project — GroupModerBot, a bot for moderating Telegram groups. I'll show how the project went from the first 'all-in-one-file' version to a well-thought-out architecture with OOP, in-memory caching, safe command execution, and non-standard user punishment algorithms.

Backstory

After finishing my previous project, I immediately decided to take on a new one: 'Can't sit idle, or I'll forget everything!'. At first, I wanted to write a full-fledged calculator with string parsing that could handle tangents and roots. I created the project, wrote some code, but quickly realized: either I'd be constantly peeking at tutorials, or I'd drown in writing a non-optimal 'reinvented wheel'.

I decided to switch to something else that recently caught my eye on YouTube — a Telegram bot. I saw it on the "Maxim C++" channel. He's the only one who made a full guide on creating a bot in C++ on YouTube. Generally, few people have raised this topic yet. So, it's a niche topic for which there's a foundation (in the form of a guide "Telegram Bot in C++"), but no one can tell me what to do next.

A great task to learn something new and create something quite unique.

How It All Began

Since I took the 'Telegram Bot in C++' guide as a base, the core libraries were the same as in the tutorial. My tech stack looked like this:

• Language: C++ 20

• Libraries: tgbot-cpp (handling Telegram API) and SQLiteCpp (a wrapper for SQLite).

Like many projects, the first version of my bot was written with the "as long as it works" principle in mind. All the program logic was concentrated in a single file, TestTGBot.cpp, inside a huge main() function. However, even this early version contained some good decisions:

  1. I immediately thought that forcing a user to dig into the code to change the bot token or database path is inconvenient. Plus, it would require recompiling the .exe. So, I implemented a simple solution: reading from DataForBot.txt, where the first line is the DB path and the second is the bot token:

    ifstream fileDataForBot("DataForBot.txt", ios_base::in);
    
    . . .
    
    for (int i = 0; fileDataForBot.good() && i < 2; ++i)
    {
        string fileLine{};
        getline(fileDataForBot, fileLine);
    
        switch (i)
        {
        case 0:
            if (!fileLine.empty())
                pathToDatabase = fileLine;
            break;
        case 1:
            if (!fileLine.empty())
                botToken = fileLine;
            break;
        }
    }
    
    fileDataForBot.close();
    
  2. To work properly with a database, you need to be sure that it has all the necessary tables and columns. Otherwise, for example, you might send an SQL query to a non-existent table, which will cause an exception and the program to crash.

    To prevent this, I created const unordered_map<string, vector<string>> dataBasesAndColumnsNames (I know the name is wrong) which stores the names of tables and columns of these tables that should be in the database and a loop that goes through dataBasesAndColumnsNames, in which, using the already built-in tableExists function, I check for the presence of the table and, if it does, a new loop begins. In which I, already with the help of the DataBaseHasColumn, function written by me, check for columns in tables. If the table or column does not exist, an exception of class SQLite::Exception will be thrown indicating the absence of the specified element.

  3. Several steps were taken to determine the bot's owner. First, a table named Managers was created in the database, containing the columns IdManager, FirstNameManager, and LastNameManager. Then, an isTableEmptyfunction was created that internally calls the SQL query: "SELECT 1 FROM " + tableName + " LIMIT 1". This SQL query checks whether the table contains at least one record. If there are no records, then the bot has no owner.

    If there is no owner, a confirmation code (a 64-character string of numbers) is generated and displayed in the console. The first user to send /start [confirmation code] was recorded in the database as the owner of the bot. And the confirmation code is invalidated. It's simple but rock-solid protection against unauthorized hijacking.

Despite these good solutions, the monolithic main() became difficult to read, even though I tried to add delimiters in the code. And besides assigning the bot owner using the /start command, there was no interaction with Telegram.

But that's nonsense. I'm young, I have plenty of time. And how long did it take me to do all this? Just 5 months!? Damn.

The Moment of Realization

5 months. Of course, I didn't work on the bot all that time. I spent half of that time working at my day job. But is that an excuse? No. I underestimated the project. I didn't think it through. I just wrote down what I thought would probably be needed without a plan. I need to make a plan, to confirm exactly what I'm doing.

So, in three months I'm planning to quit my job. That should be enough time — I have to finish the bot in the next three months.

I've set the time. But what kind of bot exactly will I make? Hmm. I need something simple but also useful. A game bot – definitely not. Creating a game is a separate process that requires graphics and learning a lot of things unrelated to Telegram. An AI chat bot – I have no idea how to work with AI in C++. Besides, there's not much room for using a database here. So no. A moderator bot seems like a good option. There are already standard functions for creating its functionality (banChatMember, unbanChatMember, restrictChatMember). And the database can be used to store admins, groups, warnings, and related data. So I'll choose to make that.

I've figured out the bot's purpose. All that's left is to come up with a name for it. It should be simple, informative, unique, and its function should be reflected in the name. So, "ModerBot"? Well, no, it's not clear what exactly it's moderating. And that name is already taken on Telegram. So, let's go through the options. "@group_moder_bot" is not taken. It's a perfectly clear and short name. Let it be. So, from now on, my bot will be called "GroupModerBot".

The Architectural Leap

Three months should be enough to complete the project. But that's still a time limit. I need to change my approach to the project's architecture. I need to move to something meaningful, clearly structuring the problems. Solve them in a way that ensures ease of use, security, and maintainability. Otherwise, I might not meet the deadline.

General Changes

All the code was in main(). This created a mess and made the code difficult to read. Also main() performs initialization and work with the database and the bot.

To address these issues, the project incorporated three main architectural pillars:

  • GroupModerBot.cpp is the entry point (main()). It initializes the database and the bot, and handles fatal exceptions.

  • BotDatabase is a class (in BotDatabase.h and BotDatabase.cpp) that completely isolates the database and implements caching.

  • BotController is a class (in BotController.h and BotController.cpp) that is the "brain" of the bot. It connects the Telegram API, the business logic of commands, and the database.

And also two additional ones:

  • Logging — the Logging.h and Logging.cpp files, which contain the logging function and everything related to it.

  • Constants.h — a file containing text constants. This file is used to eliminate "magic" data and code duplication.

I also considered the need for visual and practical separation between my code and others'. I removed all using namespaces from the code and created a main project namespace — gmb (GroupModerBot). This contained all my code.

Also additional namespaces have been added:

  • logging (Logging.h and Logging.cpp) — contains logging-related code.

  • consts (Constants.h) — contains general constants.

  • msg containing log and chat (Constants.h) — contains constants for logging and user responses.

Improving Individual Systems

  1. Reading the configuration file: Initially, I did this by simply reading the first two lines. However, this approach is impractical. The user has no visual cues and can easily confuse the order of the fields.

    This is also a problem for the developer: adding new parameters in the middle of the file will break the structure, forcing users to edit the configuration file and the developer to rewrite the parsing logic.

    I came up with the perfect solution to these problems.

    The original method is a vector, in which the data is arranged one after another. This is inconvenient in my case.

    I implemented it as in unordered_map. Data is now searched by special keys (DbPath=, BotToken=). This makes it clear which data is where. Furthermore, their order in the file no longer matters:

    std::ifstream fileDataForBot(std::string(gmb::consts::configFile), std::ios_base::in);
    
    . . .
    
    while (fileDataForBot.good())
    {
        std::string fileLine{}; 
        getline(fileDataForBot, fileLine);  
    
        fileLine.erase(std::remove(fileLine.begin(), fileLine.end(), '\r'), fileLine.end());    
    
        if (const size_t offDbPath = fileLine.find(gmb::consts::dbPathKey); offDbPath != std::string::npos) 
        {       
            dbPath = fileLine.substr(offDbPath + gmb::consts::dbPathKey.size());    
        }   
        else if (const size_t offBotToken = fileLine.find(gmb::consts::botTokenKey); offBotToken != std::string::npos)  
        {       
          botToken = fileLine.substr(offBotToken + gmb::consts::botTokenKey.size());    
        }
    }
    
    fileDataForBot.close();
    
  2. Working with the database: Initially, I created an const unordered_map<string, vector<string>> dataBasesAndColumnsNames that stored the names of database tables and columns. If I had left it as is, any name change or addition of a table or column to the database would have resulted in manually rewriting numerous SQL queries, which would have been hell. So, I created a custom structure, TableName.

    The TableName structure is a basic one and is needed to easily create structures describing the structure of a specific table (for example, BotAdminsTableName, GroupsTableName, etc.). It contains two fields: const std::string_view nameTable and const std::vector<std::string_view> columnNames. These fields represent the table structure. And three functions: std::string GetColumnNamesBetweenCommas() const, std::string GetPlaceholders() const, and std::string GetColumnsEqualPlaceholders() const. These functions work with fields regardless of the size of their contents. They are designed to simplify and automate the generation of SQL queries:

    struct BotAdminsTableName : TableName
    {
        static constexpr std::string_view idColumnName = "Id";
        static constexpr std::string_view firstNameColumnName = "FirstName";
        static constexpr std::string_view lastNameColumnName = "LastName";
        static constexpr std::string_view usernameColumnName = "Username";
        static constexpr std::string_view isBotColumnName = "IsBot";
        static constexpr std::string_view isPremiumColumnName = "IsPremium";
        static constexpr std::string_view isBotOwnerColumnName = "IsBotOwner";
    
        BotAdminsTableName() : TableName{ "BotAdmins", {idColumnName, firstNameColumnName, lastNameColumnName, usernameColumnName, isBotColumnName, isPremiumColumnName, isBotOwnerColumnName} } {};
    };
    
    void BotDatabase::AddAdmin(const Admin& user)
    {
        if (user.username.empty())
            throw std::runtime_error{ "The user must have a Telegram username (with @)" };
    
        if (IsAdmin(user.id))
            throw std::runtime_error{ "TgBot::User " + user.username + " is already an administrator" };
    
        SQLite::Statement query{ *botDatabase,
            "INSERT INTO "
            + std::string(botAdminsTableName.nameTable)
            + " ("
            + botAdminsTableName.GetColumnNamesBetweenCommas()
            + ") VALUES("
            + botAdminsTableName.GetPlaceholders()
            + ')' };
    
        query.bind(1, user.id);
        query.bind(2, user.firstName);
        query.bind(3, user.lastName);
        query.bind(4, user.username);
        query.bind(5, static_cast<int64_t>(user.isBot));
        query.bind(6, static_cast<int64_t>(user.isPremium));
        query.bind(7, static_cast<int64_t>(user.isBotOwner));
    
        query.exec();
    
        UpsertCache(user);
    }
    
    

    To store and use data from tables, I created structures: Admin, Group, and GroupSettings. They differ only in their fields. Having the same structure:

    struct Admin
    {
        int64_t id{};
        std::string firstName{}, lastName{}, username{};
        bool isBot{}, isPremium{}, isBotOwner{};
    
        auto operator<=>(const Admin&) const = default;
    
        Admin() = default;
    
        Admin(int64_t id, std::string firstName, std::string lastName, std::string username, bool isBot, bool isPremium, bool isBotOwner)
            : id(id), firstName(firstName), lastName(lastName), username(username), isBot(isBot), isPremium(isPremium), isBotOwner(isBotOwner) {
        }
    };
    

Business Logic

Example

Initially, there was only one command, /start, which could only assign the bot owner. Now I've created a full-fledged warn system with roles for bot owner, bot administrator, and guest.

There are 14 commands in total. They are divided into groups:

• Informational:

  • /start — Describes the commands available to the user depending on their role.

• Working with the bot:

  • /botActive — Activates the bot. The bot begins executing commands in the group.

  • /botDeactive — Deactivates the bot. The bot stops executing commands in the group.

• Working with groups:

  • /groups — Shows a list of all groups containing the bot.

  • /setGroupUniqueTitle — Changes the group's uniqueTitle (the uniqueTitle is needed for proper group identification).

• Working with admins:

  • /admins — Shows a list of all bot administrators.

  • /addAdmin — Generates an administrator verification code if the sender of the command is the bot owner. Otherwise, it accepts the bot owner's verification code.

  • /removeAdmin — Removes an administrator using the index number from /admins.

• Warns settings:

  • /setWarnBanSettings — Sets the number of warnings before banning a group member. Default: 5.

  • /setWarnMuteSettings — Sets the number of warnings after which a group member will be muted. Default: 3.

Instead of hard-coding the mute time (for example, always ban for a day), it was decided to calculate the duration of the mute based on Fibonacci numbers. The mute duration (in days) is calculated using the formula: Mute Duration = Fibonacci(UserWarns - QuantityWarnToMute):

int64_t BotController::Fibonacci(const size_t numberOfNumber) const
{
    int64_t num = 1, previousNum = 1;

    for (size_t i = 1; i < numberOfNumber; ++i)
    {
        const int64_t temp = previousNum;

        previousNum = num;

        num += temp;
    }

    return num;
}

Enter fullscreen mode Exit fullscreen mode

• Working with warns:

  • /addWarn — Adds the specified number of warnings to a user. Default: 1.

  • /removeWarn — Removes the specified number of warnings from a user. Default: 1.

  • /setWarn — Sets the specified number of warnings for a user.

  • /viewWarn - Displays the current number of warnings the user has.

With these commands, you can easily moderate multiple groups simultaneously. If you need help, you can assign an administrator who can maintain order but won't have all the powers of the bot owner.

Features

  1. In-memory cache: The new version implements an internal in-memory cache based on std::unordered_map using the Cache structure. Upon startup, the bot completely unloads the required data (the list of admins and groups, group settings) into RAM. Now, data is read from std::unordered_map in constant O(1) time, rather than directly from the database. This reduces the overall load and speeds up bot operation:

    struct Cache
    {
        inline static std::unordered_map<int64_t, Admin> admins{};
    
        inline static std::unordered_map<int64_t, Group> groups{};
        inline static std::unordered_map<std::string, int64_t> groupIdsByUniqueTitle{};
    
        inline static std::unordered_map<int64_t, GroupSettings> groupsSettings{};
    };
    
    const BotDatabase::Group* BotDatabase::GetGroup(const int64_t id) const
    {
        const auto it = Cache.groups.find(id);
    
        if (it != Cache.groups.end())
        {
            return &it->second;
        }
        else
        {
            return nullptr;
        }
    }
    
  2. Fault tolerance: There's no room for security and reliability in "as long as it works" code. But that stage is behind us. So I took the bot's reliability seriously. Almost all of my code works with the tgbot and SQLiteCpp libraries. Their functions can throw exceptions at any time. So I did the following:

    • Database and bot initialization: The code is wrapped in a standard try-catch. If an exception is thrown before or during their initialization, it is considered a fatal error. The cause is logged, and the program stops running, since operation is impossible without initializing the database and bot.

    • Command processing: To save time and effort, I wrote a template function, SafeExecute. It accepts logging::ContextLog and a const Func func (template). It contains a try-catch inside. It has a catch for each special exception from the libraries, std::exception, and (. . . ), for convenient logging. This provides complete protection against exceptions from the wrapped function, as well as automatic logging of called commands and errors.

    Example

    SafeExecute also has a lambda function called SafelySendMessage. It attempts to send an error log to the user who called it. If this fails, nothing else happens, and nothing is written to the log. If it were to write about its failure in the log, the log would be filled with garbage if there was no internet connection:

    template<typename Func>
    void SafeExecute(const logging::ContextLog& contextLog, const Func func) noexcept
    {
        auto SafelySendMessage = [this](const std::string& id, const std::string& textMessage) noexcept
            {
                try
                {
                    bot.getApi().sendMessage(id, textMessage);
                }
                catch (...)
                {
                    //
                }
            };
    
        try
        {
            const logging::OnEventResult onEventResult = func();
    
            if (!onEventResult.logMsg.empty())
                logging::Log(logging::LogSource::Program, logging::LogType::Event, contextLog, onEventResult.logMsg);
    
            if (!onEventResult.chatMsg.empty())
                SafelySendMessage(contextLog.userId, (contextLog.title.empty() ? "" : contextLog.title + ": ") + onEventResult.chatMsg);
    
            if (!onEventResult.groupMsg.empty())
                SafelySendMessage(std::string(contextLog.chatId), std::string(onEventResult.groupMsg));
        }
        catch (const SQLite::Exception& e)
        {
            logging::Log(logging::LogSource::Database, logging::LogType::Error, contextLog, e.what());
    
            SafelySendMessage(contextLog.userId, "Database error: " + std::string{ e.what() });
        }
        catch (const TgBot::TgException& e)
        {
    
            . . .
    
    }
    

    • TgBot::TgLongPoll: TgBot::TgLongPoll is a class that implements the Long Polling mechanism for receiving updates from Telegram servers. A Telegram server crash or lack of internet access will cause an exception from TgBot::TgLongPoll. To ensure full fault tolerance (except in cases of power or memory loss), all that remains is to protect the TgBot::TgLongPoll longPoll instance. For this, I created the Run function. In it, longPoll is initialized, after which an infinite loop with longPoll.start() in SafeExecute is started:

    void BotController::Run()
    {
        TgBot::TgLongPoll longPoll(bot);
    
        while (true)
        {
            SafeExecute(logging::ContextLog{}, [&]() -> logging::OnEventResult {
                while (true) { longPoll.start(); }
                return { "", "" }; });
    
            std::this_thread::sleep_for(std::chrono::seconds(5));
        }
    }
    
    
  3. Automatic database creation: The bot requires an SQLite database to function. But it turns out, to run GroupModerBot, the user must download a program for working with SQLite, figure out how to use it, and create the necessary tables. This is very inconvenient and time-consuming.

    Therefore, if the user prefers, they can create and name the database themselves, wherever and however they like. Then, simply specify the path to it in the configuration file. But if they don't want to do this: They can simply delete the DbPath= key from the DataForBot.txt configuration file, and when the .exe is launched, the configured GroupModerBotDatabase.db database will be automatically created in the folder containing the .exe.

    This was accomplished by simply checking for the presence of the DbPath= key in the configuration file. If the key is missing, the gmb::BotDatabase::InitStandardDB() function is called. It creates a database and fills it with all the necessary tables, after which it returns the path to it:

    std::string BotDatabase::InitStandardDB()
    {
        SQLite::Database db(consts::standardDBFile, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
    
        const std::unordered_map<std::string_view, const std::string> queries{
    
            . . .
    
        };
    
        for (const auto& table : tables)
        {
            assert(tables.size() == queries.size() && queries.contains(table->nameTable) && "Table standardDB desync");
    
            if (!db.tableExists(std::string(table->nameTable)))
            {
                SQLite::Statement query{ db, queries.at(table->nameTable)};
    
                query.exec();
            }
        }
    
        return consts::standardDBFile;
    }
    

Result

The story of GroupModerBot is a clear example of what a project can become if you take it seriously. Sit down and think through the architecture and functionality, setting a time limit.

Thanks to it, I learned not only how to work with the tgbot and SQLiteCpp libraries, but also how to create easily extensible, universal, and reliable code in modern standard C++. Of course, the code isn't perfect even in the current version: it only supports Windows, is single-threaded, and has limited functionality. But that's just for now.

I plan to continue working on GroupModerBot, adding new features and improving existing ones.

Want to see the full code for GroupModerBot, support the project, or use it for your own purposes? The project is freely available on GitHub: https://github.com/H-D-OWL/GroupModerBot

Thank you for reading my article. I'm open to questions and constructive criticism.