BareGit

Add username support to /send API

Author: MetroWind <chris.corsair@gmail.com>
Date: Mon Jan 12 11:55:33 2026 -0800
Commit: 4290752970b73e29c0d3396bd1e6ac8bfee7cf0e

Changes

diff --git a/design.md b/design.md
index 23d0eb7..2004ac6 100644
--- a/design.md
+++ b/design.md
@@ -37,13 +37,17 @@ Configuration is handled via Command Line Arguments:
 ```json
 {
   "chat_id": 123456789,
+  "username": "some_user",
   "text": "Hello World"
 }
 ```
+*   `text` (Required): The message content.
+*   `chat_id` (Optional): The target chat ID.
+*   `username` (Optional): If `chat_id` is not provided, the bot will look up the most recent `chat_id` associated with this username.
 
 **Response:**
 *   `200 OK`: Message sent successfully.
-*   `400 Bad Request`: Invalid JSON or missing fields.
+*   `400 Bad Request`: Missing `text`, or neither `chat_id` nor `username` provided, or username not found.
 *   `500 Internal Error`: Upstream Telegram error.
 
 ### 4.2. Subscribe
diff --git a/src/main.cpp b/src/main.cpp
index a28b3d6..a69389d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -6,6 +6,7 @@
 #include <thread>
 #include <chrono>
 #include <expected>
+#include <optional>
 
 #include <cxxopts.hpp>
 #include <nlohmann/json.hpp>
@@ -85,6 +86,30 @@ private:
     std::map<int64_t, std::vector<std::string>> subscribers_;
 };
 
+class UsernameResolver
+{
+public:
+    void update(const std::string& username, int64_t chat_id)
+    {
+        std::lock_guard lock(mutex_);
+        username_map_[username] = chat_id;
+    }
+
+    std::optional<int64_t> resolve(const std::string& username)
+    {
+        std::lock_guard lock(mutex_);
+        if(auto it = username_map_.find(username); it != username_map_.end())
+        {
+            return it->second;
+        }
+        return std::nullopt;
+    }
+
+private:
+    std::mutex mutex_;
+    std::map<std::string, int64_t> username_map_;
+};
+
 class App : public mw::HTTPServer
 {
 public:
@@ -100,15 +125,40 @@ public:
             try
             {
                 auto body = json::parse(req.body);
-                if(!body.contains("chat_id") || !body.contains("text"))
+                if(!body.contains("text"))
                 {
                     res.status = 400;
-                    res.set_content("Missing chat_id or text", "text/plain");
+                    res.set_content("Missing text", "text/plain");
+                    return;
+                }
+
+                int64_t chat_id = 0;
+                if(body.contains("chat_id"))
+                {
+                    chat_id = body["chat_id"];
+                }
+                else if(body.contains("username"))
+                {
+                    auto username = body["username"].get<std::string>();
+                    auto resolved = username_resolver_.resolve(username);
+                    if(!resolved.has_value())
+                    {
+                        res.status = 404;
+                        res.set_content("Username not found", "text/plain");
+                        return;
+                    }
+                    chat_id = *resolved;
+                }
+                else
+                {
+                    res.status = 400;
+                    res.set_content("Missing chat_id or username",
+                                    "text/plain");
                     return;
                 }
 
                 ASSIGN_OR_RESPOND_ERROR(auto result,
-                                        tg_client_.sendMessage(body["chat_id"],
+                                        tg_client_.sendMessage(chat_id,
                                                                body["text"]),
                                         res);
                 res.status = 200;
@@ -184,6 +234,13 @@ private:
     void dispatchMessage(const json& message)
     {
         int64_t chat_id = message["chat"]["id"];
+
+        if(message.contains("from") && message["from"].contains("username"))
+        {
+            std::string username = message["from"]["username"];
+            username_resolver_.update(username, chat_id);
+        }
+
         auto callbacks = sub_manager_.getSubscribers(chat_id);
 
         for(const auto& url : callbacks)
@@ -206,6 +263,7 @@ private:
 
     TelegramClient tg_client_;
     SubscriptionManager sub_manager_;
+    UsernameResolver username_resolver_;
     bool running_ = true;
 };
 
@@ -277,4 +335,4 @@ int main(int argc, char** argv)
     }
 
     return 0;
-}
\ No newline at end of file
+}
diff --git a/test_api.sh b/test_api.sh
index b3bef1d..b79bd00 100644
--- a/test_api.sh
+++ b/test_api.sh
@@ -11,8 +11,8 @@ curl -X POST "$API_URL/subscribe" \
      -d '{"chat_id": 12345, "callback_url": "http://localhost:9090/webhook"}'
 echo -e "\n"
 
-echo "Testing /send (should fail if token is invalid, but test API structure)..."
+echo "Testing /send with username (requires prior message from user)..."
 curl -X POST "$API_URL/send" \
      -H "Content-Type: application/json" \
-     -d '{"chat_id": 12345, "text": "Test message"}'
+     -d '{"username": "some_user", "text": "Test message to username"}'
 echo -e "\n"