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

推荐订阅源

Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
阮一峰的网络日志
阮一峰的网络日志
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
罗磊的独立博客
S
SegmentFault 最新的问题
V
V2EX
V
Visual Studio Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
Y
Y Combinator Blog
MyScale Blog
MyScale Blog
D
Docker
Google DeepMind News
Google DeepMind News
Blog — PlanetScale
Blog — PlanetScale
M
Microsoft Research Blog - Microsoft Research
Martin Fowler
Martin Fowler
S
Secure Thoughts
B
Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Recent Announcements
Recent Announcements
MongoDB | Blog
MongoDB | Blog
C
Cisco Blogs
C
CERT Recently Published Vulnerability Notes
T
True Tiger Recordings
GbyAI
GbyAI
P
Proofpoint News Feed
P
Privacy International News Feed
Jina AI
Jina AI
The Cloudflare Blog
I
Intezer
AWS News Blog
AWS News Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
Security Archives - TechRepublic
NISL@THU
NISL@THU
The Register - Security
The Register - Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
P
Palo Alto Networks Blog
S
Schneier on Security
L
LINUX DO - 热门话题
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
C
Cybersecurity and Infrastructure Security Agency CISA

DEV Community

Sixteen TUI components, copy-paste, no dependency The Boring Reliability Layer Every Autonomous Agent Needs Nven - Secret manager Building Multi-Tenant Row-Level Security in PostgreSQL: A Production Pattern Building Vylo — Looking for Collaborators, Partners & Early Support I Thought Memory Fades With Time. It Actually Fades With Information. ORA-00064 오류 원인과 해결 방법 완벽 가이드 I registered an AI agent at 1 AM and something cracked open in my head Pitch: Nven - Sync secrets. Ship faster. Why y=mx+b is the heart of AI From Routines to a Crew — Building a System That Plans Its Own Work & executes it 25 React Interview Questions 2026 (With Answers) — Hooks, React 19, Concurrent Mode An open source LLM eval tool with two independent quality signals Using Dashboard Filtering to Get Customer Usage in Seconds from TBs of Data Skills, Java 17, And Theme Accents 4 Hard Lessons on Optimizing AI Coding Agents Arctype: Cross-Platform Database GUI for LLM Artifacts Your robots.txt says GPTBot is welcome. Your server says 403. Organizing How to Use AWS Glue Workflow 5 n8n Automations Every Digital Agency Should Be Running (Bill More, Work Less) Getting Started with TorchGeo — Remote Sensing with PyTorch Designing a Scalable Cross-Platform Appium Framework Google Antigravity 2.0 & Slash Commands Building a Unified Adaptive Learning Intelligence with Gemma 4, Flutter, and Multi-Model Orchestration Looking for beta testers for a £60 server management application The Disk-Pressure Incident That Taught Me to Always Set LimitRanges and Other Lessons from Mirroring EKS Locally. Why AI Should Not Write SQL Against ERP Databases Vibe coding works until it doesn't. The debt is real. Shipping at the Edge: Migrating a Coffee Subscription Platform to Cloudflare Workers Stop Tab-Switching: A Developer's Guide to Color Tools That Actually Fit the Workflow DevOps vs MLOps vs AIOps: What Changes, What Stays, and a Simple Roadmap to Get Started Run Powerful AI Coding Locally on a Normal Laptop 5 n8n Automations Every WooCommerce Store Needs (Save 10+ Hours/Week) What I Learned Building My Own AI Harness Hytale Servers Will Fail Treasure Hunts Until We Fix Our Event Handling Redux in React: Managing Global State Like a Pro Unfreezing Your GitHub Actions: Troubleshooting Stuck Deployments and Protecting Your Git Repo Statistics Unlocking Project Discoverability on GHES: A Key to Software Engineering Productivity When the Cleanup Code Becomes the Project Rockpack 8.0 - A React Scaffolder Built for the Age of AI-Assisted Development Mismanaging the Treasure Hunt Engine in Hytale Servers Will Get You Killed Stop Calling It an AI Assistant. It’s Already Managing Your Company Why Hardcoded Automations Fail AI Agents Why I built a post-quantum signing API (and why JWT is on borrowed time) Weekend Thought: Frontend Build Tools Suffer From Work Amnesia AI Is Changing Engineering Culture More Than We Realize A 10-Line Playwright Trick That Saved Me Hours on Every Sephora Run Everyone Was Focused on Gemini, But Infinite Scaler Was the Real Twister "Gemma 4 Analyzed My Bank Statements – Apparently I 'Have a Problem' with Coffee and Late-Night Apps" #css #webdev #beginners #codenewbie The Hidden Layer Every AI Developer Must Learn AlphaEvolve: Google DeepMind's Gemini-Powered Evolutionary Coding Agent RDS Reserved Instance Pricing: Every Engine, Every Rule, Real Dollar Savings How To Build An AI-Powered MVP Without Burning Your Startup Budget In 2026 Reading a Psychrometric Chart Without Getting Lost LMR-BENCH: Can LLM Agents Reproduce NLP Research Code? (EMNLP 2025) How to turn text into colors (without AI) Building Real-Time Apps in Node.js with Rivalis: WebSockets, Rooms, Actors, and a Binary Wire This Week In React #282 : Security, Fate, TanStack, Redux, Jotai | Hermes-node, Expo, Rozenite, Harness | TC39, Bun, pnpm, npm, Yarn, Node AI Copilot vs AI Agent Architecture - What's Actually Different (And Why It Matters) Smart Contract Security: NEAR's Futures Surge and AI Token Risks Database Maintenance: Tracing Production Incidents to Their Root Cause Stop juggling AI SDKs in PHP — meet Prisma Google Quietly Changed What “Apps” Mean at I/O 2026 The Infrastructure Team Is the Real Single Point of Failure Building SQLite from Scratch: 740 Lines of C++23 to Understand Every Byte of a .db File The 4 Levels of Hermes Agent Scaling Framework: From One Hermes Agent to a Fully Automated Team Your AI Has a Memory. It Just Doesn’t Know What to Remember. Claprec: Engineering Tradeoffs - Limited time vs. Perfection (6/6) Building a Daily Google News API Monitor in Python Building RookDuel Avikal: From Chess Steganography to Post-Quantum Archival Security Google I/O e IA: o que realmente muda na vida do dev? Color Contrast Failures: The Number One Accessibility Issue and How to Fix It # I Watched 15 Hours of Hermes Agent Videos So You Don't Have To Cómo solucionar el bucle infinito en useEffect con objetos y arrays en React The First Agent-Centric Cloud Security Platform — And Why We Didn't Build It That Way On Purpose Most Treasure Hunts Engines on Hytale Servers Are Built to Fail - Lessons from a Burned Database GhostScan v3.0 — From Closed-Source EXE to Open-Source Pentest Framework De hojas de cálculo a IA: construyendo una plataforma SRM moderna When is AI fine in education? Python Tools for Managing API Rate Limits in Data Pipelines How to Implement Exponential Backoff for Rate-Limited APIs in Python "My Web Chat Wasn't a Real Channel. That Broke My Agent Pipeline" next-advanced-sitemap v1.0.7 — safer URL ingestion & automatic trimming for Next.js sitemap generation I keep seeing people build an AI lead processing agent when they really need a 6-step rules engine AI Powered Student Learning Assistant Using Gemma 4 How I Built a Drop-In Proxy to Slash My OpenAI Bills by 20%+ Automatically Building a Sarcastic AI English Tutor with Persona-as-Code and Gemini Audio Input for Pronunciation Correction Five Years Later, I Finally Have 96GB VRAM — What It Actually Unlocks for Agent Loops Turning a 1-Line Idea Into a 40-Second Short with a 10-Beat Local Video Pipeline Running LTX-2.3 Alongside TTS on a Single 96GB GPU with a Cold-Start Architecture Cutting LTX-2 22B Peak VRAM by 40% with fp8_cast — and Why optimum-quanto Was a Trap HiDream Skeleton Mode: Prompt Beats OpenPose Ref — 8 Patterns Benchmarked Replicating a Language-Learning Comedy Short with Claude Code — Gemini as a Multimodal Sub-Agent HiDream-O1-Image 3–8x Faster: Benchmarking Steps, CFG, and Resolution AWS Savings Plan Buying Strategy: How to Layer, Size, and Time Commitments application.properties I built a macro tracker powered by AI + attitude Solace: A Global Mental Health First Responder Built with Gemma 4 Why Blocking Prompt Injection Is Wrong — and What to Do Instead
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.