migration vers pycoin

This commit is contained in:
nyanloutre 2020-04-27 15:32:05 +02:00
parent a63408de18
commit 7c33bbb408
Signed by: nyanloutre
GPG Key ID: 194A92D1103ACB2A
3 changed files with 236 additions and 146 deletions

View File

@ -16,6 +16,7 @@ python3.pkgs.buildPythonApplication rec {
propagatedBuildInputs = with python3.pkgs; [ propagatedBuildInputs = with python3.pkgs; [
python-telegram-bot python-telegram-bot
requests requests
block-io pycoin
sqlalchemy
]; ];
} }

View File

@ -1,37 +1,102 @@
from telegram.ext import Updater, CommandHandler from telegram.ext import Updater, CommandHandler, CallbackContext
from telegram import ParseMode from telegram import ParseMode, Update
from block_io import BlockIo, BlockIoAPIError from pycoin.symbols.doge import network
from pycoin.encoding.hexbytes import b2h_rev, h2b, h2b_rev
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
import logging import logging
import os import os
import urllib.request import io
import requests
import json import json
import argparse import argparse
# BLOCK_IO_API_KEY = os.environ['BLOCK_IO_API_KEY']
# BLOCK_IO_PIN = os.environ['BLOCK_IO_PIN']
# TELEGRAM_API_KEY = os.environ['TELEGRAM_API_KEY']
# NETWORK = os.environ['NETWORK']
# Parsing arguments # Parsing arguments
parser = argparse.ArgumentParser(description='Dogetipbot telegram') parser = argparse.ArgumentParser(description="Dogetipbot telegram")
parser.add_argument('--block-io-api-key') parser.add_argument("--telegram-api-key")
parser.add_argument('--block-io-pin') parser.add_argument("--private-key")
parser.add_argument('--telegram-api-key') parser.add_argument("--db-path")
parser.add_argument('--network')
args = parser.parse_args() args = parser.parse_args()
BLOCK_IO_API_KEY = args.block_io_api_key
BLOCK_IO_PIN = args.block_io_pin
TELEGRAM_API_KEY = args.telegram_api_key TELEGRAM_API_KEY = args.telegram_api_key
NETWORK = args.network
# Dogecoin Wallet
DERIVATION_PATH = "44/3"
TX_FEE_PER_THOUSAND_BYTES = 100000000
with open(args.private_key, "rb") as f:
seed = f.readline().strip()
DOGE_WALLET = network.keys.bip32_seed(seed)
# SQL
engine = create_engine(f"sqlite:///{args.db_path}")
sessionM = sessionmaker(bind=engine)
SESSION = sessionM()
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
@hybrid_property
def private_key(self):
return DOGE_WALLET.subkey_for_path(f"{DERIVATION_PATH}/{self.id}")
@hybrid_property
def address(self):
return self.private_key.address()
@hybrid_property
def balance(self):
address_infos = requests.get(
f"https://api.blockcypher.com/v1/doge/main/addrs/{self.address}"
).json()
return (address_infos["final_balance"], address_infos["unconfirmed_balance"])
@hybrid_property
def unspent(self):
unspent_outputs = requests.get(
f"https://api.blockcypher.com/v1/doge/main/addrs/{self.address}?unspentOnly=true&includeScript=true"
).json()
if unspent_outputs["final_balance"] > 0:
tx_refs = []
if "txrefs" in unspent_outputs:
tx_refs.extend(unspent_outputs["txrefs"])
if "unconfirmed_txrefs" in unspent_outputs:
tx_refs.extend(unspent_outputs["unconfirmed_txrefs"])
tx_ins = []
for output in tx_refs:
tx_ins.append(
network.Tx.Spendable(
output["value"],
h2b(output["script"]),
h2b_rev(output["tx_hash"]),
output["tx_output_n"],
)
)
return tx_ins, unspent_outputs["final_balance"]
def __repr__(self):
return f"{self.name}: {self.address}"
Base.metadata.create_all(engine)
# Logging # Logging
logging.basicConfig(level=logging.ERROR, logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - \ level=logging.ERROR,
%(message)s') format="%(asctime)s - %(name)s - %(levelname)s - \
%(message)s",
)
# Exceptions # Exceptions
@ -52,186 +117,209 @@ class NotValidUnit(Exception):
pass pass
# BlockIO
version = 2
block_io = BlockIo(BLOCK_IO_API_KEY, BLOCK_IO_PIN, version)
# Core functions # Core functions
def get_balance(account): def get_user(account):
try: query = SESSION.query(User).filter_by(name=account)
response = block_io.get_address_by(label=account) if query.count() > 0:
except BlockIoAPIError: return query.first()
else:
raise NoAccountError(account) raise NoAccountError(account)
# API broken
# def get_value(amount):
# data = requests.get("https://api.coinmarketcap.com/v1/ticker/dogecoin/?convert=EUR").json()
# return round(float(data[0]['price_eur'])*amount, 2)
def transaction(sender, receiver, amount=None):
pkey = get_user(sender).private_key
unspent, balance = get_user(sender).unspent
s = io.BytesIO()
if amount is None:
tx = network.tx_utils.create_signed_tx(unspent, [receiver], wifs=[pkey.wif()])
tx.stream(s)
tx_byte_count = len(s.getvalue())
tx = network.tx_utils.create_signed_tx(
unspent,
[receiver],
wifs=[pkey.wif()],
fee=TX_FEE_PER_THOUSAND_BYTES * ((999 + tx_byte_count) // 1000),
)
elif balance >= amount:
tx = network.tx_utils.create_signed_tx(unspent, [(receiver, amount), pkey.address()], wifs=[pkey.wif()])
tx.stream(s)
tx_byte_count = len(s.getvalue())
tx = network.tx_utils.create_signed_tx(
unspent,
[(receiver, amount), pkey.address()],
wifs=[pkey.wif()],
fee=TX_FEE_PER_THOUSAND_BYTES * ((999 + tx_byte_count) // 1000),
)
else: else:
return (float(response['data']['available_balance']), raise NotEnoughDoge
float(response['data']['pending_received_balance'])) push_obj = {"tx": tx.as_hex()}
# print(push_obj)
pushed_tx = requests.post(
"https://api.blockcypher.com/v1/doge/main/txs/push", json=push_obj
)
pushed_tx.raise_for_status()
return pushed_tx.json()["tx"]["hash"]
# return "null"
def get_value(amount):
if(NETWORK == "DOGE"):
with urllib.request.urlopen("https://api.coinmarketcap.com/v1/ticker" +
"/dogecoin/?convert=EUR") as url:
data = json.loads(url.read().decode())
return round(float(data[0]['price_eur'])*amount, 2)
def create_address(account):
try:
response = block_io.get_new_address(label=account)
except BlockIoAPIError:
raise AccountExisting
else:
return response['data']['address']
def get_address(account):
try:
response = block_io.get_address_by(label=account)
except BlockIoAPIError:
raise NoAccountError(account)
else:
return response['data']['address']
def transaction(sender, receiver, amount):
try:
if get_balance(sender)[0] > amount:
get_address(receiver)
return block_io.withdraw_from_labels(amounts=amount,
from_labels=sender,
to_labels=receiver,
priority="low")
else:
raise NotEnoughDoge
except NoAccountError:
raise
def address_transaction(account, address, amount):
try:
if get_balance(account)[0] > amount:
return block_io.withdraw_from_labels(amounts=amount,
from_labels=account,
to_addresses=address,
priority="low")
else:
return NotEnoughDoge
except NoAccountError:
raise
# Telegram functions # Telegram functions
def start(bot, update): def start(bot, update):
bot.send_message(chat_id=update.message.chat_id, bot.send_message(
text="Bark ! Je suis un tipbot Dogecoin ! \n\n \ chat_id=update.message.chat_id,
Pour commencer envoyez moi /register") text="Bark ! Je suis un tipbot Dogecoin ! \n\n \
Pour commencer envoyez moi /register",
)
def dogetip(bot, update, args): def dogetip(update: Update, context: CallbackContext):
try: try:
montant = int(args[0]) montant = int(context.args[0])
unit = args[1] unit = context.args[1]
destinataire = args[2][1:] destinataire = context.args[2][1:]
except (IndexError, ValueError): except (IndexError, ValueError):
bot.send_message(chat_id=update.message.chat_id, context.bot.send_message(
text="Syntaxe : /dogetip xxx doge @destinataire") chat_id=update.message.chat_id,
text="Syntaxe : /dogetip xxx doge @destinataire",
)
else: else:
try: try:
if unit == "doge": if unit == "doge":
response = transaction(update.message.from_user.username, txid = transaction(
destinataire, montant) update.message.from_user.username,
get_user(destinataire).address,
montant * 100000000,
)
else: else:
raise NotValidUnit(unit) raise NotValidUnit(unit)
except NotEnoughDoge: except NotEnoughDoge:
message = "Pas assez de doge @" + update.message.from_user.username message = "Pas assez de doge @" + update.message.from_user.username
except NoAccountError as e: except NoAccountError as e:
message = "Vous n'avez pas de compte @" + str(e) + '\n\n' \ message = (
+ "Utilisez /register pour démarrer" "Vous n'avez pas de compte @"
+ str(e)
+ "\n\n"
+ "Utilisez /register pour démarrer"
)
except NotValidUnit as e: except NotValidUnit as e:
message = str(e) + " n'est pas une unité valide" message = str(e) + " n'est pas une unité valide"
else: else:
txid = response['data']['txid'] message = (
message = '🚀 Transaction effectuée 🚀\n\n' \ "🚀 Transaction effectuée 🚀\n\n"
+ str(montant) + ' ' + NETWORK + '\n' \ + f"{str(montant)} DOGE\n"
+ '@' + update.message.from_user.username + ' → @' \ + f"@{update.message.from_user.username} → @{destinataire}\n\n"
+ destinataire + '\n\n' \ + f'<a href="https://blockchair.com/dogecoin/transaction/{txid}">Voir la transaction</a>'
+ '<a href="https://chain.so/tx/' + NETWORK + '/' \ )
+ txid + '">Voir la transaction</a>'
bot.send_message(chat_id=update.message.chat_id, context.bot.send_message(
parse_mode=ParseMode.HTML, text=message) chat_id=update.message.chat_id, parse_mode=ParseMode.HTML, text=message
)
def register(bot, update): def register(update: Update, context: CallbackContext):
try: query = SESSION.query(User).filter_by(name=update.message.from_user.username)
address = create_address(update.message.from_user.username) if query.count() > 0:
except AccountExisting: update.message.reply_text("Vous avez déjà un compte")
bot.send_message(chat_id=update.message.chat_id,
text="Vous avez déjà un compte")
else: else:
bot.send_message(chat_id=update.message.chat_id, text=address) user = User(name=update.message.from_user.username)
SESSION.add(user)
SESSION.commit()
update.message.reply_text(get_user(update.message.from_user.username).address)
def infos(bot, update): def infos(update: Update, context: CallbackContext):
try: try:
address = get_address(update.message.from_user.username) address = get_user(update.message.from_user.username).address
balance, unconfirmed_balance = \ balance, unconfirmed_balance = get_user(
get_balance(update.message.from_user.username) update.message.from_user.username
value = get_value(balance) ).balance
except NoAccountError as e: except NoAccountError as e:
bot.send_message(chat_id=update.message.chat_id, update.message.reply_text(
text="Vous n'avez pas de compte @" + str(e) + '\n\n' "Vous n'avez pas de compte @"
+ "Utilisez /register pour démarrer") + str(e)
+ "\n\n"
+ "Utilisez /register pour démarrer"
)
else: else:
bot.send_message(chat_id=update.message.chat_id, update.message.reply_text(
text=address + "\n\n" + address
str(balance) + " " + NETWORK + + "\n\n"
" (" + str(value) + " €)" + "\n" + + str(balance / 100000000)
str(unconfirmed_balance) + " " + + " DOGE\n"
NETWORK + " unconfirmed") + str(unconfirmed_balance / 100000000)
+ " DOGE unconfirmed"
)
def withdraw(bot, update, args): def withdraw(update: Update, context: CallbackContext):
montant = int(args[0]) try:
unit = args[1] unit = context.args[1]
address = args[2] address = context.args[2]
except (IndexError, ValueError):
context.bot.send_message(
chat_id=update.message.chat_id, text="Syntaxe : /withdraw xxx doge adresse"
)
else:
if unit == "doge":
if context.args[0] == "all":
txid = transaction(update.message.from_user.username, address)
else:
montant = int(context.args[0])
txid = transaction(
update.message.from_user.username, address, montant * 100000000
)
if unit == "doge": context.bot.send_message(
response = address_transaction(update.message.from_user.username, chat_id=update.message.chat_id,
address, montant) parse_mode=ParseMode.MARKDOWN,
text="Transaction effectuée !\n"
+ f"[tx](https://blockchair.com/dogecoin/transaction/{txid})",
)
txid = response['data']['txid']
bot.send_message(chat_id=update.message.chat_id, def users(update: Update, context: CallbackContext):
parse_mode=ParseMode.MARKDOWN, query = SESSION.query(User).all()
text="Transaction effectuée !\n" + reply = ""
"[tx](https://chain.so/tx/" + NETWORK + "/" + txid + ")") for user in query:
reply += (
f"\n{repr(user)}\thttps://blockchair.com/dogecoin/address/{user.address}"
)
update.message.reply_text(reply)
# Telegram initialisation # Telegram initialisation
updater = Updater(token=TELEGRAM_API_KEY) updater = Updater(token=TELEGRAM_API_KEY, use_context=True)
dispatcher = updater.dispatcher dispatcher = updater.dispatcher
start_handler = CommandHandler('start', start) start_handler = CommandHandler("start", start)
dispatcher.add_handler(start_handler) dispatcher.add_handler(start_handler)
dogetip_handler = CommandHandler('dogetip', dogetip, pass_args=True) dogetip_handler = CommandHandler("dogetip", dogetip)
dispatcher.add_handler(dogetip_handler) dispatcher.add_handler(dogetip_handler)
register_handler = CommandHandler('register', register) register_handler = CommandHandler("register", register)
dispatcher.add_handler(register_handler) dispatcher.add_handler(register_handler)
infos_handler = CommandHandler('infos', infos) infos_handler = CommandHandler("infos", infos)
dispatcher.add_handler(infos_handler) dispatcher.add_handler(infos_handler)
withdraw_handler = CommandHandler('withdraw', withdraw, pass_args=True) infos_handler = CommandHandler("users", users)
dispatcher.add_handler(infos_handler)
withdraw_handler = CommandHandler("withdraw", withdraw)
dispatcher.add_handler(withdraw_handler) dispatcher.add_handler(withdraw_handler)
def main(): def main():
updater.start_polling() updater.start_polling()
updater.idle() updater.idle()

View File

@ -17,6 +17,7 @@ setup(
install_requires=[ install_requires=[
'python-telegram-bot', 'python-telegram-bot',
'requests', 'requests',
'block-io' 'pycoin',
'sqlalchemy'
] ]
) )