├── price.py ├── token_function.py ├── parse_wallet.py ├── utility_functions.py ├── main.py ├── setup_function.py ├── send_purchases.py ├── database.py ├── bot_command.py └── dm_setup.py /price.py: -------------------------------------------------------------------------------- 1 | import requests, json 2 | 3 | def get_asset(token_address): 4 | url = "https://mainnet.helius-rpc.com/?api-key=PUT YOUR API KEY HERE" 5 | headers = { 6 | 'Content-Type': 'application/json', 7 | } 8 | data = { 9 | "jsonrpc": "2.0", 10 | "id": "my-id", 11 | "method": "getAsset", 12 | "params": { 13 | "id": token_address, 14 | "displayOptions": { 15 | "showFungible": True 16 | } 17 | }, 18 | } 19 | response = requests.post(url, headers=headers, data=json.dumps(data)) 20 | result = response.json()['result'] 21 | price_per_token = result['token_info']['price_info']['price_per_token'] 22 | 23 | return float(price_per_token) -------------------------------------------------------------------------------- /token_function.py: -------------------------------------------------------------------------------- 1 | import requests, json 2 | 3 | def get_token_symbol(token_address): 4 | url = "https://mainnet.helius-rpc.com/?api-key=PUT YOUR API KEY HERE" 5 | headers = { 6 | 'Content-Type': 'application/json', 7 | } 8 | data = { 9 | "jsonrpc": "2.0", 10 | "id": "my-id", 11 | "method": "getAsset", 12 | "params": { 13 | "id": token_address, 14 | "displayOptions": { 15 | "showFungible": True 16 | } 17 | }, 18 | } 19 | response = requests.post(url, headers=headers, data=json.dumps(data)) 20 | result = response.json().get('result', {}) 21 | token_symbol = result.get('token_info', {}).get('symbol', '') 22 | 23 | return token_symbol 24 | -------------------------------------------------------------------------------- /parse_wallet.py: -------------------------------------------------------------------------------- 1 | import requests, datetime 2 | from database import transaction_exists, update_payment_details 3 | 4 | def parse_wallet(wallet_address, sender_wallet): 5 | #Put your Helius API key here 6 | api_key = "" 7 | url = f"https://api.helius.xyz/v0/addresses/{wallet_address}/transactions?api-key={api_key}" 8 | 9 | response = requests.get(url) 10 | data = response.json() 11 | 12 | for transaction in data: 13 | if transaction['type'] == 'TRANSFER' and \ 14 | transaction['feePayer'] == sender_wallet and \ 15 | any(tt['tokenAmount'] >= 50 and tt['mint'] == 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' for tt in transaction['tokenTransfers']): 16 | signature = transaction['signature'] 17 | if not transaction_exists(signature): 18 | current_time = datetime.datetime.utcnow() 19 | cancel_time = current_time + datetime.timedelta(days=30) 20 | update_payment_details(sender_wallet, signature, current_time, cancel_time) 21 | return True 22 | return False 23 | -------------------------------------------------------------------------------- /utility_functions.py: -------------------------------------------------------------------------------- 1 | from telegram import Update 2 | from telegram.ext import CallbackContext 3 | 4 | from dm_setup import wallet_address, handle_uuid_response 5 | from setup_function import contract_address, image_url, chosen_emoji, start_setup 6 | from bot_command import handle_new_token_address 7 | 8 | STEP_CONTRACT, STEP_IMAGE_URL, STEP_EMOJI = range(3) 9 | 10 | async def handle_user_response(update: Update, context: CallbackContext): 11 | if context.user_data.get('expecting_wallet_address'): 12 | await wallet_address(update, context) 13 | context.user_data.pop('expecting_wallet_address', None) 14 | return 15 | 16 | if context.user_data.get('awaiting_uuid'): 17 | await handle_uuid_response(update, context) 18 | context.user_data.pop('awaiting_uuid', None) 19 | return 20 | 21 | if context.user_data.get('editing_token'): 22 | await handle_new_token_address(update, context) 23 | context.user_data.pop('editing_token', None) 24 | return 25 | 26 | setup_step = context.user_data.get('setup_step') 27 | 28 | if setup_step == STEP_CONTRACT: 29 | await contract_address(update, context) 30 | elif setup_step == STEP_IMAGE_URL: 31 | await image_url(update, context) 32 | elif setup_step == STEP_EMOJI: 33 | await chosen_emoji(update, context) 34 | else: 35 | await start_setup(update, context) -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import (ApplicationBuilder, CommandHandler, MessageHandler, 2 | CallbackQueryHandler, filters, CommandHandler) 3 | 4 | import logging 5 | 6 | from utility_functions import handle_user_response 7 | from dm_setup import (start_command, handle_enter_new_wallet, handle_payment_sent, handle_use_existing_wallet, handle_yes_response, setup_buybot_command) 8 | 9 | from bot_command import (bot_command, handle_bot_selection, handle_setup, handle_edit_token, 10 | handle_edit_image, handle_edit_group, handle_edit_emoji, handle_edit_token_no, handle_edit_token_yes) 11 | 12 | from send_purchases import initialize_jobs_from_db 13 | 14 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) 15 | logger = logging.getLogger(__name__) 16 | 17 | # Put your Telegram Bot Token here 18 | TOKEN = '' 19 | CONTRACT, IMAGE_URL, EMOJI, WALLET_ADDRESS = range(4) 20 | 21 | def main(): 22 | application = ApplicationBuilder().token(TOKEN).build() 23 | 24 | application.add_handler(CommandHandler('start', start_command)) 25 | application.add_handler(CommandHandler('bot', bot_command)) 26 | application.add_handler(CommandHandler('setup_buybot', setup_buybot_command)) 27 | 28 | application.add_handler(CallbackQueryHandler(handle_yes_response, pattern='^yes$')) 29 | application.add_handler(CallbackQueryHandler(handle_use_existing_wallet, pattern='^use_existing$')) 30 | application.add_handler(CallbackQueryHandler(handle_enter_new_wallet, pattern='^enter_new$')) 31 | application.add_handler(CallbackQueryHandler(handle_payment_sent, pattern='^payment_sent$')) 32 | application.add_handler(CallbackQueryHandler(handle_bot_selection, pattern='^bot_\\d+$')) 33 | 34 | application.add_handler(CallbackQueryHandler(handle_setup, pattern='^setup$')) 35 | application.add_handler(CallbackQueryHandler(handle_edit_token, pattern='^edit_token$')) 36 | application.add_handler(CallbackQueryHandler(handle_edit_image, pattern='^edit_image$')) 37 | application.add_handler(CallbackQueryHandler(handle_edit_group, pattern='^edit_group$')) 38 | application.add_handler(CallbackQueryHandler(handle_edit_emoji, pattern='^edit_emoji$')) 39 | 40 | application.add_handler(CallbackQueryHandler(handle_edit_token_no, pattern='^edit_token_no$')) 41 | application.add_handler(CallbackQueryHandler(handle_edit_token_yes, pattern='^edit_token_yes$')) 42 | 43 | application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_user_response)) 44 | 45 | initialize_jobs_from_db(application) 46 | 47 | application.run_polling() 48 | 49 | if __name__ == '__main__': 50 | main() 51 | -------------------------------------------------------------------------------- /setup_function.py: -------------------------------------------------------------------------------- 1 | from telegram import Update, ForceReply 2 | from telegram.ext import CallbackContext 3 | import logging 4 | 5 | from database import update_setup_data 6 | from token_function import get_token_symbol 7 | 8 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) 9 | logger = logging.getLogger(__name__) 10 | 11 | STEP_CONTRACT, STEP_IMAGE_URL, STEP_EMOJI = range(3) 12 | 13 | async def store_partial_data_and_proceed(update, context, next_step_func): 14 | logger.info("store_partial_data_and_proceed called") 15 | chat_id = 1 16 | payment_uuid = context.user_data.get('payment_uuid') 17 | logger.info(f"UUID in store_partial_data_and_proceed: {payment_uuid}") 18 | contract_address = context.user_data.get('contract', '') 19 | token_name = context.user_data.get('token_name', '') 20 | image_url = context.user_data.get('image_url', '') 21 | chosen_emoji = context.user_data.get('chosen_emoji', '') 22 | 23 | try: 24 | update_setup_data(payment_uuid, chat_id, contract_address, token_name, image_url, chosen_emoji) 25 | await next_step_func(update, context) 26 | except Exception as e: 27 | logger.error(f"Error in store_partial_data_and_proceed: {e}") 28 | 29 | async def start_setup(query: Update, context: CallbackContext): 30 | context.user_data['setup_step'] = STEP_CONTRACT 31 | logger.info("start_setup called") 32 | await query.message.reply_text( 33 | "Please send me the contract address you want the Solana BuyBot to track.", 34 | reply_markup=ForceReply(selective=True), 35 | ) 36 | 37 | async def contract_address(update: Update, context: CallbackContext): 38 | logger.info("contract_address called") 39 | if 'last_processed_text' not in context.user_data or update.message.text != context.user_data['last_processed_text']: 40 | logger.info("contract_address found context data") 41 | context.user_data['last_processed_text'] = update.message.text 42 | context.user_data['contract'] = update.message.text 43 | context.user_data['token_name'] = get_token_symbol(update.message.text) 44 | context.user_data['setup_step'] = STEP_IMAGE_URL 45 | await store_partial_data_and_proceed(update, context, image_url) 46 | else: 47 | await update.message.reply_text("Please send the contract address.") 48 | 49 | async def image_url(update: Update, context: CallbackContext): 50 | logger.info("image_url called") 51 | if update.message.text != context.user_data.get('last_processed_text'): 52 | logger.info("image_url found context data") 53 | context.user_data['last_processed_text'] = update.message.text 54 | context.user_data['setup_step'] = STEP_EMOJI 55 | context.user_data['image_url'] = update.message.text 56 | await store_partial_data_and_proceed(update, context, chosen_emoji) 57 | else: 58 | await update.message.reply_text("Now please respond with a URL to a JPG, PNG, or GIF.\n\nThis needs to be a DIRECT link to the image, or it will not work.") 59 | 60 | async def chosen_emoji(update: Update, context: CallbackContext): 61 | logger.info("chosen_emoji called") 62 | if update.message.text != context.user_data.get('last_processed_text'): 63 | logger.info("chosen_emoji found context data") 64 | context.user_data['last_processed_text'] = update.message.text 65 | context.user_data['chosen_emoji'] = update.message.text 66 | await store_partial_data_and_proceed(update, context, finalize_setup) 67 | else: 68 | await update.message.reply_text("Please send the emoji you want to use for indicating purchase amounts.") 69 | 70 | async def finalize_setup(update: Update, context: CallbackContext): 71 | logger.info("finalize_setup called") 72 | payment_uuid = context.user_data.get('payment_uuid', '') 73 | instruction_message = ( 74 | f"Setup complete! Your UUID is: {payment_uuid}\n\n" 75 | "Please add the bot to your group or channel.\n" 76 | "Then, use the '/setup_buybot' command in the group or channel.\n" 77 | "When prompted, reply with this UUID.\n" 78 | ) 79 | await update.message.reply_text(instruction_message) 80 | -------------------------------------------------------------------------------- /send_purchases.py: -------------------------------------------------------------------------------- 1 | import aiohttp, urllib.parse, logging 2 | from telegram.ext import CallbackContext 3 | 4 | from database import filter_new_transactions, store_transaction, fetch_image_url, fetch_chosen_emoji, fetch_existing_data 5 | from price import get_asset 6 | 7 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) 8 | logger = logging.getLogger(__name__) 9 | 10 | job_references = {} 11 | 12 | def initialize_jobs_from_db(application): 13 | existing_data = fetch_existing_data() 14 | for chat_id, contract_address, token_name in existing_data: 15 | job_context = {'chat_id': chat_id, 'contract_address': contract_address, 'token_name': token_name} 16 | job = application.job_queue.run_repeating(fetch_and_send_transactions, interval=60, first=0, data=job_context) 17 | job_references[chat_id] = job # Store the job reference 18 | 19 | def initialize_job_for_chat(application, chat_id, contract_address, token_name): 20 | job_context = {'chat_id': chat_id, 'contract_address': contract_address, 'token_name': token_name} 21 | job = application.job_queue.run_repeating(fetch_and_send_transactions, interval=60, first=0, data=job_context) 22 | job_references[chat_id] = job # Store the job reference 23 | 24 | async def fetch_and_send_transactions(context: CallbackContext): 25 | job_context = context.job.data 26 | chat_id = job_context['chat_id'] 27 | token_address = job_context['contract_address'] 28 | logger.info("Token name in job context: %s", job_context['token_name']) 29 | chosen_ticker = job_context['token_name'] 30 | image_url = fetch_image_url(chat_id) 31 | chosen_emoji = fetch_chosen_emoji(chat_id) 32 | 33 | if not chat_id or not token_address or not chosen_ticker or not image_url or not chosen_emoji: 34 | logger.info("Missing one or more required data points, skipping operation.") 35 | return 36 | 37 | price_per_token = get_asset(token_address) 38 | 39 | url = f'https://api.helius.xyz/v0/addresses/{token_address}/transactions?api-key=PUT YOUR API KEY HERE' 40 | try: 41 | async with aiohttp.ClientSession() as session: 42 | async with session.get(url) as response: 43 | if response.status == 200: 44 | data = await response.json() 45 | 46 | swap_transactions = [t for t in data if t['type'] == 'SWAP'] 47 | transaction_hashes = [t['signature'] for t in swap_transactions] 48 | 49 | new_transaction_hashes = filter_new_transactions(chat_id, transaction_hashes) 50 | 51 | for transaction in swap_transactions: 52 | transaction_hash = transaction['signature'] 53 | 54 | if transaction_hash in new_transaction_hashes: 55 | description_parts = transaction['description'].split(' ') 56 | 57 | swapped_index = description_parts.index('swapped') 58 | for_index = description_parts.index('for') 59 | 60 | if swapped_index != -1 and for_index != -1: 61 | first_ticker = description_parts[swapped_index + 2] 62 | second_amount_raw = description_parts[for_index + 1].replace(',', '') 63 | second_ticker = description_parts[for_index + 2] 64 | 65 | if first_ticker != chosen_ticker and second_ticker == chosen_ticker: 66 | try: 67 | second_amount_float = float(second_amount_raw) 68 | total_amount_in_dollars = second_amount_float * price_per_token 69 | 70 | num_circles = max(1, int(total_amount_in_dollars // 10)) 71 | circles = chosen_emoji * num_circles 72 | 73 | second_amount_formatted = f"${total_amount_in_dollars:,.2f}" 74 | except Exception as e: 75 | logger.error(f"Error in calculating total amount in dollars: {e}") 76 | second_amount_formatted = f"{second_amount_raw} (error in conversion)" 77 | 78 | share_text = f"${chosen_ticker} is PUMPING!\n\nIYKYK\nhttps://birdeye.so/token/{transaction_hash}?chain=solana" 79 | encoded_text = urllib.parse.quote_plus(share_text) 80 | twitter_url = f"https://twitter.com/intent/tweet?text={encoded_text}" 81 | 82 | message = ( 83 | f"{circles}\n" 84 | f"{second_amount_formatted} of ${chosen_ticker} purchased.\n\n" 85 | f"📈: Birdeye\n" 86 | f"🖨: Transaction\n" 87 | f"🐦: Share Tweet" 88 | ) 89 | 90 | if image_url: 91 | if image_url.lower().endswith('.gif'): 92 | await context.bot.send_animation(chat_id=chat_id, animation=image_url, caption=message, parse_mode='HTML') 93 | elif image_url.lower().endswith(('.jpg', '.jpeg', '.png')): 94 | await context.bot.send_photo(chat_id=chat_id, photo=image_url, caption=message, parse_mode='HTML') 95 | else: 96 | logger.info("Image URL does not have a recognized extension, sending message without image.") 97 | await context.bot.send_message(chat_id=chat_id, text=message, parse_mode='HTML') 98 | else: 99 | await context.bot.send_message(chat_id=chat_id, text=message, parse_mode='HTML') 100 | 101 | store_transaction(chat_id, transaction_hash) 102 | 103 | else: 104 | logger.error(f"fetch_and_send_transactions: Failed to fetch data. Status code: {response.status}") 105 | except Exception as e: 106 | logger.error(f"fetch_and_send_transactions: An error occurred: {e}") 107 | 108 | logger.info("fetch_and_send_transactions executed") 109 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | import mysql.connector, time, logging 2 | from mysql.connector import Error 3 | from datetime import datetime 4 | 5 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) 6 | logger = logging.getLogger(__name__) 7 | 8 | #Put your SQL database info here 9 | def execute_db_query(query, params=None, is_fetch=False): 10 | connection = None 11 | cursor = None 12 | try: 13 | connection = mysql.connector.connect( 14 | host="", 15 | port=, 16 | user="", 17 | password="", 18 | database="", 19 | charset='' 20 | ) 21 | cursor = connection.cursor() 22 | cursor.execute(query, params) 23 | 24 | if is_fetch: 25 | return cursor.fetchall() 26 | else: 27 | connection.commit() 28 | 29 | except Error as e: 30 | print(f"Error: {e}") 31 | time.sleep(5) 32 | 33 | finally: 34 | if cursor: 35 | cursor.close() 36 | if connection: 37 | connection.close() 38 | 39 | def store_data(user_id, chat_id, contract_address, token_name, image_url, chosen_emoji): 40 | query = """ 41 | INSERT INTO telegram_bot_data (user_id, chat_id, contract_address, token_name, image_url, chosen_emoji) 42 | VALUES (%s, %s, %s, %s, %s, %s) 43 | """ 44 | execute_db_query(query, (user_id, chat_id, contract_address, token_name, image_url, chosen_emoji)) 45 | 46 | def fetch_existing_data(): 47 | query = "SELECT chat_id, contract_address, token_name FROM telegram_bot_data" 48 | return execute_db_query(query, is_fetch=True) 49 | 50 | def filter_new_transactions(chat_id, transaction_hashes): 51 | format_strings = ','.join(['%s'] * len(transaction_hashes)) 52 | query = f"SELECT transaction_hash FROM processed_transactions WHERE chat_id = %s AND transaction_hash IN ({format_strings})" 53 | params = [chat_id, *transaction_hashes] 54 | result = execute_db_query(query, params, is_fetch=True) 55 | existing_hashes = {row[0] for row in result} 56 | new_hashes = set(transaction_hashes) - existing_hashes 57 | return list(new_hashes) 58 | 59 | def store_transaction(chat_id, transaction_hash): 60 | insert_query = "INSERT INTO processed_transactions (chat_id, transaction_hash) VALUES (%s, %s)" 61 | execute_db_query(insert_query, (chat_id, transaction_hash)) 62 | 63 | delete_query = """ 64 | DELETE FROM processed_transactions 65 | WHERE chat_id = %s AND transaction_hash NOT IN ( 66 | SELECT transaction_hash 67 | FROM ( 68 | SELECT transaction_hash 69 | FROM processed_transactions 70 | WHERE chat_id = %s 71 | ORDER BY transaction_hash DESC 72 | LIMIT 100 73 | ) AS subquery 74 | ) 75 | """ 76 | execute_db_query(delete_query, (chat_id, chat_id)) 77 | 78 | def fetch_image_url(chat_id): 79 | query = "SELECT image_url FROM telegram_bot_data WHERE chat_id = %s" 80 | result = execute_db_query(query, (chat_id,), is_fetch=True) 81 | if result: 82 | return result[0][0] 83 | return None 84 | 85 | def fetch_chosen_emoji(chat_id): 86 | query = "SELECT chosen_emoji FROM telegram_bot_data WHERE chat_id = %s" 87 | result = execute_db_query(query, (chat_id,), is_fetch=True) 88 | if result: 89 | return result[0][0] 90 | return '🟢' 91 | 92 | def store_user_wallet(user_id, wallet_address): 93 | default_contract_address = "" 94 | default_token_name = "" 95 | default_image_url = "" 96 | default_chosen_emoji = "" 97 | chat_id = "1" 98 | 99 | query = """ 100 | INSERT INTO telegram_bot_data 101 | (chat_id, user_id, wallet_address, contract_address, token_name, image_url, chosen_emoji) 102 | VALUES (%s, %s, %s, %s, %s, %s, %s) 103 | """ 104 | params = (chat_id, user_id, wallet_address, default_contract_address, default_token_name, default_image_url, default_chosen_emoji) 105 | execute_db_query(query, params) 106 | 107 | def fetch_user_wallet(user_id): 108 | query = "SELECT wallet_address FROM telegram_bot_data WHERE user_id = %s" 109 | result = execute_db_query(query, (user_id,), is_fetch=True) 110 | if result: 111 | return result[0][0] 112 | else: 113 | return None 114 | 115 | def transaction_exists(signature): 116 | query = "SELECT COUNT(1) FROM telegram_bot_data WHERE payment_transaction = %s" 117 | result = execute_db_query(query, (signature,), is_fetch=True) 118 | return result[0][0] > 0 119 | 120 | def update_payment_details(user_wallet, signature, payment_date, cancel_date): 121 | query = """ 122 | UPDATE telegram_bot_data 123 | SET payment_transaction = %s, payment_date = %s, cancel_date = %s 124 | WHERE wallet_address = %s 125 | """ 126 | execute_db_query(query, (signature, payment_date, cancel_date, user_wallet)) 127 | 128 | def fetch_active_bots(user_id): 129 | try: 130 | current_time = datetime.utcnow() 131 | query = """ 132 | SELECT contract_address, token_name, payment_uuid 133 | FROM telegram_bot_data 134 | WHERE user_id = %s AND cancel_date > %s 135 | """ 136 | params = (user_id, current_time) 137 | result = execute_db_query(query, params, is_fetch=True) 138 | return result if result else [] 139 | except Exception as e: 140 | logger.error(f"Error in fetch_active_bots: {e}") 141 | return [] 142 | 143 | def store_payment_uuid(user_id, payment_uuid): 144 | query = """ 145 | UPDATE telegram_bot_data 146 | SET payment_uuid = %s 147 | WHERE user_id = %s 148 | """ 149 | execute_db_query(query, (payment_uuid, user_id)) 150 | 151 | def transaction_exists(payment_uuid): 152 | query = "SELECT COUNT(1) FROM telegram_bot_data WHERE payment_uuid = %s" 153 | result = execute_db_query(query, (payment_uuid,), is_fetch=True) 154 | return result[0][0] > 0 155 | 156 | def update_chat_id_for_uuid(chat_id, payment_uuid): 157 | query = """ 158 | UPDATE telegram_bot_data 159 | SET chat_id = %s 160 | WHERE payment_uuid = %s 161 | """ 162 | execute_db_query(query, (chat_id, payment_uuid)) 163 | 164 | def update_setup_data(payment_uuid, chat_id, contract_address, token_name, image_url, chosen_emoji): 165 | logger.info("update_setup_data called") 166 | query = """ 167 | UPDATE telegram_bot_data 168 | SET chat_id = %s, contract_address = %s, token_name = %s, image_url = %s, chosen_emoji = %s 169 | WHERE payment_uuid = %s 170 | """ 171 | params = (chat_id, contract_address, token_name, image_url, chosen_emoji, payment_uuid) 172 | print("update_setup_data Query data:", params) 173 | try: 174 | execute_db_query(query, params) 175 | except Error as e: 176 | logger.error(f"Error in update_setup_data: {e}") 177 | raise 178 | 179 | def fetch_current_token(payment_uuid): 180 | query = "SELECT contract_address, token_name FROM telegram_bot_data WHERE payment_uuid = %s" 181 | result = execute_db_query(query, (payment_uuid,), is_fetch=True) 182 | if result: 183 | return result[0][0], result[0][1] 184 | else: 185 | return None, None 186 | 187 | def update_token_address(payment_uuid, new_token_address, token_name): 188 | query = "UPDATE telegram_bot_data SET contract_address = %s, token_name = %s WHERE payment_uuid = %s" 189 | execute_db_query(query, (new_token_address, token_name, payment_uuid)) -------------------------------------------------------------------------------- /bot_command.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CallbackContext 2 | from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup 3 | import logging, re 4 | from database import fetch_active_bots, fetch_current_token, update_token_address 5 | from setup_function import start_setup 6 | from token_function import get_token_symbol 7 | from send_purchases import job_references 8 | 9 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) 10 | logger = logging.getLogger(__name__) 11 | 12 | async def bot_command(update: Update, context: CallbackContext): 13 | global active_bots_mapping 14 | chat_type = update.effective_chat.type 15 | user_id = update.effective_chat.id 16 | 17 | if chat_type != "private": 18 | return 19 | 20 | active_bots = fetch_active_bots(user_id) 21 | if active_bots: 22 | bot_listing = "Select a bot to manage:\n" 23 | keyboard = [] 24 | active_bots_mapping = {str(index): bot[2] for index, bot in enumerate(active_bots, start=1)} 25 | for index, bot in enumerate(active_bots, start=1): 26 | _, _, payment_uuid = bot 27 | bot_listing += f"{index}. {payment_uuid}\n" 28 | keyboard.append([InlineKeyboardButton(str(index), callback_data=f"bot_{index}")]) 29 | 30 | reply_markup = InlineKeyboardMarkup(keyboard) 31 | await update.message.reply_text(bot_listing, reply_markup=reply_markup) 32 | else: 33 | await update.message.reply_text( 34 | "You do not have any active bots.\n\nPlease purchase a bot to use this command.\n\nUse the \"/start\" command to get started!") 35 | 36 | async def handle_bot_selection(update: Update, context: CallbackContext): 37 | global active_bots_mapping 38 | query = update.callback_query 39 | await query.answer() 40 | 41 | try: 42 | selected_number = query.data.replace('bot_', '') 43 | payment_uuid = active_bots_mapping.get(selected_number) 44 | 45 | if payment_uuid: 46 | context.user_data['payment_uuid'] = payment_uuid 47 | keyboard = [ 48 | [InlineKeyboardButton("Setup", callback_data="setup")], 49 | [InlineKeyboardButton("Edit Token", callback_data="edit_token"), 50 | InlineKeyboardButton("Edit Image", callback_data="edit_image")], 51 | [InlineKeyboardButton("Edit TG Group", callback_data="edit_group"), 52 | InlineKeyboardButton("Edit Emoji", callback_data="edit_emoji")] 53 | ] 54 | reply_markup = InlineKeyboardMarkup(keyboard) 55 | 56 | await context.bot.send_message( 57 | chat_id=update.effective_chat.id, 58 | text=f"You chose the bot:\n{payment_uuid}.\n\nSelect an option to manage your bot:", 59 | reply_markup=reply_markup 60 | ) 61 | logger.info("Bot management options sent for bot: %s", payment_uuid) 62 | else: 63 | await context.bot.send_message( 64 | chat_id=update.effective_chat.id, 65 | text="Invalid selection or bot not found." 66 | ) 67 | logger.error("Invalid selection or bot not found.") 68 | 69 | except Exception as e: 70 | logger.exception("Error in handle_bot_selection: %s", str(e)) 71 | await context.bot.send_message( 72 | chat_id=update.effective_chat.id, 73 | text="An error occurred while processing your request." 74 | ) 75 | 76 | async def handle_setup(update: Update, context: CallbackContext): 77 | logger.info("handle_setup called") 78 | query = update.callback_query 79 | await query.answer() 80 | 81 | message_text = query.message.text 82 | logger.info(f"Message Text: {message_text}") 83 | 84 | try: 85 | match = re.search(r'\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b', message_text) 86 | if match: 87 | payment_uuid = match.group(0) 88 | context.user_data['payment_uuid'] = payment_uuid 89 | context.user_data.pop('last_processed_text', None) 90 | await start_setup(query, context) 91 | else: 92 | await context.bot.send_message(chat_id=update.effective_chat.id, text="Invalid UUID format.") 93 | except Exception as e: 94 | logger.error(f"Error in handle_setup: {e}") 95 | await context.bot.send_message(chat_id=update.effective_chat.id, text="An error occurred while processing your request.") 96 | 97 | async def handle_edit_token(update: Update, context: CallbackContext): 98 | query = update.callback_query 99 | await query.answer() 100 | 101 | payment_uuid = context.user_data.get('payment_uuid') 102 | if not payment_uuid: 103 | await context.bot.send_message(chat_id=update.effective_chat.id, text="Bot UUID not found.") 104 | return 105 | 106 | current_token_address, current_token_name = fetch_current_token(payment_uuid) 107 | context.user_data['current_token_name'] = current_token_name 108 | await context.bot.send_message( 109 | chat_id=update.effective_chat.id, 110 | text=f"Current Token:\n${current_token_name}\n{current_token_address}\n\nDo you want to change the token?", 111 | reply_markup=InlineKeyboardMarkup([ 112 | [InlineKeyboardButton("Yes, edit token", callback_data="edit_token_yes")], 113 | [InlineKeyboardButton("No", callback_data="edit_token_no")] 114 | ]) 115 | ) 116 | 117 | async def handle_edit_token_yes(update: Update, context: CallbackContext): 118 | query = update.callback_query 119 | await query.answer() 120 | 121 | await context.bot.send_message( 122 | chat_id=update.effective_chat.id, 123 | text="Please respond with the new token address" 124 | ) 125 | context.user_data['editing_token'] = True 126 | 127 | async def handle_edit_token_no(update: Update, context: CallbackContext): 128 | query = update.callback_query 129 | await query.answer() 130 | 131 | await context.bot.send_message( 132 | chat_id=update.effective_chat.id, 133 | text="No changes made to the token address." 134 | ) 135 | 136 | async def handle_new_token_address(update: Update, context: CallbackContext): 137 | if context.user_data.get('editing_token'): 138 | new_token_address = update.message.text 139 | token_name = get_token_symbol(update.message.text) 140 | payment_uuid = context.user_data.get('payment_uuid') 141 | chat_id = update.effective_chat.id 142 | 143 | update_token_address(payment_uuid, new_token_address, token_name) 144 | await update.message.reply_text("Token address updated successfully.") 145 | 146 | if chat_id in job_references: 147 | job = job_references[chat_id] 148 | job.job.data['contract_address'] = new_token_address 149 | job.job.data['token_name'] = token_name 150 | 151 | context.user_data.pop('editing_token', None) 152 | 153 | async def handle_edit_image(update: Update, context: CallbackContext): 154 | query = update.callback_query 155 | await query.answer() 156 | await context.bot.send_message(chat_id=update.effective_chat.id, text="Edit Image button pressed.") 157 | 158 | async def handle_edit_group(update: Update, context: CallbackContext): 159 | query = update.callback_query 160 | await query.answer() 161 | await context.bot.send_message(chat_id=update.effective_chat.id, text="Edit TG Group button pressed.") 162 | 163 | async def handle_edit_emoji(update: Update, context: CallbackContext): 164 | query = update.callback_query 165 | await query.answer() 166 | await context.bot.send_message(chat_id=update.effective_chat.id, text="Edit Emoji button pressed.") -------------------------------------------------------------------------------- /dm_setup.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CallbackContext 2 | from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup 3 | import logging, uuid 4 | 5 | from send_purchases import initialize_job_for_chat 6 | 7 | from database import store_user_wallet, fetch_user_wallet, store_payment_uuid, execute_db_query 8 | from parse_wallet import parse_wallet 9 | 10 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) 11 | logger = logging.getLogger(__name__) 12 | 13 | async def start_command(update: Update, context: CallbackContext): 14 | chat_type = update.effective_chat.type 15 | #Put your bot image here! 16 | image_url = "" 17 | 18 | if chat_type == "private": 19 | caption = ( 20 | "Welcome to the premiere Solana BuyBot for Telegram groups!\n\n" 21 | "Features:\n" 22 | "- Select an SPL token to track.\n" 23 | "- Customize emojis for buybot messages.\n" 24 | "- Customize the image in messages.\n" 25 | "- Customize the text your users will tweet when they click the 'share tweet' button.\n" 26 | "- Manage multiple buy bots with memberships.\n\n" 27 | "Membership: $50 for 30 days (one token, one group).\n\n" 28 | "Only the purchaser can manage the buybot.\n\n" 29 | "Would you like to sign up to use the buybot?" 30 | ) 31 | 32 | keyboard = [ 33 | [InlineKeyboardButton("Yes", callback_data='yes'), 34 | InlineKeyboardButton("No", callback_data='no')] 35 | ] 36 | reply_markup = InlineKeyboardMarkup(keyboard) 37 | 38 | await update.message.reply_photo(photo=image_url, caption=caption, reply_markup=reply_markup) 39 | else: 40 | await update.message.reply_text("Please use the /start command in a DM.") 41 | 42 | async def wallet_address(update: Update, context: CallbackContext): 43 | user_id = update.effective_chat.id 44 | wallet = update.message.text 45 | 46 | store_user_wallet(user_id, wallet) 47 | 48 | await update.message.reply_text( 49 | f"Please send $50 USDC to the following address:\n" 50 | "UifrcG2hjT2p4F2e96dLUrxdmKP8VedwfVb1zNpUnuo\n" 51 | "Click the button once you've sent the payment.", 52 | reply_markup=InlineKeyboardMarkup([ 53 | [InlineKeyboardButton("Sent the Payment", callback_data='payment_sent')] 54 | ]) 55 | ) 56 | 57 | async def handle_use_existing_wallet(update: Update, context: CallbackContext): 58 | query = update.callback_query 59 | await query.answer() 60 | 61 | user_id = query.from_user.id 62 | user_wallet = fetch_user_wallet(user_id) 63 | 64 | await query.message.reply_text( 65 | f"You are using your existing wallet: {user_wallet}\n" 66 | "Please send $50 USDC to the following address:\n" 67 | "UifrcG2hjT2p4F2e96dLUrxdmKP8VedwfVb1zNpUnuo", 68 | reply_markup=InlineKeyboardMarkup([ 69 | [InlineKeyboardButton("Sent the Payment", callback_data='payment_sent')] 70 | ]) 71 | ) 72 | 73 | async def handle_enter_new_wallet(update: Update, context: CallbackContext): 74 | query = update.callback_query 75 | await query.answer() 76 | 77 | await query.message.reply_text("Please provide the new wallet address you will be sending the $50 USDC payment from.") 78 | context.user_data['telegram_id'] = query.from_user.id 79 | 80 | async def handle_yes_response(update: Update, context: CallbackContext): 81 | query = update.callback_query 82 | await query.answer() 83 | user_id = query.from_user.id 84 | 85 | existing_wallet = fetch_user_wallet(user_id) 86 | if existing_wallet: 87 | message = ( 88 | f"You have previously used the wallet address: {existing_wallet}\n" 89 | "Would you like to use this wallet again?" 90 | ) 91 | keyboard = [ 92 | [InlineKeyboardButton("Use Existing Wallet", callback_data='use_existing')], 93 | [InlineKeyboardButton("Enter New Wallet", callback_data='enter_new')] 94 | ] 95 | reply_markup = InlineKeyboardMarkup(keyboard) 96 | await query.message.reply_text(message, reply_markup=reply_markup) 97 | else: 98 | await query.message.reply_text("Please provide the wallet address you will be sending the $50 USDC payment from.") 99 | context.user_data['telegram_id'] = user_id 100 | context.user_data['expecting_wallet_address'] = True 101 | 102 | async def handle_wallet_address(update: Update, context: CallbackContext): 103 | wallet_address = update.message.text 104 | user_id = context.user_data['telegram_id'] 105 | 106 | store_user_wallet(user_id, wallet_address) 107 | 108 | await update.message.reply_text( 109 | f"Please send $50 USDC to the following address:\n" 110 | "UifrcG2hjT2p4F2e96dLUrxdmKP8VedwfVb1zNpUnuo\n" 111 | "Click the button once you've sent the payment.", 112 | reply_markup=InlineKeyboardMarkup([ 113 | [InlineKeyboardButton("Sent the Payment", callback_data='payment_sent')] 114 | ]) 115 | ) 116 | 117 | async def handle_payment_sent(update: Update, context: CallbackContext): 118 | query = update.callback_query 119 | await query.answer() 120 | 121 | user_id = query.from_user.id 122 | user_wallet = fetch_user_wallet(user_id) 123 | 124 | payment_received = parse_wallet("UifrcG2hjT2p4F2e96dLUrxdmKP8VedwfVb1zNpUnuo", user_wallet) 125 | if payment_received: 126 | payment_uuid = str(uuid.uuid4()) 127 | store_payment_uuid(user_id, payment_uuid) 128 | 129 | await query.message.reply_text(f"Payment received. Thank you!\n\nYour Payment UUID is: {payment_uuid}\n\nPlease use \"/bot\" command to start setting up your BuyBot!") 130 | else: 131 | await query.message.reply_text("Payment not received yet. Please check again later.") 132 | 133 | async def setup_buybot_command(update: Update, context: CallbackContext): 134 | chat_type = update.effective_chat.type 135 | if chat_type in ["group", "supergroup", "channel"]: 136 | context.user_data['awaiting_uuid'] = True 137 | await update.message.reply_text("Please reply with the UUID for the bot you want to set up.\n\nThat means, please click\"reply\" on the top right of this message, and then paste your UUID into the response.") 138 | else: 139 | await update.message.reply_text("This command can only be used in groups or channels.") 140 | 141 | async def handle_uuid_response(update: Update, context: CallbackContext): 142 | if context.user_data.get('awaiting_uuid'): 143 | uuid = update.message.text 144 | chat_id = update.effective_chat.id 145 | 146 | query = "SELECT chat_id, contract_address, token_name FROM telegram_bot_data WHERE payment_uuid = %s" 147 | result = execute_db_query(query, (uuid,), is_fetch=True) 148 | 149 | if result: 150 | retrieved_chat_id, contract_address, token_name = result[0] 151 | logger.info(f"Retrieved data from database: chat_id={retrieved_chat_id}, contract_address={contract_address}, token_name={token_name}") 152 | else: 153 | logger.info("No result found for the given UUID.") 154 | 155 | if not result: 156 | await update.message.reply_text("There's no UUID that matches that.") 157 | elif result and str(retrieved_chat_id) == str(1): 158 | update_query = "UPDATE telegram_bot_data SET chat_id = %s WHERE payment_uuid = %s" 159 | execute_db_query(update_query, (chat_id, uuid)) 160 | await update.message.reply_text("Setup has been completed.\n\nThe Solana BuyBot should start momentarily.\n\nImportant note:\nAt the beginning of the process, the bot will be cataloging past purchases - so there will be many purchases coming through that might not be current. After that process it will keep all purchases up-to-date within 1-2 minutes of purchase.") 161 | 162 | initialize_job_for_chat(context.application, chat_id, contract_address, token_name) 163 | 164 | else: 165 | await update.message.reply_text("There's already an assigned chat for the bot, please go reset the chat using the /bot command in a DM to access bot management.") 166 | 167 | context.user_data.pop('awaiting_uuid', None) --------------------------------------------------------------------------------