mirror of
https://github.com/Reckless-Satoshi/robosats.git
synced 2024-10-26 21:02:41 +03:00
Add telegram functionality to backend. Messages: welcome and order taken.
This commit is contained in:
parent
1feebacbc1
commit
df320ea4d0
@ -40,6 +40,10 @@ ONION_LOCATION = ''
|
|||||||
ALTERNATIVE_SITE = 'RoboSats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion'
|
ALTERNATIVE_SITE = 'RoboSats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion'
|
||||||
ALTERNATIVE_NAME = 'RoboSats Mainnet'
|
ALTERNATIVE_NAME = 'RoboSats Mainnet'
|
||||||
|
|
||||||
|
# Telegram bot token
|
||||||
|
TELEGRAM_TOKEN = ''
|
||||||
|
TELEGRAM_BOT_NAME = ''
|
||||||
|
|
||||||
# Lightning node open info, url to amboss and 1ML
|
# Lightning node open info, url to amboss and 1ML
|
||||||
NETWORK = 'testnet'
|
NETWORK = 'testnet'
|
||||||
NODE_ALIAS = '🤖RoboSats⚡(RoboDevs)'
|
NODE_ALIAS = '🤖RoboSats⚡(RoboDevs)'
|
||||||
|
@ -4,6 +4,7 @@ from api.lightning.node import LNNode
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from api.models import Order, LNPayment, MarketTick, User, Currency
|
from api.models import Order, LNPayment, MarketTick, User, Currency
|
||||||
|
from api.messages import Telegram
|
||||||
from decouple import config
|
from decouple import config
|
||||||
|
|
||||||
from api.tasks import follow_send_payment
|
from api.tasks import follow_send_payment
|
||||||
@ -130,6 +131,7 @@ class Logics:
|
|||||||
order.expires_at = timezone.now() + timedelta(
|
order.expires_at = timezone.now() + timedelta(
|
||||||
seconds=Order.t_to_expire[Order.Status.TAK])
|
seconds=Order.t_to_expire[Order.Status.TAK])
|
||||||
order.save()
|
order.save()
|
||||||
|
Telegram.order_taken(order)
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
def is_buyer(order, user):
|
def is_buyer(order, user):
|
||||||
@ -1051,3 +1053,4 @@ class Logics:
|
|||||||
user.profile.platform_rating = rating
|
user.profile.platform_rating = rating
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
50
api/management/commands/telegram_watcher.py
Normal file
50
api/management/commands/telegram_watcher.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
from api.models import Profile
|
||||||
|
from api.messages import Telegram
|
||||||
|
from decouple import config
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
help = "Polls telegram /getUpdates method"
|
||||||
|
rest = 3 # seconds between consecutive polls
|
||||||
|
|
||||||
|
bot_token = config('TELEGRAM_TOKEN')
|
||||||
|
updates_url = f'https://api.telegram.org/bot{bot_token}/getUpdates'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
"""Infinite loop to check for telegram updates.
|
||||||
|
If it finds a new user (/start), enables it's taker found
|
||||||
|
notification and sends a 'Hey {username} {order_id}' message back"""
|
||||||
|
|
||||||
|
offset = 0
|
||||||
|
while True:
|
||||||
|
time.sleep(self.rest)
|
||||||
|
|
||||||
|
params = {'offset' : offset + 1 , 'timeout' : 5}
|
||||||
|
print(params)
|
||||||
|
response = requests.get(self.updates_url, params=params).json()
|
||||||
|
if len(list(response['result'])) == 0:
|
||||||
|
continue
|
||||||
|
for result in response['result']:
|
||||||
|
text = result['message']['text']
|
||||||
|
splitted_text = text.split(' ')
|
||||||
|
if splitted_text[0] == '/start':
|
||||||
|
token = splitted_text[-1]
|
||||||
|
print(token)
|
||||||
|
try :
|
||||||
|
profile = Profile.objects.get(telegram_token=token)
|
||||||
|
except:
|
||||||
|
print(f'No profile with token {token}')
|
||||||
|
continue
|
||||||
|
profile.telegram_chat_id = result['message']['from']['id']
|
||||||
|
profile.telegram_lang_code = result['message']['from']['language_code']
|
||||||
|
Telegram.welcome(profile.user)
|
||||||
|
profile.telegram_enabled = True
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
offset = response['result'][-1]['update_id']
|
||||||
|
|
||||||
|
|
63
api/messages.py
Normal file
63
api/messages.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
from decouple import config
|
||||||
|
from secrets import token_urlsafe
|
||||||
|
from api.models import Order
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class Telegram():
|
||||||
|
''' Simple telegram messages by requesting to API'''
|
||||||
|
|
||||||
|
def get_context(user):
|
||||||
|
"""returns context needed to enable TG notifications"""
|
||||||
|
context = {}
|
||||||
|
if user.profile.telegram_enabled :
|
||||||
|
context['tg_enabled'] = True
|
||||||
|
else:
|
||||||
|
context['tg_enabled'] = False
|
||||||
|
|
||||||
|
if user.profile.telegram_token == None:
|
||||||
|
user.profile.telegram_token = token_urlsafe(15)
|
||||||
|
user.profile.save()
|
||||||
|
|
||||||
|
context['tg_token'] = user.profile.telegram_token
|
||||||
|
context['tg_bot_name'] = config("TELEGRAM_BOT_NAME")
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def send_message(user, text):
|
||||||
|
""" sends a message to a user with telegram notifications enabled"""
|
||||||
|
|
||||||
|
bot_token=config('TELEGRAM_TOKEN')
|
||||||
|
|
||||||
|
chat_id = user.profile.telegram_chat_id
|
||||||
|
message_url = f'https://api.telegram.org/bot{bot_token}/sendMessage?chat_id={chat_id}&text={text}'
|
||||||
|
|
||||||
|
response = requests.get(message_url).json()
|
||||||
|
print(response)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def welcome(cls, user):
|
||||||
|
lang = user.profile.telegram_lang_code
|
||||||
|
order = Order.objects.get(maker=user)
|
||||||
|
print(str(order.id))
|
||||||
|
if lang == 'es':
|
||||||
|
text = f'Hola {user.username}, te enviaré un mensaje cuando tu orden con ID {str(order.id)} haya sido tomada.'
|
||||||
|
else:
|
||||||
|
text = f"Hey {user.username}, I will send you a message when someone takes your order with ID {str(order.id)}."
|
||||||
|
cls.send_message(user, text)
|
||||||
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def order_taken(cls, order):
|
||||||
|
user = order.maker
|
||||||
|
lang = user.profile.telegram_lang_code
|
||||||
|
taker_nick = order.taker.username
|
||||||
|
site = config('HOST_NAME')
|
||||||
|
if lang == 'es':
|
||||||
|
text = f'Tu orden con ID {order.id} ha sido tomada por {taker_nick}!🎉 Visita http://{site}/order/{order.id} para continuar.'
|
||||||
|
else:
|
||||||
|
text = f'Your order with ID {order.id} was taken by {taker_nick}!🎉 Visit http://{site}/order/{order.id} to proceed with the trade.'
|
||||||
|
|
||||||
|
cls.send_message(user, text)
|
||||||
|
return
|
@ -250,12 +250,11 @@ class Order(models.Model):
|
|||||||
) # unique = True, a taker can only take one order
|
) # unique = True, a taker can only take one order
|
||||||
maker_last_seen = models.DateTimeField(null=True, default=None, blank=True)
|
maker_last_seen = models.DateTimeField(null=True, default=None, blank=True)
|
||||||
taker_last_seen = models.DateTimeField(null=True, default=None, blank=True)
|
taker_last_seen = models.DateTimeField(null=True, default=None, blank=True)
|
||||||
maker_asked_cancel = models.BooleanField(
|
|
||||||
default=False, null=False
|
# When collaborative cancel is needed and one partner has cancelled.
|
||||||
) # When collaborative cancel is needed and one partner has cancelled.
|
maker_asked_cancel = models.BooleanField(default=False, null=False)
|
||||||
taker_asked_cancel = models.BooleanField(
|
taker_asked_cancel = models.BooleanField(default=False, null=False)
|
||||||
default=False, null=False
|
|
||||||
) # When collaborative cancel is needed and one partner has cancelled.
|
|
||||||
is_fiat_sent = models.BooleanField(default=False, null=False)
|
is_fiat_sent = models.BooleanField(default=False, null=False)
|
||||||
|
|
||||||
# in dispute
|
# in dispute
|
||||||
@ -372,7 +371,7 @@ class Profile(models.Model):
|
|||||||
default=None,
|
default=None,
|
||||||
validators=[validate_comma_separated_integer_list],
|
validators=[validate_comma_separated_integer_list],
|
||||||
blank=True,
|
blank=True,
|
||||||
) # Will only store latest ratings
|
) # Will only store latest rating
|
||||||
avg_rating = models.DecimalField(
|
avg_rating = models.DecimalField(
|
||||||
max_digits=4,
|
max_digits=4,
|
||||||
decimal_places=1,
|
decimal_places=1,
|
||||||
@ -382,7 +381,30 @@ class Profile(models.Model):
|
|||||||
MaxValueValidator(100)],
|
MaxValueValidator(100)],
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
# Used to deep link telegram chat in case telegram notifications are enabled
|
||||||
|
telegram_token = models.CharField(
|
||||||
|
max_length=20,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
telegram_chat_id = models.BigIntegerField(
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
telegram_enabled = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
telegram_lang_code = models.CharField(
|
||||||
|
max_length=4,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
telegram_welcomed = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
# Disputes
|
# Disputes
|
||||||
num_disputes = models.PositiveIntegerField(null=False, default=0)
|
num_disputes = models.PositiveIntegerField(null=False, default=0)
|
||||||
lost_disputes = models.PositiveIntegerField(null=False, default=0)
|
lost_disputes = models.PositiveIntegerField(null=False, default=0)
|
||||||
|
28
api/utils.py
28
api/utils.py
@ -3,6 +3,7 @@ from decouple import config
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from api.models import Order
|
from api.models import Order
|
||||||
|
from secrets import token_urlsafe
|
||||||
|
|
||||||
market_cache = {}
|
market_cache = {}
|
||||||
|
|
||||||
@ -86,8 +87,6 @@ def get_commit_robosats():
|
|||||||
|
|
||||||
|
|
||||||
premium_percentile = {}
|
premium_percentile = {}
|
||||||
|
|
||||||
|
|
||||||
@ring.dict(premium_percentile, expire=300)
|
@ring.dict(premium_percentile, expire=300)
|
||||||
def compute_premium_percentile(order):
|
def compute_premium_percentile(order):
|
||||||
|
|
||||||
@ -106,3 +105,28 @@ def compute_premium_percentile(order):
|
|||||||
|
|
||||||
rates = np.array(rates)
|
rates = np.array(rates)
|
||||||
return round(np.sum(rates < order_rate) / len(rates), 2)
|
return round(np.sum(rates < order_rate) / len(rates), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def get_telegram_context(user):
|
||||||
|
"""returns context needed to enable TG notifications"""
|
||||||
|
context = {}
|
||||||
|
if user.profile.telegram_enabled :
|
||||||
|
context['tg_enabled'] = True
|
||||||
|
else:
|
||||||
|
context['tg_enabled'] = False
|
||||||
|
|
||||||
|
if user.profile.telegram_token == None:
|
||||||
|
user.profile.telegram_token = token_urlsafe(15)
|
||||||
|
|
||||||
|
context['tg_token'] = user.profile.telegram_token
|
||||||
|
context['tg_bot_name'] = config("TELEGRAM_BOT_NAME")
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def send_telegram_notification(user, text):
|
||||||
|
bot_token=config('TELEGRAM_TOKEN')
|
||||||
|
chat_id = user.profile.telegram_chat_id
|
||||||
|
message_url = f'https://api.telegram.org/bot{bot_token}/sendMessage?chat_id={chat_id}&text={text}'
|
||||||
|
response = requests.get(message_url).json()
|
||||||
|
print(response)
|
||||||
|
return
|
21
api/views.py
21
api/views.py
@ -9,10 +9,11 @@ from rest_framework.response import Response
|
|||||||
from django.contrib.auth import authenticate, login, logout
|
from django.contrib.auth import authenticate, login, logout
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from .serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer
|
from api.serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer
|
||||||
from .models import LNPayment, MarketTick, Order, Currency
|
from api.models import LNPayment, MarketTick, Order, Currency
|
||||||
from .logics import Logics
|
from api.logics import Logics
|
||||||
from .utils import get_lnd_version, get_commit_robosats, compute_premium_percentile
|
from api.messages import Telegram
|
||||||
|
from api.utils import get_lnd_version, get_commit_robosats, compute_premium_percentile
|
||||||
|
|
||||||
from .nick_generator.nick_generator import NickGenerator
|
from .nick_generator.nick_generator import NickGenerator
|
||||||
from robohash import Robohash
|
from robohash import Robohash
|
||||||
@ -182,15 +183,17 @@ class OrderView(viewsets.ViewSet):
|
|||||||
|
|
||||||
# 3.b If order is between public and WF2
|
# 3.b If order is between public and WF2
|
||||||
if order.status >= Order.Status.PUB and order.status < Order.Status.WF2:
|
if order.status >= Order.Status.PUB and order.status < Order.Status.WF2:
|
||||||
data["price_now"], data[
|
data["price_now"], data["premium_now"] = Logics.price_and_premium_now(order)
|
||||||
"premium_now"] = Logics.price_and_premium_now(order)
|
|
||||||
|
|
||||||
# 3. c) If maker and Public, add num robots in book, premium percentile and num similar orders.
|
# 3. c) If maker and Public, add num robots in book, premium percentile
|
||||||
|
# num similar orders, and maker information to enable telegram notifications.
|
||||||
if data["is_maker"] and order.status == Order.Status.PUB:
|
if data["is_maker"] and order.status == Order.Status.PUB:
|
||||||
data["premium_percentile"] = compute_premium_percentile(order)
|
data["premium_percentile"] = compute_premium_percentile(order)
|
||||||
data["num_similar_orders"] = len(
|
data["num_similar_orders"] = len(
|
||||||
Order.objects.filter(currency=order.currency,
|
Order.objects.filter(currency=order.currency,
|
||||||
status=Order.Status.PUB))
|
status=Order.Status.PUB))
|
||||||
|
# Adds/generate telegram token and whether it is enabled
|
||||||
|
data = {**data,**Telegram.get_context(request.user)}
|
||||||
|
|
||||||
# 4) Non participants can view details (but only if PUB)
|
# 4) Non participants can view details (but only if PUB)
|
||||||
elif not data["is_participant"] and order.status != Order.Status.PUB:
|
elif not data["is_participant"] and order.status != Order.Status.PUB:
|
||||||
@ -518,8 +521,7 @@ class UserView(APIView):
|
|||||||
# Sends the welcome back message, only if created +3 mins ago
|
# Sends the welcome back message, only if created +3 mins ago
|
||||||
if request.user.date_joined < (timezone.now() -
|
if request.user.date_joined < (timezone.now() -
|
||||||
timedelta(minutes=3)):
|
timedelta(minutes=3)):
|
||||||
context[
|
context["found"] = "We found your Robot avatar. Welcome back!"
|
||||||
"found"] = "We found your Robot avatar. Welcome back!"
|
|
||||||
return Response(context, status=status.HTTP_202_ACCEPTED)
|
return Response(context, status=status.HTTP_202_ACCEPTED)
|
||||||
else:
|
else:
|
||||||
# It is unlikely, but maybe the nickname is taken (1 in 20 Billion change)
|
# It is unlikely, but maybe the nickname is taken (1 in 20 Billion change)
|
||||||
@ -612,7 +614,6 @@ class BookView(ListAPIView):
|
|||||||
|
|
||||||
return Response(book_data, status=status.HTTP_200_OK)
|
return Response(book_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class InfoView(ListAPIView):
|
class InfoView(ListAPIView):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
@ -20,7 +20,6 @@ services:
|
|||||||
DEVELOPMENT: 1
|
DEVELOPMENT: 1
|
||||||
volumes:
|
volumes:
|
||||||
- .:/usr/src/robosats
|
- .:/usr/src/robosats
|
||||||
- /mnt/development/database:/usr/src/database
|
|
||||||
- /mnt/development/lnd:/lnd
|
- /mnt/development/lnd:/lnd
|
||||||
network_mode: service:tor
|
network_mode: service:tor
|
||||||
|
|
||||||
@ -38,7 +37,6 @@ services:
|
|||||||
command: python3 manage.py clean_orders
|
command: python3 manage.py clean_orders
|
||||||
volumes:
|
volumes:
|
||||||
- .:/usr/src/robosats
|
- .:/usr/src/robosats
|
||||||
- /mnt/development/database:/usr/src/database
|
|
||||||
network_mode: service:tor
|
network_mode: service:tor
|
||||||
|
|
||||||
follow-invoices:
|
follow-invoices:
|
||||||
@ -51,7 +49,16 @@ services:
|
|||||||
command: python3 manage.py follow_invoices
|
command: python3 manage.py follow_invoices
|
||||||
volumes:
|
volumes:
|
||||||
- .:/usr/src/robosats
|
- .:/usr/src/robosats
|
||||||
- /mnt/development/database:/usr/src/database
|
- /mnt/development/lnd:/lnd
|
||||||
|
network_mode: service:tor
|
||||||
|
|
||||||
|
telegram-watcher:
|
||||||
|
build: .
|
||||||
|
container_name: tg-dev
|
||||||
|
restart: always
|
||||||
|
command: python3 manage.py telegram_watcher
|
||||||
|
volumes:
|
||||||
|
- .:/usr/src/robosats
|
||||||
- /mnt/development/lnd:/lnd
|
- /mnt/development/lnd:/lnd
|
||||||
network_mode: service:tor
|
network_mode: service:tor
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user