[go: up one dir, main page]

0% found this document useful (0 votes)
11 views18 pages

Code

This document contains a Cloudflare Workers script for handling a Telegram bot webhook. It defines various functions for processing messages, managing user states, and interacting with a Google Sheet API and a KV database. The script includes admin functionalities for managing groups and user interactions in both Bengali and English.

Uploaded by

samsungsetup71
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views18 pages

Code

This document contains a Cloudflare Workers script for handling a Telegram bot webhook. It defines various functions for processing messages, managing user states, and interacting with a Google Sheet API and a KV database. The script includes admin functionalities for managing groups and user interactions in both Bengali and English.

Uploaded by

samsungsetup71
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 18

cloudflare code:

// এই ভেরিয়েবলগুলি Cloudflare Workers Dashboard-এ Environment Variables সেকশনে সেট


করা আছে।
// এগুলি সরাসরি Worker স্ক্রিপ্টের গ্লোবাল স্কোপে উপলব্ধ।
// অর্থাৎ, ADMIN_ID, BOT_TOKEN, SHEET_API_URL ভেরিয়েবলগুলো কোডের মধ্যে সরাসরি
ব্যবহার করা যাবে।
// তাই, এদেরকে `const` দিয়ে পুনরায় ডিক্লেয়ার করার প্রয়োজন নেই।

const TELEGRAM_API = `https://api.telegram.org/bot${BOT_TOKEN}`; // BOT_TOKEN এখানে


সরাসরি উপলব্ধ

// Cloudflare Workers-এর KV Namespace Binding, যেমন 'DB'


// এটি তোমার 'All Premium Link' নামের KV Namespace-এর সাথে বাইন্ড করা আছে।
// কোডে 'DB' ভেরিয়েবলটি এই Namespace-কে উপস্থাপন করবে।
// DB নামের KV Namespace Binding সরাসরি উপলব্ধ, তাই এটি ডিক্লেয়ার করা প্রয়োজন
নেই।

addEventListener('fetch', event => {


event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {


if (request.method !== 'POST') {
return new Response('This is a Telegram Bot webhook handler. Send POST
requests.', { status: 200 });
}

const update = await request.json();


const chatId = update.message?.chat?.id ||
update.callback_query?.message?.chat?.id;
const userId = update.message?.from?.id || update.callback_query?.from?.id;
const messageText = update.message?.text;
const callbackData = update.callback_query?.data;
const messageId = update.message?.message_id ||
update.callback_query?.message?.message_id;
const contact = update.message?.contact;
const username = update.message?.from?.username ||
update.callback_query?.from?.username;
const fullName = (update.message?.from?.first_name ||
update.callback_query?.from?.first_name) +
((update.message?.from?.last_name ||
update.callback_query?.from?.last_name) ? " " + (update.message?.from?.last_name ||
update.callback_query?.from?.last_name) : "");

// Check if the user is an admin


const isAdmin = userId == ADMIN_ID; // ADMIN_ID সরাসরি গ্লোবালি উপলব্ধ

try {
if (callbackData) {
await handleCallbackQuery(update.callback_query, isAdmin);
} else if (messageText) {
await handleMessage(update.message, isAdmin);
} else if (contact) {
await handleContactShare(update.message, isAdmin);
}
return new Response('OK', { status: 200 });
} catch (error) {
console.error('Error handling Telegram update:', error);
// Optionally send an error message to the user, but be careful with loops
// await sendMessage(chatId, `An internal error occurred: ${error.message}`);
return new Response('Error', { status: 500 });
}
}

// --- TELEGRAM API FUNCTIONS ---


async function sendMessage(chat_id, text, reply_markup = null, parse_mode = 'HTML',
disable_web_page_preview = false) {
const url = `${TELEGRAM_API}/sendMessage`;
const body = { chat_id, text, parse_mode, disable_web_page_preview };
if (reply_markup) {
body.reply_markup = reply_markup;
}
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
return response.json(); // Return response to get message_id if needed
}

async function editMessageText(chat_id, message_id, text, reply_markup = null,


parse_mode = 'HTML', disable_web_page_preview = false) {
const url = `${TELEGRAM_API}/editMessageText`;
const body = { chat_id, message_id, text, parse_mode, disable_web_page_preview };
if (reply_markup) {
body.reply_markup = reply_markup;
}
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
}

async function deleteMessage(chat_id, message_id) {


const url = `${TELEGRAM_API}/deleteMessage`;
const body = { chat_id, message_id };
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
}

async function setMyCommands(chat_id, commands) {


const url = `${TELEGRAM_API}/setMyCommands`;
const body = {
commands: commands,
scope: { type: "chat", chat_id: chat_id }
};
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
}
// --- GOOGLE SHEET API INTERACTION ---
async function sendToGoogleSheet(action, payload) {
try {
const response = await fetch(SHEET_API_URL, { // SHEET_API_URL এখন স্পষ্টভাবে
ডিক্লেয়ার করা আছে
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, payload }),
});
const data = await response.json();
if (data.status !== 'success') {
console.error('Google Sheet API Error:', data.message);
throw new Error(`Sheet API Error: ${data.message}`);
}
return data;
} catch (error) {
console.error('Failed to send data to Google Sheet:', error);
throw new Error(`Google Sheet connection error: ${error.message}`);
}
}

// --- KV DATABASE INTERACTION ---


// DB নামের KV Namespace Binding সরাসরি উপলব্ধ, তাই এটি ডিক্লেয়ার করা প্রয়োজন
নেই।
async function getKV(key) {
const value = await DB.get(key);
return value ? JSON.parse(value) : null;
}

async function putKV(key, value) {


await DB.put(key, JSON.stringify(value));
}

async function deleteKV(key) {


await DB.delete(key);
}

// --- STATE MANAGEMENT (USING KV FOR USER/ADMIN STATE) ---


async function getUserState(userId) {
return await getKV(`state_${userId}`);
}

async function setUserState(userId, state) {


await putKV(`state_${userId}`, state);
}

async function clearUserState(userId) {


await deleteKV(`state_${userId}`);
}

// --- ADMIN PANEL FUNCTIONS ---


async function showAdminPanel(chatId, messageId = null) {
const keyboard = {
inline_keyboard: [
[{ text: '➕ Add Group', callback_data: 'admin_add_group' }],
[{ text: '✏️ Edit Group', callback_data: 'admin_edit_group' }],
[{ text: ' Delete Group', callback_data: 'admin_delete_group' }],
[{ text: '🔗 Set First Link', callback_data: 'admin_set_first_link' }],
[{ text: ' Preview Groups', callback_data: 'admin_preview' }],
],
};
const text = 'Welcome, Admin! Choose an action:';
if (messageId) {
await editMessageText(chatId, messageId, text, keyboard);
} else {
await sendMessage(chatId, text, keyboard);
}
}

async function handleAdminAddGroup(chatId, messageId) {


await deleteMessage(chatId, messageId); // Clean previous message
await sendMessage(chatId, 'Please send the **name** of the group you want to
add.', {
inline_keyboard: [[{ text: 'Cancel', callback_data: 'admin_cancel' }]]
});
await setUserState(chatId, { step: 'await_group_name', admin_flow: true });
}

async function handleAdminEditGroup(chatId, messageId) {


await deleteMessage(chatId, messageId);
const groups = await getKV('groups') || [];
if (groups.length === 0) {
await sendMessage(chatId, 'No groups added yet to edit.', {
inline_keyboard: [[{ text: 'Back to Admin Panel', callback_data:
'admin_panel' }]]
});
return;
}
const keyboard = {
inline_keyboard: groups.map(group => [{ text: group.name, callback_data:
`edit_group_select_${group.id}` }])
};
keyboard.inline_keyboard.push([{ text: 'Cancel', callback_data:
'admin_cancel' }]);
await sendMessage(chatId, 'Select the group you want to edit:', keyboard);
await setUserState(chatId, { step: 'select_group_to_edit', admin_flow: true });
}

async function handleAdminDeleteGroup(chatId, messageId) {


await deleteMessage(chatId, messageId);
const groups = await getKV('groups') || [];
if (groups.length === 0) {
await sendMessage(chatId, 'No groups added yet to delete.', {
inline_keyboard: [[{ text: 'Back to Admin Panel', callback_data:
'admin_panel' }]]
});
return;
}
const keyboard = {
inline_keyboard: groups.map(group => [{ text: group.name, callback_data:
`delete_group_select_${group.id}` }])
};
keyboard.inline_keyboard.push([{ text: 'Cancel', callback_data:
'admin_cancel' }]);
await sendMessage(chatId, 'Select the group you want to delete:', keyboard);
await setUserState(chatId, { step: 'select_group_to_delete', admin_flow: true });
}
async function handleAdminSetFirstLink(chatId, messageId) {
await deleteMessage(chatId, messageId);
const groups = await getKV('groups') || [];
if (groups.length === 0) {
await sendMessage(chatId, 'No groups added yet to set a first link. Please add
groups first.', {
inline_keyboard: [[{ text: 'Back to Admin Panel', callback_data:
'admin_panel' }]]
});
return;
}

const keyboard = {
inline_keyboard: [
...groups.map(group => [{ text: group.name, callback_data:
`set_first_link_select_${group.id}` }]),
[{ text: 'Or enter a custom link', callback_data:
'admin_set_custom_first_link' }],
[{ text: 'Cancel', callback_data: 'admin_cancel' }]
]
};
await sendMessage(chatId, 'Select a group to set its link as the first link, or
provide a custom link:', keyboard);
await setUserState(chatId, { step: 'select_first_link_group', admin_flow:
true });
}

async function handleAdminPreview(chatId, messageId) {


await deleteMessage(chatId, messageId);
const groups = await getKV('groups') || [];
const firstLink = await getKV('firstLink');

if (groups.length === 0) {
await sendMessage(chatId, 'No groups added yet to preview.', {
inline_keyboard: [[{ text: 'Back to Admin Panel', callback_data:
'admin_panel' }]]
});
return;
}

// Preview of Fake Group List


const fakeKeyboard = {
inline_keyboard: groups.map(group => {
// For fake list, all buttons point to the firstLink
return [{ text: group.name, url: firstLink || 'https://t.me/' }]; // Fallback
to a dummy link if firstLink not set
})
};
await sendMessage(chatId, '<b>Preview of Fake Group List:</b>\n\n আমাদের সকল
গ্রুপ লিস্ট। নিচের নামে ক্লিক করে গ্রুপে জয়েন হয়ে নিন।', fakeKeyboard, 'HTML',
true);

// Preview of Real Group List


const realKeyboard = {
inline_keyboard: groups.map(group => {
return [{ text: group.name, url: group.link }];
})
};
await sendMessage(chatId, '<b>Preview of Real Group List:</b>\n\n আমাদের সকল
গ্রুপ লিস্ট। নিচের নামে ক্লিক করে গ্রুপে জয়েন হয়ে নিন।', realKeyboard, 'HTML',
true);

await sendMessage(chatId, 'Done with preview.', { inline_keyboard: [[{ text:


'Back to Admin Panel', callback_data: 'admin_panel' }]] });
}

// --- USER FLOW FUNCTIONS ---

async function showLanguageSelection(chatId) {


const keyboard = {
inline_keyboard: [
[{ text: 'বাংলা 🇧🇩', callback_data: 'lang_bn' }],
[{ text: 'English 🇬🇧', callback_data: 'lang_en' }],
],
};
await sendMessage(chatId, 'Welcome! Please choose your language:\n\n স্বাগতম!
আপনার ভাষা নির্বাচন করুন:', keyboard);
}

async function showGenderSelection(chatId, messageId, lang) {


await deleteMessage(chatId, messageId);
const text = lang === 'bn' ? 'আপনার লিঙ্গ নির্বাচন করুন:' : 'Please select your
gender:';
const maleButton = lang === 'bn' ? 'পুরুষ' : 'Male';
const femaleButton = lang === 'bn' ? 'মহিলা' : 'Female';

const keyboard = {
inline_keyboard: [
[{ text: maleButton, callback_data: 'gender_Male' }],
[{ text: femaleButton, callback_data: 'gender_Female' }],
],
};
await sendMessage(chatId, text, keyboard);
await setUserState(chatId, { step: 'select_gender', lang: lang }); // Store lang
in state
}

async function showFakeGroupList(chatId, messageId, lang, firstLink) {


await deleteMessage(chatId, messageId);
const groups = await getKV('groups') || [];
if (groups.length === 0) {
const noGroupsText = lang === 'bn' ? 'বর্তমানে কোনো গ্রুপ উপলব্ধ নেই।' : 'No
groups currently available.';
await sendMessage(chatId, noGroupsText);
await clearUserState(chatId); // Clear state if no groups to show
return;
}

const caption = lang === 'bn' ? 'আমাদের সকল গ্রুপ লিস্ট। নিচের নামে ক্লিক করে
গ্রুপে জয়েন হয়ে নিন।' : 'Our complete group list. Click on the names below to
join the groups.';
const keyboard = {
inline_keyboard: groups.map(group => [{ text: group.name, url: firstLink,
callback_data: `fake_link_clicked_first` }]) // First click for fake list
};
await sendMessage(chatId, `${caption}`, keyboard);
await setUserState(chatId, { step: 'fake_list_first_shown', lang: lang,
firstLink: firstLink });
}

async function showFakeGroupListSecondTime(chatId, messageId, lang, firstLink) {


await deleteMessage(chatId, messageId); // Remove the previous fake list
const groups = await getKV('groups') || [];
if (groups.length === 0) {
const noGroupsText = lang === 'bn' ? 'বর্তমানে কোনো গ্রুপ উপলব্ধ নেই।' :
'No groups currently available.';
await sendMessage(chatId, noGroupsText);
await clearUserState(chatId); // Clear state if no groups to show
return;
}

const caption = lang === 'bn' ? 'আমাদের সকল গ্রুপ লিস্ট। নিচের নামে ক্লিক করে
গ্রুপে জয়েন হয়ে নিন।' : 'Our complete group list. Click on the names below to
join the groups.';
const keyboard = {
inline_keyboard: groups.map(group => [{ text: group.name, url: firstLink,
callback_data: `fake_link_clicked_second` }]) // Second click for robot check
};

await sendMessage(chatId, `${caption}`, keyboard);


await setUserState(chatId, { step: 'fake_list_second_shown', lang: lang,
firstLink: firstLink });
}

async function showRobotCheck(chatId, messageId, lang) {


if (messageId) { // Only delete if a message ID is provided
await deleteMessage(chatId, messageId);
}

const text = lang === 'bn' ?


'আমাদের সিস্টেম চেক করতে চাচ্ছে আপনি রোবট কিনা। রোবট কিনা চ্যাকের জন্য আপনার
নাম্বার শেয়ার করতে হবে। এটি বাধ্যতামূলক।' :
'Our system wants to verify if you are a robot. To check, you must share your
phone number. This is mandatory.';
const shareButtonText = lang === 'bn' ? '📞 নাম্বার শেয়ার করুন' : '📞 Share Phone
Number';

const keyboard = {
keyboard: [
[{ text: shareButtonText, request_contact: true }]
],
resize_keyboard: true,
one_time_keyboard: true // Hide keyboard after use
};
await sendMessage(chatId, text, keyboard);
await setUserState(chatId, { step: 'await_contact', lang: lang });
}

async function showRealGroupList(chatId, lang) {


const groups = await getKV('groups') || [];
if (groups.length === 0) {
const noGroupsText = lang === 'bn' ? 'বর্তমানে কোনো গ্রুপ উপলব্ধ নেই।' : 'No
groups currently available.';
await sendMessage(chatId, noGroupsText);
return;
}

const caption = lang === 'bn' ? 'আমাদের সকল গ্রুপ লিস্ট। নিচের নামে ক্লিক করে
গ্রুপে জয়েন হয়ে নিন।' : 'Our complete group list. Click on the names below to
join the groups.';
const keyboard = {
inline_keyboard: groups.map(group => [{ text: group.name, url:
group.link }]) // Real links
};
await sendMessage(chatId, `${caption}`, keyboard, 'HTML', true); // Disable web
page preview for cleaner look

// Update Telegram chat menu command


const updateCommandText = lang === 'bn' ? 'আপডেট গ্রুপ লিস্ট' : 'Update Group
List';
await setMyCommands(chatId, [{ command: 'update_groups', description:
updateCommandText }]);
await clearUserState(chatId); // Clear state after real list shown
}

// --- HANDLERS ---

async function handleMessage(message, isAdmin) {


const chatId = message.chat.id;
const userId = message.from.id;
const messageText = message.text;
const username = message.from.username;
const fullName = message.from.first_name + (message.from.last_name ? " " +
message.last_name : "");
const userState = await getUserState(userId);

// Admin flow handling


if (isAdmin && messageText === '/start') {
await clearUserState(userId); // Clear any existing state for admin
await showAdminPanel(chatId);
return;
}

if (isAdmin && userState && userState.admin_flow) {


switch (userState.step) {
case 'await_group_name':
await setUserState(userId, { ...userState, step: 'await_group_link',
group_name: messageText });
await deleteMessage(chatId, message.message_id); // Clean admin's input
message
await sendMessage(chatId, `Got it! Now, please send the **invite link** for
"${messageText}".`, {
inline_keyboard: [[{ text: 'Cancel', callback_data: 'admin_cancel' }]]
});
break;
case 'await_group_link':
const groupLink = messageText;
if (!groupLink.startsWith('http')) {
await sendMessage(chatId, 'Invalid link format. Please provide a full URL
starting with http:// or https://.', {
inline_keyboard: [[{ text: 'Cancel', callback_data: 'admin_cancel' }]]
});
return;
}
const groupName = userState.group_name; // From previous step
const groups = await getKV('groups') || [];
const newGroup = { id: Date.now().toString(), name: groupName, link:
groupLink }; // Simple unique ID
groups.push(newGroup);
await putKV('groups', groups);
await deleteMessage(chatId, message.message_id); // Clean admin's input
message
await sendMessage(chatId, `Group "${groupName}" added successfully!`, {
inline_keyboard: [[{ text: 'Back to Admin Panel', callback_data:
'admin_panel' }]]
});
await clearUserState(userId);
break;
case 'await_edit_group_name': // For editing name
await setUserState(userId, { ...userState, new_group_name: messageText,
step: 'await_edit_group_link' });
await deleteMessage(chatId, message.message_id);
await sendMessage(chatId, `Okay, now send the **new invite link** for "$
{messageText}".`, {
inline_keyboard: [[{ text: 'Cancel', callback_data: 'admin_cancel' }]]
});
break;
case 'await_edit_group_link': // For editing link
const targetGroupId = userState.editing_group_id;
const newGroupName = userState.new_group_name;
const newGroupLink = messageText;
if (!newGroupLink.startsWith('http')) {
await sendMessage(chatId, 'Invalid link format. Please provide a full URL
starting with http:// or https://.', {
inline_keyboard: [[{ text: 'Cancel', callback_data: 'admin_cancel' }]]
});
return;
}
let existingGroups = await getKV('groups') || [];
const indexToEdit = existingGroups.findIndex(g => g.id === targetGroupId);

if (indexToEdit !== -1) {


existingGroups[indexToEdit].name = newGroupName;
existingGroups[indexToEdit].link = newGroupLink;
await putKV('groups', existingGroups);
await deleteMessage(chatId, message.message_id);
await sendMessage(chatId, `Group "${newGroupName}" updated successfully!
`, {
inline_keyboard: [[{ text: 'Back to Admin Panel', callback_data:
'admin_panel' }]]
});
} else {
await deleteMessage(chatId, message.message_id);
await sendMessage(chatId, 'Error: Group not found for editing.', {
inline_keyboard: [[{ text: 'Back to Admin Panel', callback_data:
'admin_panel' }]]
});
}
await clearUserState(userId);
break;
case 'await_custom_first_link_input': // For setting first link by direct
input
const customFirstLink = messageText;
if (!customFirstLink.startsWith('http')) {
await sendMessage(chatId, 'Invalid link format. Please provide a full
URL starting with http:// or https://.', {
inline_keyboard: [[{ text: 'Cancel', callback_data:
'admin_cancel' }]]
});
return;
}
await putKV('firstLink', customFirstLink);
await deleteMessage(chatId, message.message_id);
await sendMessage(chatId, `First link set to: ${customFirstLink}`, {
inline_keyboard: [[{ text: 'Back to Admin Panel', callback_data:
'admin_panel' }]]
});
await clearUserState(userId);
break;
}
return; // Don't process non-admin messages if admin is in a state
}

// User specific message handling


if (messageText === '/start') {
// Check if user already shared contact before
const userProfile = await getKV(`user_profile_${userId}`);
if (userProfile && userProfile.contactShared) {
await showRealGroupList(chatId, userProfile.lang || 'en'); // Use stored lang
or default
return;
}
await showLanguageSelection(chatId);
// Store initial user data in KV for later Google Sheet update
await setUserState(userId, {
step: 'select_lang',
fullName: fullName,
username: username,
// Get current date in Bangladesh time (Asia/Dhaka)
startDate: new Date().toLocaleDateString('en-CA', { timeZone:
'Asia/Dhaka' }), //YYYY-MM-DD
});
} else if (messageText === '/update_groups') {
// This command is added to the chat menu after contact sharing
const userProfile = await getKV(`user_profile_${userId}`);
if (userProfile && userProfile.contactShared) {
await showRealGroupList(chatId, userProfile.lang || 'en');
} else {
const currentLang = userState?.lang || 'en';
const text = currentLang === 'bn' ? 'আপনাকে আগে নাম্বার শেয়ার করতে হবে।' :
'You must share your phone number first.';
await sendMessage(chatId, text);
}
}
// For any other text message from user, if they are on fake_list_shown step,
// it might mean they are trying to click a button, but Telegram sent a text
message.
// We can handle this by re-showing robot check or just ignore.
// Per instruction: "ইউজার যেকোনো একটি বাটনে ক্লিক করে first group link ওপেন করলে
মেসেজটি রিমুভ হয়ে যাবে।"
// So, no explicit text message handling needed here for user flow unless
specified.
}

async function handleCallbackQuery(callbackQuery, isAdmin) {


const chatId = callbackQuery.message.chat.id;
const userId = callbackQuery.from.id;
const messageId = callbackQuery.message.message_id;
const callbackData = callbackQuery.data;
const userState = await getUserState(userId);

// Answer callback query to dismiss loading on button


await fetch(`${TELEGRAM_API}/answerCallbackQuery`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ callback_query_id: callbackQuery.id }),
});

// Admin Panel Callbacks


if (isAdmin) {
if (callbackData === 'admin_panel') {
await showAdminPanel(chatId, messageId);
await clearUserState(userId); // Clear any pending admin state
return;
} else if (callbackData === 'admin_add_group') {
await handleAdminAddGroup(chatId, messageId);
return;
} else if (callbackData === 'admin_edit_group') {
await handleAdminEditGroup(chatId, messageId);
return;
} else if (callbackData === 'admin_delete_group') {
await handleAdminDeleteGroup(chatId, messageId);
return;
} else if (callbackData === 'admin_set_first_link') {
await handleAdminSetFirstLink(chatId, messageId);
return;
} else if (callbackData === 'admin_preview') {
await handleAdminPreview(chatId, messageId);
return;
} else if (callbackData === 'admin_cancel') {
await deleteMessage(chatId, messageId);
await showAdminPanel(chatId);
await clearUserState(userId);
return;
} else if (callbackData.startsWith('edit_group_select_')) {
const groupId = callbackData.replace('edit_group_select_', '');
const groups = await getKV('groups') || [];
const selectedGroup = groups.find(g => g.id === groupId);
if (selectedGroup) {
await deleteMessage(chatId, messageId);
await sendMessage(chatId, `You are editing group: <b>$
{selectedGroup.name}</b> (Current Link: ${selectedGroup.link})\n\nPlease send the
<b>new name</b> for this group.`, {
inline_keyboard: [[{ text: 'Cancel', callback_data: 'admin_cancel' }]]
});
await setUserState(userId, {
step: 'await_edit_group_name',
admin_flow: true,
editing_group_id: groupId,
original_name: selectedGroup.name,
original_link: selectedGroup.link
});
} else {
await deleteMessage(chatId, messageId);
await sendMessage(chatId, 'Error: Group not found.', { inline_keyboard: [[{
text: 'Back to Admin Panel', callback_data: 'admin_panel' }]] });
await clearUserState(userId);
}
return;
} else if (callbackData.startsWith('delete_group_select_')) {
const groupId = callbackData.replace('delete_group_select_', '');
const groups = await getKV('groups') || [];
const selectedGroup = groups.find(g => g.id === groupId);
if (selectedGroup) {
await deleteMessage(chatId, messageId);
await sendMessage(chatId, `Are you sure you want to delete group "<b>$
{selectedGroup.name}</b>"? This action cannot be undone.`, {
inline_keyboard: [
[{ text: 'Yes, Delete', callback_data: `confirm_delete_group_$
{groupId}` }],
[{ text: 'No, Cancel', callback_data: 'admin_cancel' }]
]
});
} else {
await deleteMessage(chatId, messageId);
await sendMessage(chatId, 'Error: Group not found.', { inline_keyboard: [[{
text: 'Back to Admin Panel', callback_data: 'admin_panel' }]] });
await clearUserState(userId);
}
return;
} else if (callbackData.startsWith('confirm_delete_group_')) {
const groupId = callbackData.replace('confirm_delete_group_', '');
let groups = await getKV('groups') || [];
const initialLength = groups.length;
groups = groups.filter(g => g.id !== groupId);
if (groups.length < initialLength) {
await putKV('groups', groups);
await deleteMessage(chatId, messageId);
await sendMessage(chatId, 'Group deleted successfully!', { inline_keyboard:
[[{ text: 'Back to Admin Panel', callback_data: 'admin_panel' }]] });
} else {
await deleteMessage(chatId, messageId);
await sendMessage(chatId, 'Error: Group not found for deletion.',
{ inline_keyboard: [[{ text: 'Back to Admin Panel', callback_data:
'admin_panel' }]] });
}
await clearUserState(userId);
return;
} else if (callbackData.startsWith('set_first_link_select_')) {
const groupId = callbackData.replace('set_first_link_select_', '');
const groups = await getKV('groups') || [];
const selectedGroup = groups.find(g => g.id === groupId);
if (selectedGroup) {
await putKV('firstLink', selectedGroup.link);
await deleteMessage(chatId, messageId);
await sendMessage(chatId, `First link set to: <b>${selectedGroup.name}</b>
(${selectedGroup.link})`, {
inline_keyboard: [[{ text: 'Back to Admin Panel', callback_data:
'admin_panel' }]]
});
} else {
await deleteMessage(chatId, messageId);
await sendMessage(chatId, 'Error: Group not found.', { inline_keyboard: [[{
text: 'Back to Admin Panel', callback_data: 'admin_panel' }]] });
}
await clearUserState(userId);
return;
} else if (callbackData === 'admin_set_custom_first_link') {
await deleteMessage(chatId, messageId);
await sendMessage(chatId, 'Please send the **custom link** to set as the
first link.', {
inline_keyboard: [[{ text: 'Cancel', callback_data: 'admin_cancel' }]]
});
await setUserState(userId, { step: 'await_custom_first_link_input',
admin_flow: true });
return;
}
}

// User Flow Callbacks


if (callbackData.startsWith('lang_')) {
const lang = callbackData.replace('lang_', '');
const userStateData = await getUserState(userId); // Get existing state

// Update user state with selected language


if (userStateData) {
userStateData.lang = lang;
await setUserState(userId, userStateData);
} else { // Should not happen if /start initiated
await setUserState(userId, { lang: lang, step: 'select_lang',
fullName: callbackQuery.from.first_name +
(callbackQuery.from.last_name ? " " + callbackQuery.from.last_name : ""),
username: callbackQuery.from.username,
startDate: new Date().toLocaleDateString('en-
CA', { timeZone: 'Asia/Dhaka' }) }); // Use Bangladesh time
}
await showGenderSelection(chatId, messageId, lang);
} else if (callbackData.startsWith('gender_')) {
const gender = callbackData.replace('gender_', '');
const userStateData = await getUserState(userId);

if (userStateData) {
userStateData.gender = gender;
await setUserState(userId, userStateData); // Update state to store gender

// Save initial user data to Google Sheet


await sendToGoogleSheet('saveUser', {
userId: userId,
fullName: userStateData.fullName,
username: userStateData.username,
phoneNumber: null, // Will be updated later
gender: gender,
startDate: userStateData.startDate // Use stored startDate (Bangladesh
time)
});
}

const firstLink = await getKV('firstLink'); // Get the first link set by admin
if (!firstLink) {
const currentLang = userStateData?.lang || 'en';
const noFirstLinkText = currentLang === 'bn' ? 'অ্যাডমিন এখনো কোনো প্রথম লিংক
সেট করেননি।' : 'Admin has not set a first link yet.';
await deleteMessage(chatId, messageId);
await sendMessage(chatId, noFirstLinkText);
await clearUserState(chatId); // Clear state if no first link
return;
}
await showFakeGroupList(chatId, messageId, userStateData?.lang || 'en',
firstLink);
} else if (userState?.step === 'fake_list_first_shown' && callbackData ===
'fake_link_clicked_first') {
// User clicked the first fake list. Now show the second fake list for robot
check.
const firstLink = await getKV('firstLink'); // Get the first link set by admin
await showFakeGroupListSecondTime(chatId, messageId, userState.lang,
firstLink);
} else if (userState?.step === 'fake_list_second_shown' && callbackData ===
'fake_link_clicked_second') {
// User clicked the second fake list. Now show the robot check.
await showRobotCheck(chatId, messageId, userState.lang);
}
}

async function handleContactShare(message, isAdmin) {


const chatId = message.chat.id;
const userId = message.from.id;
const contact = message.contact;
const phoneNumber = contact.phone_number;
const fullName = message.from.first_name + (message.from.last_name ? " " +
message.from.last_name : "");
const username = message.from.username;
const messageId = message.message_id; // Message containing the contact button
const userState = await getUserState(userId); // Get the current user state
const currentLang = userState?.lang || 'en';

if (userState?.step === 'await_contact') {


// Delete the contact sharing message itself for cleaner chat
await deleteMessage(chatId, messageId);

// Remove the custom keyboard after contact is shared


await sendMessage(chatId, currentLang === 'bn' ? 'ধন্যবাদ! আপনার নাম্বার
সংরক্ষিত হয়েছে।' : 'Thank you! Your number has been saved.', {
reply_markup: { remove_keyboard: true }
});

// Update user profile with phone number and mark contact as shared
let userProfile = await getKV(`user_profile_${userId}`);
if (!userProfile) { // If profile not found, create a new one from current
userState
userProfile = {
userId: userId,
fullName: fullName,
username: username,
startDate: userState?.startDate || new Date().toLocaleDateString('en-CA', {
timeZone: 'Asia/Dhaka' }), // Ensure start date from state or get current BG time
lang: currentLang,
gender: userState?.gender || 'not set',
};
}
userProfile.phoneNumber = phoneNumber;
userProfile.contactShared = true;
userProfile.fullName = fullName; // Always update full name and username from
latest message
userProfile.username = username;

await putKV(`user_profile_${userId}`, userProfile);

// Send updated user data to Google Sheet


await sendToGoogleSheet('saveUser', userProfile);

// Show real group list


await showRealGroupList(chatId, currentLang);
} else {
// If contact is shared unexpectedly, or not in the right step
const unexpectedText = currentLang === 'bn' ? 'অপ্রত্যাশিত নাম্বার শেয়ার।
আপনার নাম্বার সংরক্ষিত হয়েছে।' : 'Unexpected contact share. Your number has been
saved.';
await sendMessage(chatId, unexpectedText, { reply_markup: { remove_keyboard:
true } }); // Remove keyboard even if unexpected

// Still update profile in KV and Sheet even if unexpected share


let userProfile = await getKV(`user_profile_${userId}`);
if (!userProfile) {
userProfile = {
userId: userId,
fullName: fullName,
username: username,
startDate: new Date().toLocaleDateString('en-CA', { timeZone:
'Asia/Dhaka' }), // Use current BG time
lang: currentLang, // Default to current lang if no state
gender: 'not set', // Can't determine gender if not via flow
};
}
userProfile.phoneNumber = phoneNumber;
userProfile.contactShared = true;
userProfile.fullName = fullName;
userProfile.username = username;
await putKV(`user_profile_${userId}`, userProfile);
await sendToGoogleSheet('saveUser', userProfile);

await showRealGroupList(chatId, currentLang); // Show real list


}
}

App script code:

// আপনার Google Sheet ID এখানে দিন


const SPREADSHEET_ID = "1ZiO2ScSGrBS10Y4aF_Ty4vRP_b74KPUtr1Y39At37t8"; // <<<<<<
এখানে তোমার স্প্রেডশীট আইডি দাও
// আপনার ফিমেল এবং মেল শীটের সঠিক নাম দিন
const FEMALE_SHEET_NAME = "Female";
const MALE_SHEET_NAME = "Male";

function doPost(e) {
try {
const requestData = JSON.parse(e.postData.contents);
const action = requestData.action;

switch (action) {
case "saveUser":
return handleSaveUser(requestData.payload);
default:
return createJsonResponse({ status: "error", message: "Unknown action or
invalid request format." }, 400);
}
} catch (error) {
return createJsonResponse({ status: "error", message: "Error processing
request: " + error.message }, 500);
}
}

function doGet(e) {
return ContentService.createTextOutput("Hello from Google Apps Script! Send a
POST request with 'action' and
'payload'.").setMimeType(ContentService.MimeType.TEXT);
}

function handleSaveUser(userData) {
const { userId, fullName, username, phoneNumber, gender, startDate } = userData;

if (!userId) {
return createJsonResponse({ status: "error", message: "User ID is required for
saving user data." }, 400);
}

// Determine target sheet based on gender. If gender is not provided or is null,


use MALE_SHEET_NAME.
const targetSheetName = (gender === "Female") ? FEMALE_SHEET_NAME :
MALE_SHEET_NAME;

const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
const sheet = ss.getSheetByName(targetSheetName);

if (!sheet) {
return createJsonResponse({ status: "error", message: `Sheet not found for
gender: ${targetSheetName}. Please check sheet names.` }, 404);
}

const headers = sheet.getRange("A1:1").getValues()[0];


const lastRow = sheet.getLastRow();

let userRowIndex = -1;


if (lastRow > 1) {
const telegramIdColIndex = headers.indexOf("Telegram ID");
if (telegramIdColIndex !== -1) {
const dataRange = sheet.getRange(2, telegramIdColIndex + 1, lastRow - 1, 1);
const userIdsInSheet = dataRange.getValues().map(row => row[0].toString());
for (let i = 0; i < userIdsInSheet.length; i++) {
if (userIdsInSheet[i] === userId.toString()) {
userRowIndex = i;
break;
}
}
}
}

// Define how each header maps to user data


const rowDataMap = {
"Start Date": startDate || "not set",
"Telegram ID": userId,
"Full Name": fullName || "not set",
"Telegram Username": username ? "@" + username : "not set",
"Phone Number": phoneNumber || "not set",
"Gender": gender || "not set", // If gender is null/undefined, store as "not
set"
};

const rowData = [];


for (let i = 0; i < headers.length; i++) {
const header = headers[i];
rowData.push(rowDataMap[header] !== undefined ? rowDataMap[header] : "");
}

if (userRowIndex !== -1) {


// Update existing row
const existingRowData = sheet.getRange(userRowIndex + 2, 1, 1,
headers.length).getValues()[0];

for (let i = 0; i < headers.length; i++) {


const header = headers[i];
const newValue = rowDataMap[header] !== undefined ? rowDataMap[header] : "";

if (newValue !== "" && (existingRowData[i] === "not set" ||


existingRowData[i] === "" || newValue !== existingRowData[i])) {
sheet.getRange(userRowIndex + 2, i + 1).setValue(newValue);
}
}
return createJsonResponse({ status: "success", message: "User data updated in
Google Sheet." });
} else {
// Add new row at the beginning (row 2)
sheet.insertRowBefore(2);
sheet.getRange(2, 1, 1, rowData.length).setValues([rowData]);
return createJsonResponse({ status: "success", message: "New user data added to
Google Sheet." });
}
}

function createJsonResponse(data, status = 200) {


const output = ContentService.createTextOutput(JSON.stringify(data));
output.setMimeType(ContentService.MimeType.JSON);
return output;
}

You might also like