adding code so far
parent
c0d57071dd
commit
3f0a3748c5
@ -1,3 +1,43 @@
|
|||||||
# tg-tipbot
|
# tg-tipbot
|
||||||
|
|
||||||
Telegram tip bot
|
Telegram tip bot.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```
|
||||||
|
# initialize new wallet and retain seed
|
||||||
|
docker run --rm -it --name wow-wallet-init \
|
||||||
|
-v $(pwd)/data:/root \
|
||||||
|
lalanza808/wownero \
|
||||||
|
wownero-wallet-cli \
|
||||||
|
--daemon-address https://node.suchwow.xyz:443 \
|
||||||
|
--generate-new-wallet /root/wow \
|
||||||
|
--password zzzzzz \
|
||||||
|
|
||||||
|
# setup rpc process
|
||||||
|
docker run --rm -d --name wow-wallet \
|
||||||
|
-v $(pwd)/data:/root \
|
||||||
|
-p 8888:8888 \
|
||||||
|
lalanza808/wownero \
|
||||||
|
wownero-wallet-rpc \
|
||||||
|
--daemon-address https://node.suchwow.xyz:443 \
|
||||||
|
--wallet-file /root/wow \
|
||||||
|
--password zzzzzz \
|
||||||
|
--rpc-bind-port 8888 \
|
||||||
|
--rpc-bind-ip 0.0.0.0 \
|
||||||
|
--confirm-external-bind \
|
||||||
|
--rpc-login xxxx:yyyy \
|
||||||
|
--log-file /root/rpc.log
|
||||||
|
|
||||||
|
# install python dependencies
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# setup secrets in config file outside of git
|
||||||
|
cp tipbot/config.example.py tipbot/config.py
|
||||||
|
vim !$
|
||||||
|
|
||||||
|
# run it
|
||||||
|
python3 tipbot/tipbot.py
|
||||||
|
```
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
certifi==2020.6.20
|
||||||
|
cffi==1.14.0
|
||||||
|
chardet==3.0.4
|
||||||
|
cryptography==2.9.2
|
||||||
|
decorator==4.4.2
|
||||||
|
idna==2.10
|
||||||
|
peewee==3.13.3
|
||||||
|
pycparser==2.20
|
||||||
|
python-telegram-bot==12.8
|
||||||
|
requests==2.24.0
|
||||||
|
six==1.15.0
|
||||||
|
tornado==6.0.4
|
||||||
|
urllib3==1.25.9
|
@ -0,0 +1,250 @@
|
|||||||
|
import wownero
|
||||||
|
import config
|
||||||
|
import logging
|
||||||
|
import db
|
||||||
|
import six
|
||||||
|
from functools import wraps
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
|
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].effective_chat.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
|
||||||
|
def register(update, context):
|
||||||
|
uid = update.effective_chat.id
|
||||||
|
un = update.effective_chat.username
|
||||||
|
if db.User.filter(telegram_id=uid):
|
||||||
|
update.message.reply_text('You are already registered.')
|
||||||
|
else:
|
||||||
|
wallet = wownero.Wallet()
|
||||||
|
try:
|
||||||
|
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 = [
|
||||||
|
'You have been registered and can now send and receive tips.',
|
||||||
|
'Ask for /help to see all available bot commands.'
|
||||||
|
]
|
||||||
|
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
|
||||||
|
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.effective_chat.username:
|
||||||
|
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.effective_chat.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
|
||||||
|
u = db.User.get(telegram_user=target_un)
|
||||||
|
address = wownero.Wallet().addresses(account=u.account_index)[0]
|
||||||
|
|
||||||
|
# transfer funds to user
|
||||||
|
try:
|
||||||
|
tx = wownero.Wallet().transfer(dest_address=address, amount=amount, priority=2, account=u.account_index)
|
||||||
|
print(tx)
|
||||||
|
update.message.reply_text(f'@{update.effective_chat.username} has tipped @{target_un} {amount} WOW!')
|
||||||
|
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
|
||||||
|
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 = 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.effective_chat.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
|
||||||
|
|
||||||
|
# transfer funds to given address
|
||||||
|
try:
|
||||||
|
tx = wownero.Wallet().transfer(dest_address=address, amount=amount, priority=2, account=u.account_index)
|
||||||
|
print(tx)
|
||||||
|
update.message.reply_text(f'@{update.effective_chat.username} has tipped @{target_un} {amount} WOW!')
|
||||||
|
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
|
||||||
|
def balance(update, context):
|
||||||
|
u = db.User.get(telegram_id=update.effective_chat.id)
|
||||||
|
balances = wownero.Wallet().balances(account=u.account_index)
|
||||||
|
update.message.reply_text(f'Available balance for {update.effective_chat.username}: {balances[1]} ({balances[0]} locked)')
|
||||||
|
|
||||||
|
@wallet_rpc_required
|
||||||
|
@registration_required
|
||||||
|
def deposit(update, context):
|
||||||
|
u = db.User.get(telegram_id=update.effective_chat.id)
|
||||||
|
address = wownero.Wallet().addresses(account=u.account_index)[0]
|
||||||
|
update.message.reply_text(f'Deposit address for {update.effective_chat.username}: {address}')
|
||||||
|
|
||||||
|
@wallet_rpc_required
|
||||||
|
def debug(update, context):
|
||||||
|
if is_tg_admin(update.effective_chat.id):
|
||||||
|
# tx = wownero.Wallet().transfer(
|
||||||
|
# dest_address='WW2vmEGV68ZFeQWwPEJda3UcdWCPfWBnDK1Y6MB9Uojx9adBhCxfx9F51TomRjmD3z7Gyogie3mfVQEkRQjLxqbs1KMzaozDw',
|
||||||
|
# amount=Decimal(2),
|
||||||
|
# priority=2,
|
||||||
|
# account=0
|
||||||
|
# )
|
||||||
|
# update.message.reply_text(str(tx))
|
||||||
|
# balances = wownero.Wallet().balances(account=0)
|
||||||
|
# addresses = wownero.Wallet().addresses(account=0)
|
||||||
|
# accounts = wownero.Wallet().accounts()
|
||||||
|
# a = []
|
||||||
|
# for i in accounts:
|
||||||
|
# a.append(str(wownero.Wallet().balances(account=i)[1]))
|
||||||
|
update.message.reply_text("sup")
|
||||||
|
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 <username> <amount> <message>',
|
||||||
|
'help': 'Tip a user in Wownero'
|
||||||
|
},
|
||||||
|
'send': {
|
||||||
|
'func': send,
|
||||||
|
'example': '/send <address> <amount>',
|
||||||
|
'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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
TG_TOKEN = 'tttttttttttt'
|
||||||
|
TG_ADMIN_ID = 0000000000
|
||||||
|
WALLET_PROTO = 'http'
|
||||||
|
WALLET_HOST = 'localhost'
|
||||||
|
WALLET_PORT = 8888
|
||||||
|
WALLET_USER = 'yyyy'
|
||||||
|
WALLET_PASS = 'xxxxxxxxx'
|
||||||
|
SQLITE_DB_PATH = '/tmp/db.sqlite'
|
@ -0,0 +1,17 @@
|
|||||||
|
from peewee import *
|
||||||
|
import config
|
||||||
|
|
||||||
|
|
||||||
|
db = SqliteDatabase(config.SQLITE_DB_PATH)
|
||||||
|
|
||||||
|
class BaseModel(Model):
|
||||||
|
class Meta:
|
||||||
|
database = db
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
telegram_id = IntegerField()
|
||||||
|
telegram_user = CharField()
|
||||||
|
account_index = IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
db.create_tables([User])
|
@ -0,0 +1,25 @@
|
|||||||
|
import logging
|
||||||
|
import commands
|
||||||
|
import wownero
|
||||||
|
import config
|
||||||
|
from os import environ
|
||||||
|
from telegram.ext import Updater, CommandHandler
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
token = config.TG_TOKEN
|
||||||
|
if token:
|
||||||
|
logging.info('Starting bot.')
|
||||||
|
updater = Updater(token=token, use_context=True)
|
||||||
|
dispatcher = updater.dispatcher
|
||||||
|
for cmd in commands.all_commands:
|
||||||
|
handler = CommandHandler(cmd, commands.all_commands[cmd]['func'])
|
||||||
|
dispatcher.add_handler(handler)
|
||||||
|
updater.start_polling()
|
||||||
|
else:
|
||||||
|
logging.error('No token provided. Quitting!')
|
||||||
|
exit(2)
|
@ -0,0 +1,116 @@
|
|||||||
|
import requests
|
||||||
|
import six
|
||||||
|
import json
|
||||||
|
import operator
|
||||||
|
import config
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
|
PICOWOW = Decimal('0.000000000001')
|
||||||
|
|
||||||
|
class Wallet(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.host = config.WALLET_HOST
|
||||||
|
self.port = config.WALLET_PORT
|
||||||
|
self.proto = config.WALLET_PROTO
|
||||||
|
self.username = config.WALLET_USER
|
||||||
|
self.password = config.WALLET_PASS
|
||||||
|
self.endpoint = '{}://{}:{}/json_rpc'.format(
|
||||||
|
self.proto, self.host, self.port
|
||||||
|
)
|
||||||
|
self.auth = requests.auth.HTTPDigestAuth(
|
||||||
|
self.username, self.password
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = self.height()
|
||||||
|
height = r['height']
|
||||||
|
self.connected = True
|
||||||
|
except:
|
||||||
|
self.connected = False
|
||||||
|
|
||||||
|
def make_wallet_rpc(self, method, params={}):
|
||||||
|
r = requests.get(
|
||||||
|
self.endpoint,
|
||||||
|
data=json.dumps({'method': method, 'params': params}),
|
||||||
|
auth=self.auth
|
||||||
|
)
|
||||||
|
# print(r.status_code)
|
||||||
|
if 'error' in r.json():
|
||||||
|
return r.json()['error']
|
||||||
|
else:
|
||||||
|
return r.json()['result']
|
||||||
|
|
||||||
|
def height(self):
|
||||||
|
return self.make_wallet_rpc('get_height', {})
|
||||||
|
|
||||||
|
def spend_key(self):
|
||||||
|
return self.make_wallet_rpc('query_key', {'key_type': 'spend_key'})['key']
|
||||||
|
|
||||||
|
def view_key(self):
|
||||||
|
return self.make_wallet_rpc('query_key', {'key_type': 'view_key'})['key']
|
||||||
|
|
||||||
|
def seed(self):
|
||||||
|
return self.make_wallet_rpc('query_key', {'key_type': 'mnemonic'})['key']
|
||||||
|
|
||||||
|
def accounts(self):
|
||||||
|
accounts = []
|
||||||
|
_accounts = self.make_wallet_rpc('get_accounts')
|
||||||
|
idx = 0
|
||||||
|
self.master_address = _accounts['subaddress_accounts'][0]['base_address']
|
||||||
|
for _acc in _accounts['subaddress_accounts']:
|
||||||
|
assert idx == _acc['account_index']
|
||||||
|
accounts.append(_acc['account_index'])
|
||||||
|
idx += 1
|
||||||
|
return accounts
|
||||||
|
|
||||||
|
def new_account(self, label=None):
|
||||||
|
_account = self.make_wallet_rpc('create_account', {'label': label})
|
||||||
|
return _account['account_index']
|
||||||
|
|
||||||
|
def addresses(self, account, addr_indices=None):
|
||||||
|
qdata = {'account_index': account}
|
||||||
|
if addr_indices:
|
||||||
|
qdata['address_index'] = addr_indices
|
||||||
|
_addresses = self.make_wallet_rpc('get_address', qdata)
|
||||||
|
addresses = [None] * (max(map(operator.itemgetter('address_index'), _addresses['addresses'])) + 1)
|
||||||
|
for _addr in _addresses['addresses']:
|
||||||
|
addresses[_addr['address_index']] = _addr['address']
|
||||||
|
return addresses
|
||||||
|
|
||||||
|
def new_address(self, account, label=None):
|
||||||
|
data = {'account_index': account, 'label': label}
|
||||||
|
_address = self.make_wallet_rpc('create_address', data)
|
||||||
|
return (_address['address_index'], _address['address'])
|
||||||
|
|
||||||
|
def balances(self, account):
|
||||||
|
data = {'account_index': account}
|
||||||
|
_balance = self.make_wallet_rpc('getbalance', data)
|
||||||
|
return (from_atomic(_balance['balance']), from_atomic(_balance['unlocked_balance']))
|
||||||
|
|
||||||
|
def transfer(self, dest_address, amount, priority, account):
|
||||||
|
data = {
|
||||||
|
'account_index': account,
|
||||||
|
'destinations': [{'address': dest_address, 'amount': to_atomic(amount)}],
|
||||||
|
'priority': priority,
|
||||||
|
'unlock_time': 0,
|
||||||
|
'get_tx_key': True,
|
||||||
|
'get_tx_hex': True,
|
||||||
|
'new_algorithm': True,
|
||||||
|
'do_not_relay': False,
|
||||||
|
}
|
||||||
|
transfer = self.make_wallet_rpc('transfer', data)
|
||||||
|
return transfer
|
||||||
|
|
||||||
|
|
||||||
|
def to_atomic(amount):
|
||||||
|
if not isinstance(amount, (Decimal, float) + six.integer_types):
|
||||||
|
raise ValueError("Amount '{}' doesn't have numeric type. Only Decimal, int, long and "
|
||||||
|
"float (not recommended) are accepted as amounts.")
|
||||||
|
return int(amount * 10**12)
|
||||||
|
|
||||||
|
def from_atomic(amount):
|
||||||
|
return (Decimal(amount) * PICOWOW).quantize(PICOWOW)
|
||||||
|
|
||||||
|
def as_wownero(amount):
|
||||||
|
return Decimal(amount).quantize(PICOWOW)
|
Loading…
Reference in New Issue