migration vers pycoin
This commit is contained in:
parent
a63408de18
commit
7c33bbb408
@ -16,6 +16,7 @@ python3.pkgs.buildPythonApplication rec {
|
||||
propagatedBuildInputs = with python3.pkgs; [
|
||||
python-telegram-bot
|
||||
requests
|
||||
block-io
|
||||
pycoin
|
||||
sqlalchemy
|
||||
];
|
||||
}
|
||||
|
@ -1,37 +1,102 @@
|
||||
from telegram.ext import Updater, CommandHandler
|
||||
from telegram import ParseMode
|
||||
from block_io import BlockIo, BlockIoAPIError
|
||||
from telegram.ext import Updater, CommandHandler, CallbackContext
|
||||
from telegram import ParseMode, Update
|
||||
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 os
|
||||
import urllib.request
|
||||
import io
|
||||
import requests
|
||||
import json
|
||||
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
|
||||
|
||||
parser = argparse.ArgumentParser(description='Dogetipbot telegram')
|
||||
parser.add_argument('--block-io-api-key')
|
||||
parser.add_argument('--block-io-pin')
|
||||
parser.add_argument('--telegram-api-key')
|
||||
parser.add_argument('--network')
|
||||
parser = argparse.ArgumentParser(description="Dogetipbot telegram")
|
||||
parser.add_argument("--telegram-api-key")
|
||||
parser.add_argument("--private-key")
|
||||
parser.add_argument("--db-path")
|
||||
|
||||
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
|
||||
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.basicConfig(level=logging.ERROR,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - \
|
||||
%(message)s')
|
||||
logging.basicConfig(
|
||||
level=logging.ERROR,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - \
|
||||
%(message)s",
|
||||
)
|
||||
|
||||
# Exceptions
|
||||
|
||||
@ -52,186 +117,209 @@ class NotValidUnit(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# BlockIO
|
||||
|
||||
version = 2
|
||||
block_io = BlockIo(BLOCK_IO_API_KEY, BLOCK_IO_PIN, version)
|
||||
|
||||
# Core functions
|
||||
|
||||
|
||||
def get_balance(account):
|
||||
try:
|
||||
response = block_io.get_address_by(label=account)
|
||||
except BlockIoAPIError:
|
||||
def get_user(account):
|
||||
query = SESSION.query(User).filter_by(name=account)
|
||||
if query.count() > 0:
|
||||
return query.first()
|
||||
else:
|
||||
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:
|
||||
return (float(response['data']['available_balance']),
|
||||
float(response['data']['pending_received_balance']))
|
||||
raise NotEnoughDoge
|
||||
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
|
||||
|
||||
|
||||
def start(bot, update):
|
||||
bot.send_message(chat_id=update.message.chat_id,
|
||||
text="Bark ! Je suis un tipbot Dogecoin ! \n\n \
|
||||
Pour commencer envoyez moi /register")
|
||||
bot.send_message(
|
||||
chat_id=update.message.chat_id,
|
||||
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:
|
||||
montant = int(args[0])
|
||||
unit = args[1]
|
||||
destinataire = args[2][1:]
|
||||
montant = int(context.args[0])
|
||||
unit = context.args[1]
|
||||
destinataire = context.args[2][1:]
|
||||
except (IndexError, ValueError):
|
||||
bot.send_message(chat_id=update.message.chat_id,
|
||||
text="Syntaxe : /dogetip xxx doge @destinataire")
|
||||
context.bot.send_message(
|
||||
chat_id=update.message.chat_id,
|
||||
text="Syntaxe : /dogetip xxx doge @destinataire",
|
||||
)
|
||||
else:
|
||||
try:
|
||||
if unit == "doge":
|
||||
response = transaction(update.message.from_user.username,
|
||||
destinataire, montant)
|
||||
txid = transaction(
|
||||
update.message.from_user.username,
|
||||
get_user(destinataire).address,
|
||||
montant * 100000000,
|
||||
)
|
||||
else:
|
||||
raise NotValidUnit(unit)
|
||||
except NotEnoughDoge:
|
||||
message = "Pas assez de doge @" + update.message.from_user.username
|
||||
except NoAccountError as e:
|
||||
message = "Vous n'avez pas de compte @" + str(e) + '\n\n' \
|
||||
+ "Utilisez /register pour démarrer"
|
||||
message = (
|
||||
"Vous n'avez pas de compte @"
|
||||
+ str(e)
|
||||
+ "\n\n"
|
||||
+ "Utilisez /register pour démarrer"
|
||||
)
|
||||
except NotValidUnit as e:
|
||||
message = str(e) + " n'est pas une unité valide"
|
||||
else:
|
||||
txid = response['data']['txid']
|
||||
message = '🚀 Transaction effectuée 🚀\n\n' \
|
||||
+ str(montant) + ' ' + NETWORK + '\n' \
|
||||
+ '@' + update.message.from_user.username + ' → @' \
|
||||
+ destinataire + '\n\n' \
|
||||
+ '<a href="https://chain.so/tx/' + NETWORK + '/' \
|
||||
+ txid + '">Voir la transaction</a>'
|
||||
message = (
|
||||
"🚀 Transaction effectuée 🚀\n\n"
|
||||
+ f"{str(montant)} DOGE\n"
|
||||
+ f"@{update.message.from_user.username} → @{destinataire}\n\n"
|
||||
+ f'<a href="https://blockchair.com/dogecoin/transaction/{txid}">Voir la transaction</a>'
|
||||
)
|
||||
|
||||
bot.send_message(chat_id=update.message.chat_id,
|
||||
parse_mode=ParseMode.HTML, text=message)
|
||||
context.bot.send_message(
|
||||
chat_id=update.message.chat_id, parse_mode=ParseMode.HTML, text=message
|
||||
)
|
||||
|
||||
|
||||
def register(bot, update):
|
||||
try:
|
||||
address = create_address(update.message.from_user.username)
|
||||
except AccountExisting:
|
||||
bot.send_message(chat_id=update.message.chat_id,
|
||||
text="Vous avez déjà un compte")
|
||||
def register(update: Update, context: CallbackContext):
|
||||
query = SESSION.query(User).filter_by(name=update.message.from_user.username)
|
||||
if query.count() > 0:
|
||||
update.message.reply_text("Vous avez déjà un compte")
|
||||
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:
|
||||
address = get_address(update.message.from_user.username)
|
||||
balance, unconfirmed_balance = \
|
||||
get_balance(update.message.from_user.username)
|
||||
value = get_value(balance)
|
||||
address = get_user(update.message.from_user.username).address
|
||||
balance, unconfirmed_balance = get_user(
|
||||
update.message.from_user.username
|
||||
).balance
|
||||
except NoAccountError as e:
|
||||
bot.send_message(chat_id=update.message.chat_id,
|
||||
text="Vous n'avez pas de compte @" + str(e) + '\n\n'
|
||||
+ "Utilisez /register pour démarrer")
|
||||
update.message.reply_text(
|
||||
"Vous n'avez pas de compte @"
|
||||
+ str(e)
|
||||
+ "\n\n"
|
||||
+ "Utilisez /register pour démarrer"
|
||||
)
|
||||
else:
|
||||
bot.send_message(chat_id=update.message.chat_id,
|
||||
text=address + "\n\n" +
|
||||
str(balance) + " " + NETWORK +
|
||||
" (" + str(value) + " €)" + "\n" +
|
||||
str(unconfirmed_balance) + " " +
|
||||
NETWORK + " unconfirmed")
|
||||
update.message.reply_text(
|
||||
address
|
||||
+ "\n\n"
|
||||
+ str(balance / 100000000)
|
||||
+ " DOGE\n"
|
||||
+ str(unconfirmed_balance / 100000000)
|
||||
+ " DOGE unconfirmed"
|
||||
)
|
||||
|
||||
|
||||
def withdraw(bot, update, args):
|
||||
montant = int(args[0])
|
||||
unit = args[1]
|
||||
address = args[2]
|
||||
def withdraw(update: Update, context: CallbackContext):
|
||||
try:
|
||||
unit = context.args[1]
|
||||
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":
|
||||
response = address_transaction(update.message.from_user.username,
|
||||
address, montant)
|
||||
context.bot.send_message(
|
||||
chat_id=update.message.chat_id,
|
||||
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,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
text="Transaction effectuée !\n" +
|
||||
"[tx](https://chain.so/tx/" + NETWORK + "/" + txid + ")")
|
||||
def users(update: Update, context: CallbackContext):
|
||||
query = SESSION.query(User).all()
|
||||
reply = ""
|
||||
for user in query:
|
||||
reply += (
|
||||
f"\n{repr(user)}\thttps://blockchair.com/dogecoin/address/{user.address}"
|
||||
)
|
||||
update.message.reply_text(reply)
|
||||
|
||||
|
||||
# Telegram initialisation
|
||||
|
||||
updater = Updater(token=TELEGRAM_API_KEY)
|
||||
updater = Updater(token=TELEGRAM_API_KEY, use_context=True)
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
start_handler = CommandHandler('start', start)
|
||||
start_handler = CommandHandler("start", start)
|
||||
dispatcher.add_handler(start_handler)
|
||||
|
||||
dogetip_handler = CommandHandler('dogetip', dogetip, pass_args=True)
|
||||
dogetip_handler = CommandHandler("dogetip", dogetip)
|
||||
dispatcher.add_handler(dogetip_handler)
|
||||
|
||||
register_handler = CommandHandler('register', register)
|
||||
register_handler = CommandHandler("register", register)
|
||||
dispatcher.add_handler(register_handler)
|
||||
|
||||
infos_handler = CommandHandler('infos', infos)
|
||||
infos_handler = CommandHandler("infos", infos)
|
||||
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)
|
||||
|
||||
|
||||
def main():
|
||||
updater.start_polling()
|
||||
updater.idle()
|
||||
|
Loading…
Reference in New Issue
Block a user