import wownero import config import logging import db import six from functools import wraps from decimal import Decimal def log_event(f): @wraps(f) def decorated_function(*args, **kwargs): msg = args[0].message logging.info(f'"{f.__name__}" invoked from {msg.from_user["id"]} ({msg.from_user["first_name"]}) - Full command: "{msg.text}"') return f(*args, **kwargs) return decorated_function def wallet_rpc_required(f): @wraps(f) def decorated_function(*args, **kwargs): wallet = wownero.Wallet() if not wallet.connected: logging.error(f'Wallet RPC interface is not available: {args[0].message}') args[0].message.reply_text('Wallet RPC interface is not available right now. Try again later.') return False return f(*args, **kwargs) return decorated_function def registration_required(f): @wraps(f) def decorated_function(*args, **kwargs): wallet = wownero.Wallet() if not db.User.filter(telegram_id=args[0].message.from_user['id']): args[0].message.reply_text('You are not yet registered. Issue the /register command.') return False return f(*args, **kwargs) return decorated_function def help(update, context): commands = list() for i in all_commands: pk = all_commands[i] if not pk.get('admin', False): commands.append('{example} - {help}'.format( example=pk['example'], help=pk['help'] )) update.message.reply_text('Here are the available commands for this bot:\n\n' + '\n\n'.join(commands)) @wallet_rpc_required @log_event def register(update, context): uid = update.message.from_user['id'] un = update.message.from_user['first_name'] if db.User.filter(telegram_id=uid): if db.User.filter(telegram_id=uid, telegram_user=un): update.message.reply_text('You are already registered. Use /help to see available bot commands.') else: update.message.reply_text('Your ID exists in the database already but your `first_name` attribute has changed. Updating.') try: u = db.User.get(telegram_id=uid) u.telegram_user = un u.save() update.message.reply_text(f'You have been registered again as Telegram ID {uid} but with username {un}.') except Exception as e: logging.error(f'Unable to update user in DB: {e}. Debug: {update.message}') update.message.reply_text('Unable to update your existing account. Ask for help.') return False else: try: wallet = wownero.Wallet() account_index = wallet.new_account(label=un) except Exception as e: logging.error(f'Unable to create a new account in wallet RPC: {e}. Debug: {update.message}') update.message.reply_text('Unable to create a new account for you. Ask for help.') return False try: u = db.User( telegram_id=uid, telegram_user=un, account_index=account_index, ) u.save() reply_text = [ f'You have been registered as Telegram ID {uid} and username {un} and can now send and receive tips.', 'Ask for /help to see all available bot commands. Maybe start with /deposit to get your deposit address.' ] update.message.reply_text(' '.join(reply_text)) except Exception as e: logging.error(f'Unable to register user in DB: {e}. Debug: {update.message}') update.message.reply_text('Unable to create a new account for you. Ask for help.') return False @wallet_rpc_required @registration_required @log_event def tip(update, context): if len(context.args) < 2: update.message.reply_text('Not enough arguments passed.') return False elif len(context.args) == 2: message = "" elif len(context.args) > 2: message = context.args[2:] # validate target user if context.args[0].startswith('@'): target_un = context.args[0][1:] else: target_un = context.args[0] if target_un == update.message.from_user['first_name']: update.message.reply_text('You cannot tip yourself!') return False if not db.User.filter(telegram_user=target_un): reply_text = [ 'That user has not registered and cannot receive tips yet.', 'If they would like to receive a tip, have them /register with the bot.' ] update.message.reply_text(' '.join(reply_text)) return False # validate amount try: amount = Decimal(context.args[1]) except: update.message.reply_text(f'Bad Wownero amount specified; "{context.args[1]}" is not a valid number.') return False if amount < 1: update.message.reply_text('Bad Wownero amount specified. Provide only positive integers or decimals greater than or equal to 1.') return False tipper = db.User.get(telegram_id=update.message.from_user['id']) tipper_balances = wownero.Wallet().balances(account=tipper.account_index) if amount >= tipper_balances[1]: update.message.reply_text(f'You do not have sufficient funds to send {amount} WOW. Check your /balance') return False # get target user details receiver = db.User.get(telegram_user=target_un) address = wownero.Wallet().addresses(account=receiver.account_index)[0] # transfer funds to user try: tx = wownero.Wallet().transfer(dest_address=address, amount=wownero.as_wownero(amount), priority=2, account=tipper.account_index) if 'tx_hash' in tx: update.message.reply_text(f'Tipped @{target_un} {wownero.from_atomic(tx["amount"])} WOW! Here is the TX ID: {tx["tx_hash"]}') else: update.message.reply_text('Failed to send a tip. Ask for help.') except Exception as e: logging.error(f'Unable to send transfer: {e}. Debug: {update.message}') update.message.reply_text('Failed to send a tip. Ask for help.') @wallet_rpc_required @registration_required @log_event def send(update, context): if len(context.args) < 2: update.message.reply_text('Not enough arguments passed.') return False # validate address if len(context.args[0]) in [97, 107]: address = context.args[0] else: update.message.reply_text('This does not look like a valid Wownero address. Try again.') return False # validate amount try: amount = wownero.as_wownero(context.args[1]) except: update.message.reply_text(f'Bad Wownero amount specified; "{context.args[1]}" is not a valid number.') return False if amount < 1: update.message.reply_text('Bad Wownero amount specified. Provide only positive integers or decimals greater than or equal to 1.') return False sender = db.User.get(telegram_id=update.message.from_user['id']) sender_balances = wownero.Wallet().balances(account=sender.account_index) if amount > sender_balances[1]: update.message.reply_text(f'You do not have sufficient funds to send {amount} WOW. Check your /balance') return False # transfer funds to given address try: tx = wownero.Wallet().transfer(dest_address=address, amount=wownero.as_wownero(amount), priority=2, account=sender.account_index) if 'tx_hash' in tx: update.message.reply_text(f'Sent {wownero.from_atomic(tx["amount"])} WOW! Here is the TX ID: {tx["tx_hash"]}') else: update.message.reply_text('Failed to send Wownero. Ask for help.') except Exception as e: logging.error(f'Unable to send transfer: {e}. Debug: {update.message}') update.message.reply_text('Failed to send Wownero. Ask for help.') @wallet_rpc_required @registration_required @log_event def balance(update, context): u = db.User.get(telegram_id=update.message.from_user['id']) balances = wownero.Wallet().balances(account=u.account_index) update.message.reply_text(f'Available balance for {u.telegram_user}: {float(balances[1])} WOW ({float(balances[0])} WOW locked)') @wallet_rpc_required @registration_required @log_event def deposit(update, context): u = db.User.get(telegram_id=update.message.from_user['id']) address = wownero.Wallet().addresses(account=u.account_index)[0] update.message.reply_text(f'Deposit address for {u.telegram_user}: {address}') @wallet_rpc_required @log_event def debug(update, context): if is_tg_admin(update.message.from_user['id']): pass else: update.message.reply_text('you cant do that.') def is_tg_admin(chat_id): if chat_id == config.TG_ADMIN_ID: return True else: return False all_commands = { 'tip': { 'func': tip, 'example': '/tip ', 'help': 'Tip a user in Wownero' }, 'send': { 'func': send, 'example': '/send
', 'help': 'Send Wownero to a specified Wownero address' }, 'balance': { 'func': balance, 'example': '/balance', 'help': 'Show your current balance' }, 'register': { 'func': register, 'example': '/register', 'help': 'Register your Telegram user ID to this bot to begin sending and receiving tips', }, 'deposit': { 'func': deposit, 'example': '/deposit', 'help': 'Show your Wownero wallet address for transferring funds to' }, 'help': { 'func': help, 'example': '/help', 'help': 'Show available commands for the bot', }, 'debug': { 'func': debug, 'admin': True } }