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;
}