mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 10:31:35 +00:00
Merge pull request #53 from Reckless-Satoshi/telegram-notifications
Telegram notifications
This commit is contained in:
commit
2eaa3cf7df
@ -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,10 +4,9 @@ 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
|
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ FIAT_EXCHANGE_DURATION = int(config("FIAT_EXCHANGE_DURATION"))
|
|||||||
|
|
||||||
|
|
||||||
class Logics:
|
class Logics:
|
||||||
|
telegram = Telegram()
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_already_maker_or_taker(cls, user):
|
def validate_already_maker_or_taker(cls, user):
|
||||||
"""Validates if a use is already not part of an active order"""
|
"""Validates if a use is already not part of an active order"""
|
||||||
@ -130,6 +129,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()
|
||||||
|
cls.telegram.order_taken(order)
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
def is_buyer(order, user):
|
def is_buyer(order, user):
|
||||||
@ -234,7 +234,8 @@ class Logics:
|
|||||||
if maker_is_seller:
|
if maker_is_seller:
|
||||||
cls.settle_bond(order.maker_bond)
|
cls.settle_bond(order.maker_bond)
|
||||||
cls.return_bond(order.taker_bond)
|
cls.return_bond(order.taker_bond)
|
||||||
try: # If seller is offline the escrow LNpayment does not even exist
|
# If seller is offline the escrow LNpayment does not exist
|
||||||
|
try:
|
||||||
cls.cancel_escrow(order)
|
cls.cancel_escrow(order)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@ -245,7 +246,8 @@ class Logics:
|
|||||||
# If maker is buyer, settle the taker's bond order goes back to public
|
# If maker is buyer, settle the taker's bond order goes back to public
|
||||||
else:
|
else:
|
||||||
cls.settle_bond(order.taker_bond)
|
cls.settle_bond(order.taker_bond)
|
||||||
try: # If seller is offline the escrow LNpayment does not even exist
|
# If seller is offline the escrow LNpayment does not even exist
|
||||||
|
try:
|
||||||
cls.cancel_escrow(order)
|
cls.cancel_escrow(order)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@ -569,7 +571,7 @@ class Logics:
|
|||||||
When the second user asks for cancel. Order is totally cancelled.
|
When the second user asks for cancel. Order is totally cancelled.
|
||||||
Must have a small cost for both parties to prevent node DDOS."""
|
Must have a small cost for both parties to prevent node DDOS."""
|
||||||
elif order.status in [
|
elif order.status in [
|
||||||
Order.Status.WFI, Order.Status.CHA, Order.Status.FSE
|
Order.Status.WFI, Order.Status.CHA
|
||||||
]:
|
]:
|
||||||
|
|
||||||
# if the maker had asked, and now the taker does: cancel order, return everything
|
# if the maker had asked, and now the taker does: cancel order, return everything
|
||||||
@ -1049,3 +1051,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
|
||||||
|
|
||||||
|
51
api/management/commands/telegram_watcher.py
Normal file
51
api/management/commands/telegram_watcher.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
from api.models import Profile
|
||||||
|
from api.messages import Telegram
|
||||||
|
from api.utils import get_tor_session
|
||||||
|
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'
|
||||||
|
|
||||||
|
session = get_tor_session()
|
||||||
|
telegram = Telegram()
|
||||||
|
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}
|
||||||
|
response = self.session.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]
|
||||||
|
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']
|
||||||
|
self.telegram.welcome(profile.user)
|
||||||
|
profile.telegram_enabled = True
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
offset = response['result'][-1]['update_id']
|
||||||
|
|
||||||
|
|
67
api/messages.py
Normal file
67
api/messages.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
from decouple import config
|
||||||
|
from secrets import token_urlsafe
|
||||||
|
from api.models import Order
|
||||||
|
from api.utils import get_tor_session
|
||||||
|
|
||||||
|
class Telegram():
|
||||||
|
''' Simple telegram messages by requesting to API'''
|
||||||
|
|
||||||
|
session = get_tor_session()
|
||||||
|
|
||||||
|
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(self, 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 = self.session.get(message_url).json()
|
||||||
|
print(response)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def welcome(self, 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)}."
|
||||||
|
self.send_message(user, text)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def order_taken(self, order):
|
||||||
|
user = order.maker
|
||||||
|
if not user.profile.telegram_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
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.'
|
||||||
|
|
||||||
|
self.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)
|
||||||
|
21
api/utils.py
21
api/utils.py
@ -1,12 +1,18 @@
|
|||||||
import requests, ring, os
|
import requests, ring, os
|
||||||
from decouple import config
|
from decouple import config
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import requests
|
||||||
|
|
||||||
from api.models import Order
|
from api.models import Order
|
||||||
|
|
||||||
|
def get_tor_session():
|
||||||
|
session = requests.session()
|
||||||
|
# Tor uses the 9050 port as the default socks port
|
||||||
|
session.proxies = {'http': 'socks5://127.0.0.1:9050',
|
||||||
|
'https': 'socks5://127.0.0.1:9050'}
|
||||||
|
return session
|
||||||
|
|
||||||
market_cache = {}
|
market_cache = {}
|
||||||
|
|
||||||
|
|
||||||
@ring.dict(market_cache, expire=3) # keeps in cache for 3 seconds
|
@ring.dict(market_cache, expire=3) # keeps in cache for 3 seconds
|
||||||
def get_exchange_rates(currencies):
|
def get_exchange_rates(currencies):
|
||||||
"""
|
"""
|
||||||
@ -15,6 +21,8 @@ def get_exchange_rates(currencies):
|
|||||||
Returns the median price list.
|
Returns the median price list.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
session = get_tor_session()
|
||||||
|
|
||||||
APIS = config("MARKET_PRICE_APIS",
|
APIS = config("MARKET_PRICE_APIS",
|
||||||
cast=lambda v: [s.strip() for s in v.split(",")])
|
cast=lambda v: [s.strip() for s in v.split(",")])
|
||||||
|
|
||||||
@ -22,7 +30,7 @@ def get_exchange_rates(currencies):
|
|||||||
for api_url in APIS:
|
for api_url in APIS:
|
||||||
try: # If one API is unavailable pass
|
try: # If one API is unavailable pass
|
||||||
if "blockchain.info" in api_url:
|
if "blockchain.info" in api_url:
|
||||||
blockchain_prices = requests.get(api_url).json()
|
blockchain_prices = session.get(api_url).json()
|
||||||
blockchain_rates = []
|
blockchain_rates = []
|
||||||
for currency in currencies:
|
for currency in currencies:
|
||||||
try: # If a currency is missing place a None
|
try: # If a currency is missing place a None
|
||||||
@ -33,7 +41,7 @@ def get_exchange_rates(currencies):
|
|||||||
api_rates.append(blockchain_rates)
|
api_rates.append(blockchain_rates)
|
||||||
|
|
||||||
elif "yadio.io" in api_url:
|
elif "yadio.io" in api_url:
|
||||||
yadio_prices = requests.get(api_url).json()
|
yadio_prices = session.get(api_url).json()
|
||||||
yadio_rates = []
|
yadio_rates = []
|
||||||
for currency in currencies:
|
for currency in currencies:
|
||||||
try:
|
try:
|
||||||
@ -74,8 +82,6 @@ def get_lnd_version():
|
|||||||
|
|
||||||
|
|
||||||
robosats_commit_cache = {}
|
robosats_commit_cache = {}
|
||||||
|
|
||||||
|
|
||||||
@ring.dict(robosats_commit_cache, expire=3600)
|
@ring.dict(robosats_commit_cache, expire=3600)
|
||||||
def get_commit_robosats():
|
def get_commit_robosats():
|
||||||
|
|
||||||
@ -84,10 +90,7 @@ def get_commit_robosats():
|
|||||||
|
|
||||||
return commit_hash
|
return commit_hash
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@ -152,7 +152,9 @@ You have to copy or scan the invoice with your lightning wallet in order to lock
|
|||||||
<img src="images/how-to-use/contract-box-8.png" width="370" />
|
<img src="images/how-to-use/contract-box-8.png" width="370" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Your order will be public for 6 hours. You can check the time left to expiration by checking the "Order" tab. It can be canceled at any time without penalty before it is taken by another robot. Keep the contract tab open to be notified [with this sound](https://github.com/Reckless-Satoshi/robosats/raw/main/frontend/static/assets/sounds/taker-found.mp3). It might be best to do this on a desktop computer and turn on the volume, so you do not miss when your order is taken. It might take long! Maybe you even forget! *Note: If you forget your order and a robot takes it and locks his fidelity bond, you risk losing your own fidelity bond by not fulfilling the next contract steps.*
|
Your order will be public for 6 hours. You can check the time left to expiration by checking the "Order" tab. It can be canceled at any time without penalty before it is taken by another robot. Keep the contract tab open to be notified [with this sound](https://github.com/Reckless-Satoshi/robosats/raw/main/frontend/static/assets/sounds/taker-found.mp3). It might be best to do this on a desktop computer and turn on the volume, so you do not miss when your order is taken. It might take long! Maybe you even forget! You can also enable telegram notifications by pressing "Enable Telegram Notification" and then pressing "Start" in the chat. You will receive a welcome message as confirmation of the enabled notifications. Another message will be sent once a taker for your order is found.
|
||||||
|
|
||||||
|
*Note: If you forget your order and a robot takes it and locks his fidelity bond, you risk losing your own fidelity bond by not fulfilling the next contract steps.*
|
||||||
|
|
||||||
In the contract tab you can also see how many other orders are public for the same currency. You can also see how well does your premium ranks among all other orders for the same currency.
|
In the contract tab you can also see how many other orders are public for the same currency. You can also see how well does your premium ranks among all other orders for the same currency.
|
||||||
|
|
||||||
|
@ -154,7 +154,9 @@ Debes copiar o escanear la factura con tu billetera lightning para bloquear tu f
|
|||||||
<img src="images/how-to-use/contract-box-8.png" width="370" />
|
<img src="images/how-to-use/contract-box-8.png" width="370" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Tu orden permanecerá publicada durante 6 horas. Puedes comprobar cuánto tiempo le queda consultando la pestaña "Order". Se puede cancelar en cualquier momento sin penalización antes de que otro robot tome tu orden. Mantén abierta la pestaña del contrato para recibir notificaciones [con este sonido](https://github.com/Reckless-Satoshi/robosats/raw/main/frontend/static/assets/sounds/taker-found.mp3). Es aconsejable hacer esto en un ordenador o portátil con el volumen encendido para enterarte cuando alguien tome tu orden porque puede transcurrir bastante tiempo. ¡Quizás incluso olvides que tienes publicada una orden! *Nota: Si no estás pendiente de tu orden y un robot la toma y bloquea su fianza de fidelidad, corres el riesgo de perder la fianza de fidelidad que depositaste por no cumplir con los siguientes pasos del contrato.*
|
Tu orden permanecerá publicada durante 6 horas. Puedes comprobar cuánto tiempo le queda consultando la pestaña "Order". Se puede cancelar en cualquier momento sin penalización antes de que otro robot tome tu orden. Mantén abierta la pestaña del contrato para recibir notificaciones [con este sonido](https://github.com/Reckless-Satoshi/robosats/raw/main/frontend/static/assets/sounds/taker-found.mp3). Es aconsejable hacer esto en un ordenador o portátil con el volumen encendido para enterarte cuando alguien tome tu orden porque puede transcurrir bastante tiempo. ¡Quizás incluso olvides que tienes publicada una orden! También puedes activar las notificaciones de telegram. Simplemente pulsa en "Enable Telegram Notifications" y presiona "Start" en la conversación con el bot de RoboSats. Te llegará un mensaje de bienvenida y cuando alguien tome la orden te avisará con un mensaje.
|
||||||
|
|
||||||
|
*Nota: Si no estás pendiente de tu orden y un robot la toma y bloquea su fianza, corres el riesgo de perder tu fianza por no cumplir con los siguientes pasos del contrato.*
|
||||||
|
|
||||||
En la pestaña del contrato también puedes ver cuántas órdenes hay publicadas para la misma moneda. También puedes en qué posición (en porcentaje) se sitúa la prima de tu oferta con respecto a las demás publicadas con la misma moneda.
|
En la pestaña del contrato también puedes ver cuántas órdenes hay publicadas para la misma moneda. También puedes en qué posición (en porcentaje) se sitúa la prima de tu oferta con respecto a las demás publicadas con la misma moneda.
|
||||||
|
|
||||||
|
@ -180,21 +180,21 @@ export default class BottomBar extends Component {
|
|||||||
<List>
|
<List>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
|
|
||||||
<ListItemButton component="a" href="https://t.me/robosats">
|
<ListItemButton component="a" target="_blank" href="https://t.me/robosats">
|
||||||
<ListItemIcon><SendIcon/></ListItemIcon>
|
<ListItemIcon><SendIcon/></ListItemIcon>
|
||||||
<ListItemText primary="Join the RoboSats group"
|
<ListItemText primary="Join the RoboSats group"
|
||||||
secondary="Telegram (English / Main)"/>
|
secondary="Telegram (English / Main)"/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
|
|
||||||
<ListItemButton component="a" href="https://t.me/robosats_es">
|
<ListItemButton component="a" target="_blank" href="https://t.me/robosats_es">
|
||||||
<ListItemIcon><SendIcon/></ListItemIcon>
|
<ListItemIcon><SendIcon/></ListItemIcon>
|
||||||
<ListItemText primary="Unase al grupo RoboSats"
|
<ListItemText primary="Unase al grupo RoboSats"
|
||||||
secondary="Telegram (Español)"/>
|
secondary="Telegram (Español)"/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
|
|
||||||
<ListItemButton component="a" href="https://github.com/Reckless-Satoshi/robosats/issues">
|
<ListItemButton component="a" target="_blank" href="https://github.com/Reckless-Satoshi/robosats/issues">
|
||||||
<ListItemIcon><GitHubIcon/></ListItemIcon>
|
<ListItemIcon><GitHubIcon/></ListItemIcon>
|
||||||
<ListItemText primary="Tell us about a new feature or a bug"
|
<ListItemText primary="Tell us about a new feature or a bug"
|
||||||
secondary="Github Issues - The Robotic Satoshis Open Source Project"/>
|
secondary="Github Issues - The Robotic Satoshis Open Source Project"/>
|
||||||
|
@ -10,6 +10,7 @@ import QrReader from 'react-qr-reader'
|
|||||||
import PercentIcon from '@mui/icons-material/Percent';
|
import PercentIcon from '@mui/icons-material/Percent';
|
||||||
import BookIcon from '@mui/icons-material/Book';
|
import BookIcon from '@mui/icons-material/Book';
|
||||||
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner';
|
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner';
|
||||||
|
import SendIcon from '@mui/icons-material/Send';
|
||||||
|
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
let cookieValue = null;
|
let cookieValue = null;
|
||||||
@ -41,6 +42,7 @@ export default class TradeBox extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
openConfirmFiatReceived: false,
|
openConfirmFiatReceived: false,
|
||||||
openConfirmDispute: false,
|
openConfirmDispute: false,
|
||||||
|
openEnableTelegram: false,
|
||||||
badInvoice: false,
|
badInvoice: false,
|
||||||
badStatement: false,
|
badStatement: false,
|
||||||
qrscanner: false,
|
qrscanner: false,
|
||||||
@ -93,7 +95,7 @@ export default class TradeBox extends Component {
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText id="alert-dialog-description">
|
<DialogContentText id="alert-dialog-description">
|
||||||
The RoboSats staff will examine the statements and evidence provided. You need to build
|
The RoboSats staff will examine the statements and evidence provided. You need to build
|
||||||
a complete case, as the staff cannot read the chat. You MUST provide a burner contact
|
a complete case, as the staff cannot read the chat. It is best to provide a burner contact
|
||||||
method with your statement. The satoshis in the trade escrow will be sent to the dispute winner,
|
method with your statement. The satoshis in the trade escrow will be sent to the dispute winner,
|
||||||
while the dispute loser will lose the bond.
|
while the dispute loser will lose the bond.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
@ -246,11 +248,50 @@ export default class TradeBox extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClickOpenTelegramDialog = () => {
|
||||||
|
this.setState({openEnableTelegram: true});
|
||||||
|
};
|
||||||
|
handleClickCloseEnableTelegramDialog = () => {
|
||||||
|
this.setState({openEnableTelegram: false});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClickEnableTelegram = () =>{
|
||||||
|
window.open("https://t.me/"+this.props.data.tg_bot_name+'?start='+this.props.data.tg_token, '_blank').focus()
|
||||||
|
this.handleClickCloseEnableTelegramDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
EnableTelegramDialog =() =>{
|
||||||
|
return(
|
||||||
|
<Dialog
|
||||||
|
open={this.state.openEnableTelegram}
|
||||||
|
onClose={this.handleClickCloseEnableTelegramDialog}
|
||||||
|
aria-labelledby="enable-telegram-dialog-title"
|
||||||
|
aria-describedby="enable-telegram-dialog-description"
|
||||||
|
>
|
||||||
|
<DialogTitle id="open-dispute-dialog-title">
|
||||||
|
Enable TG Notifications
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="alert-dialog-description">
|
||||||
|
You will be taken to a conversation with RoboSats telegram bot.
|
||||||
|
Simply open the chat and press "Start". Note that by enabling
|
||||||
|
telegram notifications you might lower your level of anonimity.
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={this.handleClickCloseEnableTelegramDialog}>Go back</Button>
|
||||||
|
<Button onClick={this.handleClickEnableTelegram} autoFocus> Enable </Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
showMakerWait=()=>{
|
showMakerWait=()=>{
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={1}>
|
<Grid container spacing={1}>
|
||||||
{/* Make confirmation sound for HTLC received. */}
|
{/* Make confirmation sound for HTLC received. */}
|
||||||
<this.Sound soundFileName="locked-invoice"/>
|
<this.Sound soundFileName="locked-invoice"/>
|
||||||
|
<this.EnableTelegramDialog/>
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
<Typography component="subtitle1" variant="subtitle1">
|
<Typography component="subtitle1" variant="subtitle1">
|
||||||
<b> Your order is public. Wait for a taker. </b>
|
<b> Your order is public. Wait for a taker. </b>
|
||||||
@ -264,11 +305,20 @@ export default class TradeBox extends Component {
|
|||||||
<Typography component="body2" variant="body2" align="left">
|
<Typography component="body2" variant="body2" align="left">
|
||||||
<p>Be patient while robots check the book.
|
<p>Be patient while robots check the book.
|
||||||
It might take some time. This box will ring 🔊 once a robot takes your order. </p>
|
It might take some time. This box will ring 🔊 once a robot takes your order. </p>
|
||||||
<p>Please note that if your premium is too high, or if your currency or payment
|
<p>Please note that if your premium is excessive, or your currency or payment
|
||||||
methods are not popular, your order might expire untaken. Your bond will
|
methods are not popular, your order might expire untaken. Your bond will
|
||||||
return to you (no action needed).</p>
|
return to you (no action needed).</p>
|
||||||
</Typography>
|
</Typography>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
<Grid item xs={12} align="center">
|
||||||
|
{this.props.data.tg_enabled ?
|
||||||
|
<Typography color='primary' component="h6" variant="h6" align="center"> Telegram enabled</Typography>
|
||||||
|
:
|
||||||
|
<Button color="primary" onClick={this.handleClickOpenTelegramDialog}>
|
||||||
|
<SendIcon/>Enable Telegram Notifications
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
</Grid>
|
||||||
{/* TODO API sends data for a more confortable wait */}
|
{/* TODO API sends data for a more confortable wait */}
|
||||||
<Divider/>
|
<Divider/>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
@ -446,7 +496,7 @@ export default class TradeBox extends Component {
|
|||||||
<Grid item xs={12} align="left">
|
<Grid item xs={12} align="left">
|
||||||
<Typography component="body2" variant="body2">
|
<Typography component="body2" variant="body2">
|
||||||
Please, submit your statement. Be clear and specific about what happened and provide the necessary
|
Please, submit your statement. Be clear and specific about what happened and provide the necessary
|
||||||
evidence. You MUST provide a burner email, XMPP or telegram username to follow up with the staff.
|
evidence. It is best to provide a burner email, XMPP or telegram username to follow up with the staff.
|
||||||
Disputes are solved at the discretion of real robots <i>(aka humans)</i>, so be as helpful
|
Disputes are solved at the discretion of real robots <i>(aka humans)</i>, so be as helpful
|
||||||
as possible to ensure a fair outcome. Max 5000 chars.
|
as possible to ensure a fair outcome. Max 5000 chars.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -28,13 +28,29 @@ export default class UserGenPage extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
token: this.genBase62Token(36),
|
|
||||||
openInfo: false,
|
openInfo: false,
|
||||||
loadingRobot: true,
|
|
||||||
tokenHasChanged: false,
|
tokenHasChanged: false,
|
||||||
};
|
};
|
||||||
this.props.setAppState({avatarLoaded: false, nickname: null, token: null});
|
|
||||||
this.getGeneratedUser(this.state.token);
|
//this.props.setAppState({avatarLoaded: false, nickname: null, token: null});
|
||||||
|
|
||||||
|
// Checks in parent HomePage if there is already a nick and token
|
||||||
|
// Displays the existing one
|
||||||
|
if (this.props.nickname != null){
|
||||||
|
this.state = {
|
||||||
|
nickname: this.props.nickname,
|
||||||
|
token: this.props.token? this.props.token : null,
|
||||||
|
avatar_url: 'static/assets/avatars/' + this.props.nickname + '.png',
|
||||||
|
loadingRobot: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
var newToken = this.genBase62Token(36)
|
||||||
|
this.state = {
|
||||||
|
token: newToken
|
||||||
|
}
|
||||||
|
this.getGeneratedUser(newToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort of cryptographically strong function to generate Base62 token client-side
|
// sort of cryptographically strong function to generate Base62 token client-side
|
||||||
|
File diff suppressed because one or more lines are too long
@ -23,3 +23,4 @@ scipy==1.8.0
|
|||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
psycopg2==2.9.3
|
psycopg2==2.9.3
|
||||||
SQLAlchemy==1.4.31
|
SQLAlchemy==1.4.31
|
||||||
|
requests[socks]
|
Loading…
Reference in New Issue
Block a user