Creating a simple, lightweight, and privacy-focused Telegram bot that administers psychological tests, provides immediate results, and securely stores user IDs and outcomes in a Cloudflare D1 database is entirely feasible. This setup leverages Cloudflare Workers for serverless hosting, ensuring speed, global reach, and robust privacy. Crucially, the entire deployment process can be managed through the Cloudflare dashboard, making it accessible even without command-line interface tools.
The core architecture for your Telegram bot revolves around Cloudflare Workers acting as a webhook endpoint. When a user interacts with your bot on Telegram, Telegram sends an update (a POST request) to your Worker's URL. Your Worker then processes this request, interacts with Cloudflare D1 for state management and data storage, and sends a response back to Telegram via its Bot API.
Privacy is a paramount concern for your bot. Here's how this solution addresses it:
An illustration of human-chatbot interaction.
Below is the complete JavaScript code for your Telegram bot. You can directly copy and paste this into the Cloudflare Worker editor in your dashboard. This bot handles a multi-question psychological test, calculates a score, and stores the user's ID and results in Cloudflare D1.
// worker.js - Cloudflare Worker script for a privacy-first Telegram psychological test bot // Define your psychological test questions and their scoring implications const questions = [ { id: 1, text: "On a scale of 1-5, how stressed do you feel today?", type: "numeric", score_contribution: 1 }, { id: 2, text: "On a scale of 1-5, how well did you sleep last night?", type: "numeric", score_contribution: -1 }, // Lower sleep score might contribute more to "stress" { id: 3, text: "On a scale of 1-5, how happy are you currently?", type: "numeric", score_contribution: 2 }, // Higher happiness score weighs more positively { id: 4, text: "On a scale of 1-5, how focused were you today?", type: "numeric", score_contribution: 1 }, { id: 5, text: "On a scale of 1-5, how socially engaged have you been?", type: "numeric", score_contribution: 0.5 } ]; export default { async fetch(request, env, ctx) { // Bind environment variables from Cloudflare dashboard globalThis.BOT_TOKEN = env.BOT_TOKEN; globalThis.D1_DB = env.D1_DB; // Assuming your D1 binding is named 'D1_DB' try { if (request.method !== 'POST') { return new Response('Telegram Bot expects POST requests', { status: 405 }); } const body = await request.json(); // Extract Telegram update info from message or callback_query const message = body.message || body.callback_query?.message; if (!message) return new Response('No message found', { status: 200 }); const chatId = message.chat.id; const text = (body.message && body.message.text) || (body.callback_query && body.callback_query.data) || ""; // Initialize D1 tables if they don't exist await initializeD1Tables(globalThis.D1_DB); // Handle commands if (text === "/start") { await sendMessage(chatId, "Welcome to the psychological test bot! I'll ask you a series of questions.\n\nTo begin the test, type <code>/test"); return new Response('OK', { status: 200 }); } else if (text === "/test") { // Clear previous state and start new test await globalThis.D1_DB.prepare("DELETE FROM UserTestStates WHERE userId = ?").bind(chatId).run(); await globalThis.D1_DB.prepare("INSERT INTO UserTestStates (userId, currentQuestionIndex, answersJson, completed) VALUES (?, ?, ?, ?)").bind(chatId, 0, JSON.stringify([]), 0).run(); await sendMessage(chatId,Question 1: ${questions[0].text}); return new Response('OK', { status: 200 }); } // Handle test responses const userStateResult = await globalThis.D1_DB.prepare("SELECT * FROM UserTestStates WHERE userId = ?").bind(chatId).first(); if (!userStateResult || userStateResult.completed) { await sendMessage(chatId, "Please start the test with/testfirst."); return new Response('OK', { status: 200 }); } let currentQuestionIndex = userStateResult.currentQuestionIndex; let answerList = JSON.parse(userStateResult.answersJson); if (currentQuestionIndex >= questions.length) { await sendMessage(chatId, "You've already completed the test. Please start a new one with/test."); return new Response('OK', { status: 200 }); } // Validate and store the user's answer for the current question const currentQuestion = questions[currentQuestionIndex]; const answerValue = parseInt(text.trim()); if (currentQuestion.type === "numeric") { if (isNaN(answerValue) || answerValue < 1 || answerValue > 5) { await sendMessage(chatId,Invalid response for Question ${currentQuestionIndex + 1}. Please answer a number between 1 and 5.); return new Response('OK', { status: 200 }); } } else { // Future: handle other question types like boolean, multiple choice await sendMessage(chatId,Sorry, I only understand numeric answers (1-5) for now.); return new Response('OK', { status: 200 }); } answerList.push(answerValue); // Store the current answer currentQuestionIndex++; // Proceed to the next question or finalize the test if (currentQuestionIndex < questions.length) { await globalThis.D1_DB.prepare("UPDATE UserTestStates SET currentQuestionIndex = ?, answersJson = ? WHERE userId = ?") .bind(currentQuestionIndex, JSON.stringify(answerList), chatId).run(); await sendMessage(chatId,Question ${currentQuestionIndex + 1}: ${questions[currentQuestionIndex].text}); } else { // Test completed - calculate score and store final result let totalWeightedScore = 0; for (let i = 0; i < questions.length; i++) { totalWeightedScore += answerList[i] * questions[i].score_contribution; } // A simple interpretation: normalize to a 1-10 scale const maxPossibleScore = questions.reduce((sum, q) => sum + (5 * q.score_contribution), 0); const minPossibleScore = questions.reduce((sum, q) => sum + (1 * q.score_contribution), 0); let finalScore; if (maxPossibleScore === minPossibleScore) { finalScore = 0; // Avoid division by zero } else { // Normalize to 0-1 range then to 1-10 finalScore = 1 + ((totalWeightedScore - minPossibleScore) / (maxPossibleScore - minPossibleScore)) * 9; } finalScore = parseFloat(finalScore.toFixed(2)); // Store final result await globalThis.D1_DB.prepare("INSERT INTO TestResults (userId, score, timestamp) VALUES (?, ?, ?) ON CONFLICT(userId) DO UPDATE SET score = excluded.score, timestamp = excluded.timestamp;") .bind(chatId, finalScore, Date.now()).run(); // Mark test as completed and optionally clear state await globalThis.D1_DB.prepare("UPDATE UserTestStates SET completed = ? WHERE userId = ?").bind(1, chatId).run(); // Or, to clear session: await globalThis.D1_DB.prepare("DELETE FROM UserTestStates WHERE userId = ?").bind(chatId).run(); await sendMessage(chatId,Test completed! Your psychological assessment score is: ${finalScore} out of 10. (Higher is generally better, based on question weighting)\nYour answers were: ${answerList.join(", ")}\n\nTo restart, type \/test\`); } return new Response('OK', { status: 200 }); } catch (err) { console.error("Bot Error:", err.stack); // It's good practice not to expose detailed errors to the user in production return new Response('Error processing your request: ' + err.message, { status: 500 }); } } } // Helper function to send message via Telegram API async function sendMessage(chatId, text) { const payload = { chat_id: chatId, text: text, parse_mode: "Markdown" // Allows for bold, italic, etc. }; await fetch(https://api.telegram.org/bot${globalThis.BOT_TOKEN}/sendMessage, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); } // Helper to initialize D1 tables if they don't exist async function initializeD1Tables(db) { await db.exec(CREATE TABLE IF NOT EXISTS UserTestStates ( userId TEXT PRIMARY KEY, currentQuestionIndex INTEGER, answersJson TEXT, completed INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS TestResults ( id INTEGER PRIMARY KEY AUTOINCREMENT, userId TEXT UNIQUE, score REAL, timestamp INTEGER ); `); }
The provided JavaScript code orchestrates the bot's functionality within the Cloudflare Workers environment. Here's a breakdown of its key elements:
questions Array: This array holds your psychological test questions. Each question object contains an ID, the question text, its type (e.g., "numeric"), and a score_contribution. The score_contribution allows for nuanced scoring, where certain answers or questions might impact the final score more positively or negatively.fetch Event Listener: This is the entry point for your Worker. It listens for incoming HTTP POST requests from Telegram's webhook.chatId (the user's unique Telegram ID) and the text of their message.initializeD1Tables(): Ensures that the necessary tables (UserTestStates for session management and TestResults for final data storage) exist in your Cloudflare D1 database.UserTestStates Table: This table temporarily stores the user's progress through the test (currentQuestionIndex, answersJson, and completed status). It's crucial for maintaining conversational state across multiple messages.TestResults Table: This table stores the final computed score and a timestamp for each unique userId. It uses ON CONFLICT(userId) DO UPDATE to ensure that if a user takes the test multiple times, their latest score is updated./start, /test): The bot responds to these commands to initiate or restart the test. The /test command specifically clears any previous session state for the user and begins the question sequence.score_contribution of each question. It then normalizes this to a 1-10 scale for easier interpretation.sendMessage() Helper: This asynchronous function constructs and sends messages back to Telegram using the Bot API's sendMessage method, utilizing your BOT_TOKEN.try...catch block is included to gracefully handle any errors during processing, preventing the bot from crashing.Follow these detailed steps to get your privacy-focused Telegram bot up and running without touching the command line:
/newbot.123456789:ABC-DEF...). Copy this token and keep it safe; it is crucial.psych-test-bot).psych_results_db). This name is important as it will be your binding name.initializeD1Tables function that will create the necessary tables automatically on the first request.BOT_TOKENworker.js code to access D1. For our example, it's D1_DB.psych_results_db).https://your-worker-name.your-username.workers.dev. Copy this URL.https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook?url=<YOUR_WORKER_URL>
<YOUR_BOT_TOKEN> with the actual token you got from @BotFather.<YOUR_WORKER_URL> with the URL of your Cloudflare Worker you copied in the previous step.12345:ABCDEF and your worker URL is https://psych-test-bot.your-username.workers.dev, the full URL to open in your browser would be:
https://api.telegram.org/bot12345:ABCDEF/setWebhook?url=https://psych-test-bot.your-username.workers.dev/{"ok":true, "result":true, ...} if successful. This means Telegram is now configured to send updates to your Cloudflare Worker.@privacytest_bot), and start a chat./start. Your bot should respond with the welcome message./test to begin the psychological test.psych_results_db).UserTestStates and TestResults tables.
SELECT * FROM TestResults;SELECT * FROM UserTestStates;userId, score, and timestamp stored in the TestResults table.To provide a clearer understanding of your bot's operational characteristics, let's visualize its key attributes using a radar chart. This chart will represent qualitative aspects based on the design choices made in this comprehensive solution.
This radar chart illustrates the strengths of your Telegram bot hosted on Cloudflare Workers. Simplicity refers to the straightforward code and dashboard-only deployment. Lightweight highlights the minimal dependencies and efficient resource usage. Speed and Scalability are inherent benefits of Cloudflare Workers' global edge network. The bot's design also emphasizes Privacy through minimal data collection and secure hosting. Finally, Genius/Smart represents the effective use of serverless technology for a practical application.
The mindmap below illustrates the sequential data flow and key interactions involved when a user takes a psychological test through your Telegram bot, from initial message to result storage.
The mindmap outlines the journey of a user's interaction with your bot. It starts with the bot's creation via BotFather, then moves to the Cloudflare Worker which is the brain of the operation, receiving messages via webhooks and processing them. Cloudflare D1 serves as the persistent storage layer for both temporary conversational states and final test results. The user interacts directly with the Telegram app, sending commands and answers. Crucially, the entire process is underpinned by principles of privacy and anonymity, ensuring a secure and user-friendly experience.
While this guide focuses on Cloudflare D1 for storage, it's beneficial to understand other Cloudflare storage options and why D1 is particularly well-suited for this use case. Below is a table comparing D1 with Cloudflare KV (Key-Value store), another common choice for Workers.
| Feature | Cloudflare D1 (SQL Database) | Cloudflare KV (Key-Value Store) |
|---|---|---|
| Data Model | Relational (SQL tables, rows, columns) | Key-value pairs (unstructured data) |
| Query Language | SQL (SELECT, INSERT, UPDATE, DELETE) | Simple API calls (get, put, list, delete) |
| Use Case Suitability | Structured data, relationships, complex queries, backups (like test results and conversational state) | Simple caching, user preferences, session data where relationships are not needed |
| Consistency Model | Strongly consistent (reads reflect latest writes) | Eventually consistent (reads might not reflect latest writes immediately) |
| Pricing (Free Tier) | Generous free tier (up to 10GB storage, 50k reads/day, 10k writes/day) | Generous free tier (up to 1GB storage, 100k reads/day, 1k writes/day) |
| Complexity | Requires SQL knowledge, schema design | Simpler to use for basic storage |
For storing psychological test results and managing conversational flow (which often involves updating structured records), D1's relational model and strong consistency make it a more robust choice than KV. It allows for easy querying and structured storage of each user's progress and final score, fitting the "backup" requirement perfectly.
To further assist you in the deployment process, here's a highly relevant video tutorial that demonstrates the process of deploying a Telegram bot on Cloudflare Workers. While the video might not cover the psychological test aspect specifically, it provides an excellent visual walkthrough of the fundamental steps, including setting up your Worker, configuring webhooks, and interacting with the bot.
This video, titled "Deploy A Telegram Bot For Free With Cloudflare Workers," provides a practical demonstration of setting up a basic Telegram echo bot on Cloudflare Workers. It's highly relevant as it covers the foundational steps necessary for deploying any Telegram bot using this serverless platform, including obtaining the bot token, configuring the Worker, and establishing the webhook. Watching this can solidify your understanding of the Cloudflare-side deployment process.
https://your-worker-name.your-username.workers.dev) is what matters for setting the Telegram webhook. Ensure you use the correct, full Worker URL when constructing the setWebhook API call.psych_results_db). You will find tabs like "Browse Data" or "Query editor" where you can directly view the contents of your tables (UserTestStates and TestResults) or run SQL queries to retrieve specific data.@yourbotname Hey!) or commands that start with a slash (e.g., /start). This limits the bot's access to all group messages, enhancing group privacy. For one-on-one chats, the bot always receives all messages.questions array in the Worker code to include more questions, different types of questions (e.g., multiple choice, true/false, open-ended), and more sophisticated scoring logic. For very complex tests, consider storing question definitions in D1 as well, allowing for easier updates without redeploying the Worker code.You now have a comprehensive understanding and an executable solution for deploying a simple, lightweight, and privacy-focused Telegram bot that administers psychological tests. By leveraging Cloudflare Workers and D1 database, you achieve a serverless, fast, and scalable application that prioritizes user privacy. The step-by-step guide, complete with dashboard-only instructions, ensures that anyone can set up this bot and manage it effectively. This architecture provides a robust foundation, which you can expand upon to create more intricate tests and features while maintaining its core principles of privacy and efficiency.