Add Telegram notifications for order takers (#244)

* Create EnableTelegramDialog functional component

* Move Enable TG button to Profile dialog

* Add TG bot_name, token and state to API /info/

* Add messages for order takers to Telegram class
This commit is contained in:
Reckless_Satoshi 2022-09-15 15:42:33 +00:00 committed by GitHub
parent cd8b607891
commit 1ba94b2abc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 215 additions and 238 deletions

View File

@ -221,7 +221,6 @@ class Logics:
order.expires_at = timezone.now() + timedelta(
seconds=order.t_to_expire(Order.Status.TAK))
order.save()
# send_message.delay(order.id,'order_taken') # Too spammy
return True, None
def is_buyer(order, user):
@ -315,7 +314,6 @@ class Logics:
elif order.status == Order.Status.TAK:
cls.cancel_bond(order.taker_bond)
cls.kick_taker(order)
# send_message.delay(order.id,'taker_expired_b4bond') # Too spammy
return True
elif order.status == Order.Status.WF2:
@ -882,7 +880,6 @@ class Logics:
# adds a timeout penalty
cls.cancel_bond(order.taker_bond)
cls.kick_taker(order)
# send_message.delay(order.id,'taker_canceled_b4bond') # too spammy
return True, None
# 4) When taker or maker cancel after bond (before escrow)

View File

@ -8,6 +8,7 @@ class Telegram():
''' Simple telegram messages by requesting to API'''
session = get_session()
site = config('HOST_NAME')
def get_context(user):
"""returns context needed to enable TG notifications"""
@ -41,188 +42,131 @@ class Telegram():
return
except:
pass
def welcome(self, user):
''' User enabled Telegram Notifications'''
lang = user.profile.telegram_lang_code
# In weird cases the order cannot be found (e.g. it is cancelled)
queryset = Order.objects.filter(maker=user)
order = queryset.last()
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.'
text = f'Hola {user.username}, te enviaré notificaciones sobre tus órdenes en RoboSats.'
else:
text = f"Hey {user.username}, I will send you a message when someone takes your order with ID {str(order.id)}."
text = f"Hey {user.username}, I will send you notifications about your RoboSats orders."
self.send_message(user, text)
user.profile.telegram_welcomed = True
user.profile.save()
return
def order_taken(self, order):
user = order.maker
if not user.profile.telegram_enabled:
return
# def welcome(self, user):
# lang = user.profile.telegram_lang_code
# # In weird cases the order cannot be found (e.g. it is cancelled)
# queryset = Order.objects.filter(maker=user)
# order = queryset.last()
# 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)
# user.profile.telegram_welcomed = True
# user.profile.save()
# return
lang = user.profile.telegram_lang_code
taker_nick = order.taker.username
site = config('HOST_NAME')
if lang == 'es':
text = f'Hey {order.maker.username} ¡Tu orden con ID {order.id} ha sido tomada por {taker_nick}!🥳 Visita http://{site}/order/{order.id} para continuar.'
else:
text = f'Hey {order.maker.username}, your order was taken by {taker_nick}!🥳 Visit http://{site}/order/{order.id} to proceed with the trade.'
self.send_message(user, text)
return
def order_taken_confirmed(self, order):
user = order.maker
if not user.profile.telegram_enabled:
return
if order.maker.profile.telegram_enabled:
lang = order.maker.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {order.maker.username} ¡Tu orden con ID {order.id} ha sido tomada por {order.taker.username}!🥳 Visita http://{self.site}/order/{order.id} para continuar.'
else:
text = f'Hey {order.maker.username}, your order was taken by {order.taker.username}!🥳 Visit http://{self.site}/order/{order.id} to proceed with the trade.'
self.send_message(order.maker, text)
if order.taker.profile.telegram_enabled:
lang = order.taker.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {order.taker.username}, acabas de tomar la orden con ID {order.id}.'
else:
text = f'Hey {order.taker.username}, you just took the order with ID {order.id}.'
self.send_message(order.taker, text)
lang = user.profile.telegram_lang_code
taker_nick = order.taker.username
site = config('HOST_NAME')
if lang == 'es':
text = f'Hey {order.maker.username} ¡Tu orden con ID {order.id} ha sido tomada por {taker_nick}!🥳 El tomador ya ha bloqueado su fianza. Visita http://{site}/order/{order.id} para continuar.'
else:
text = f'Hey {order.maker.username}, your order with ID {order.id} was taken by {taker_nick}!🥳 The taker bond has already been locked. Visit http://{site}/order/{order.id} to proceed with the trade.'
self.send_message(user, text)
return
def fiat_exchange_starts(self, order):
user = order.maker
if not user.profile.telegram_enabled:
return
lang = user.profile.telegram_lang_code
site = config('HOST_NAME')
if lang == 'es':
text = f'Hey {order.maker.username}, el depósito de garantía y el recibo del comprador han sido recibidos. Es hora de enviar el dinero fiat. Visita http://{site}/order/{order.id} para hablar con tu contraparte.'
else:
text = f'Hey {order.maker.username}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat. Visit http://{site}/order/{order.id} to talk with your counterpart.'
self.send_message(user, text)
for user in [order.maker, order.taker]:
if user.profile.telegram_enabled:
lang = user.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {user.username}, el depósito de garantía y el recibo del comprador han sido recibidos. Es hora de enviar el dinero fiat. Visita http://{self.site}/order/{order.id} para hablar con tu contraparte.'
else:
text = f'Hey {user.username}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat. Visit http://{self.site}/order/{order.id} to talk with your counterpart.'
self.send_message(user, text)
return
def order_expired_untaken(self, order):
user = order.maker
if not user.profile.telegram_enabled:
return
lang = user.profile.telegram_lang_code
site = config('HOST_NAME')
if lang == 'es':
text = f'Hey {order.maker.username}, tu orden con ID {order.id} ha expirado sin ser tomada por ningún robot. Visita http://{site}/order/{order.id} para renovarla.'
else:
text = f'Hey {order.maker.username}, your order with ID {order.id} has expired without a taker. Visit http://{site}/order/{order.id} to renew it.'
self.send_message(user, text)
if order.maker.profile.telegram_enabled:
lang = order.maker.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {order.maker.username}, tu orden con ID {order.id} ha expirado sin ser tomada por ningún robot. Visita http://{self.site}/order/{order.id} para renovarla.'
else:
text = f'Hey {order.maker.username}, your order with ID {order.id} has expired without a taker. Visit http://{self.site}/order/{order.id} to renew it.'
self.send_message(order.maker, text)
return
def trade_successful(self, order):
user = order.maker
if not user.profile.telegram_enabled:
return
lang = user.profile.telegram_lang_code
if lang == 'es':
text = f'¡Tu orden con ID {order.id} ha finalizado exitosamente!⚡ Únete a nosotros en @robosats_es y ayúdanos a mejorar.'
else:
text = f'Your order with ID {order.id} has finished successfully!⚡ Join us @robosats and help us improve.'
self.send_message(user, text)
for user in [order.maker, order.taker]:
if user.profile.telegram_enabled:
lang = user.profile.telegram_lang_code
if lang == 'es':
text = f'¡Tu orden con ID {order.id} ha finalizado exitosamente!⚡ Únete a nosotros en @robosats_es y ayúdanos a mejorar.'
else:
text = f'Your order with ID {order.id} has finished successfully!⚡ Join us @robosats and help us improve.'
self.send_message(user, text)
return
def public_order_cancelled(self, order):
user = order.maker
if not user.profile.telegram_enabled:
return
lang = user.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {order.maker.username}, has cancelado tu orden pública con ID {order.id}.'
else:
text = f'Hey {order.maker.username}, you have cancelled your public order with ID {order.id}.'
self.send_message(user, text)
return
def taker_canceled_b4bond(self, order):
user = order.maker
if not user.profile.telegram_enabled:
return
lang = user.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {order.maker.username}, el tomador ha cancelado antes de bloquear su fianza.'
else:
text = f'Hey {order.maker.username}, the taker has canceled before locking the bond.'
self.send_message(user, text)
return
def taker_expired_b4bond(self, order):
user = order.maker
if not user.profile.telegram_enabled:
return
lang = user.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {order.maker.username}, el tomador no ha bloqueado la fianza a tiempo.'
else:
text = f'Hey {order.maker.username}, the taker has not locked the bond in time.'
self.send_message(user, text)
if order.maker.profile.telegram_enabled:
lang = order.maker.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {order.maker.username}, has cancelado tu orden pública con ID {order.id}.'
else:
text = f'Hey {order.maker.username}, you have cancelled your public order with ID {order.id}.'
self.send_message(order.maker, text)
return
def collaborative_cancelled(self, order):
user = order.maker
if not user.profile.telegram_enabled:
return
lang = user.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {order.maker.username}, tu orden con ID {str(order.id)} fue cancelada colaborativamente.'
else:
text = f'Hey {order.maker.username}, your order with ID {str(order.id)} has been collaboratively cancelled.'
self.send_message(user, text)
for user in [order.maker, order.taker]:
if user.profile.telegram_enabled:
lang = user.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {user.username}, tu orden con ID {str(order.id)} fue cancelada colaborativamente.'
else:
text = f'Hey {user.username}, your order with ID {str(order.id)} has been collaboratively cancelled.'
self.send_message(user, text)
return
def dispute_opened(self, order):
user = order.maker
if not user.profile.telegram_enabled:
return
lang = user.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {order.maker.username}, la orden con ID {str(order.id)} ha entrado en disputa.'
else:
text = f'Hey {order.maker.username}, a dispute has been opened on your order with ID {str(order.id)}.'
self.send_message(user, text)
for user in [order.maker, order.taker]:
if user.profile.telegram_enabled:
lang = user.profile.telegram_lang_code
if lang == 'es':
text = f'Hey {user.username}, la orden con ID {str(order.id)} ha entrado en disputa.'
else:
text = f'Hey {user.username}, a dispute has been opened on your order with ID {str(order.id)}.'
self.send_message(user, text)
return
def order_published(self, order):
time.sleep(1) # Just so this message always arrives after the previous two
user = order.maker
lang = user.profile.telegram_lang_code
# In weird cases the order cannot be found (e.g. it is cancelled)
queryset = Order.objects.filter(maker=user)
order = queryset.last()
print(str(order.id))
if lang == 'es':
text = f'Hey {order.maker.username}, tu orden con ID {str(order.id)} es pública en el libro de ordenes.'
else:
text = f"Hey {order.maker.username}, your order with ID {str(order.id)} is public in the order book."
self.send_message(user, text)
user.profile.telegram_welcomed = True
user.profile.save()
if order.maker.profile.telegram_enabled:
lang = order.maker.profile.telegram_lang_code
# In weird cases the order cannot be found (e.g. it is cancelled)
queryset = Order.objects.filter(maker=order.maker)
if len(queryset) == 0:
return
order = queryset.last()
if lang == 'es':
text = f'Hey {order.maker.username}, tu orden con ID {str(order.id)} es pública en el libro de ordenes.'
else:
text = f"Hey {order.maker.username}, your order with ID {str(order.id)} is public in the order book."
self.send_message(order.maker, text)
return

View File

@ -272,9 +272,6 @@ def send_message(order_id, message):
if message == 'welcome':
telegram.welcome(order)
if message == 'order_taken':
telegram.order_taken(order)
elif message == 'order_expired_untaken':
telegram.order_expired_untaken(order)
@ -288,9 +285,6 @@ def send_message(order_id, message):
elif message == 'taker_expired_b4bond':
telegram.taker_expired_b4bond(order)
elif message == 'taker_canceled_b4bond':
telegram.taker_canceled_b4bond(order)
elif message == 'order_published':
telegram.order_published(order)

View File

@ -246,14 +246,11 @@ class OrderView(viewsets.ViewSet):
data["price_now"], data["premium_now"] = Logics.price_and_premium_now(order)
# 3. c) If maker and Public/Paused, add premium percentile
# num similar orders, and maker information to enable telegram notifications.
if data["is_maker"] and order.status in [Order.Status.PUB, Order.Status.PAU]:
data["premium_percentile"] = compute_premium_percentile(order)
data["num_similar_orders"] = len(
Order.objects.filter(currency=order.currency,
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)
elif not data["is_participant"] and order.status != Order.Status.PUB:
@ -921,6 +918,8 @@ class InfoView(ListAPIView):
context["nickname"] = request.user.username
context["referral_code"] = str(request.user.profile.referral_code)
context["earned_rewards"] = request.user.profile.earned_rewards
# Adds/generate telegram token and whether it is enabled
context = {**context,**Telegram.get_context(request.user)}
has_no_active_order, _, order = Logics.validate_already_maker_or_taker(
request.user)
if not has_no_active_order:

View File

@ -111,6 +111,8 @@ services:
environment:
REDIS_URL: redis://localhost:6379
command: celery -A robosats beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
volumes:
- .:/usr/src/robosats
depends_on:
- redis
network_mode: service:tor

View File

@ -82,6 +82,9 @@ class BottomBar extends Component {
activeOrderId: data.active_order_id ? data.active_order_id : null,
lastOrderId: data.last_order_id ? data.last_order_id : null,
referralCode: data.referral_code,
tgEnabled: data.tg_enabled,
tgBotName: data.tg_bot_name,
tgToken: data.tg_token,
earnedRewards: data.earned_rewards,
lastDayPremium: data.last_day_nonkyc_btc_premium,
}),
@ -658,6 +661,9 @@ class BottomBar extends Component {
activeOrderId={this.props.activeOrderId}
lastOrderId={this.props.lastOrderId}
referralCode={this.props.referralCode}
tgEnabled={this.props.tgEnabled}
tgBotName={this.props.tgBotName}
tgToken={this.props.tgToken}
handleSubmitInvoiceClicked={this.handleSubmitInvoiceClicked}
host={this.getHost()}
showRewardsSpinner={this.state.showRewardsSpinner}

View File

@ -0,0 +1,66 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useTheme } from '@mui/material/styles';
import QRCode from 'react-qr-code';
import {
Dialog,
DialogTitle,
DialogActions,
DialogContent,
DialogContentText,
Button,
} from '@mui/material';
interface Props {
open: boolean;
onClose: () => void;
tgBotName: string;
tgToken: string;
onClickEnable: () => void;
}
const EnableTelegramDialog = ({
open,
onClose,
tgBotName,
tgToken,
onClickEnable,
}: Props): JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
return (
<Dialog
open={open}
onClose={onClose}
aria-labelledby='enable-telegram-dialog-title'
aria-describedby='enable-telegram-dialog-description'
>
<DialogTitle id='open-dispute-dialog-title'>{t('Enable TG Notifications')}</DialogTitle>
<DialogContent>
<div style={{ textAlign: 'center' }}>
<QRCode
bgColor={'rgba(255, 255, 255, 0)'}
fgColor={theme.palette.text.primary}
value={'tg://resolve?domain=' + tgBotName + '&start=' + tgToken}
size={275}
/>
</div>
<DialogContentText id='alert-dialog-description'>
{t(
'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 anonymity.',
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose}> {t('Go back')} </Button>
<Button onClick={onClickEnable} autoFocus>
{' '}
{t('Enable')}{' '}
</Button>
</DialogActions>
</Dialog>
);
};
export default EnableTelegramDialog;

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useTheme } from '@mui/material/styles';
import { Link as LinkRouter } from 'react-router-dom';
import {
@ -25,7 +26,9 @@ import {
Typography,
} from '@mui/material';
import { EnableTelegramDialog } from '.';
import BoltIcon from '@mui/icons-material/Bolt';
import SendIcon from '@mui/icons-material/Send';
import NumbersIcon from '@mui/icons-material/Numbers';
import PasswordIcon from '@mui/icons-material/Password';
import ContentCopy from '@mui/icons-material/ContentCopy';
@ -44,6 +47,9 @@ interface Props {
activeOrderId: string | number;
lastOrderId: string | number;
referralCode: string;
tgEnabled: boolean;
tgBotName: string;
tgToken: string;
handleSubmitInvoiceClicked: (e: any, invoice: string) => void;
host: string;
showRewardsSpinner: boolean;
@ -62,6 +68,9 @@ const ProfileDialog = ({
activeOrderId,
lastOrderId,
referralCode,
tgEnabled,
tgBotName,
tgToken,
handleSubmitInvoiceClicked,
host,
showRewardsSpinner,
@ -73,11 +82,13 @@ const ProfileDialog = ({
handleSetStealthInvoice,
}: Props): JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
const [rewardInvoice, setRewardInvoice] = useState<string>('');
const [showRewards, setShowRewards] = useState<boolean>(false);
const [openClaimRewards, setOpenClaimRewards] = useState<boolean>(false);
const [weblnEnabled, setWeblnEnabled] = useState<boolean>(false);
const [openEnableTelegram, setOpenEnableTelegram] = useState<boolean>(false);
useEffect(() => {
getWebln().then((webln) => {
@ -110,6 +121,11 @@ const ProfileDialog = ({
}
};
const handleClickEnableTelegram = () => {
window.open('https://t.me/' + tgBotName + '?start=' + tgToken, '_blank').focus();
setOpenEnableTelegram(false);
};
return (
<Dialog
open={isOpen}
@ -238,6 +254,32 @@ const ProfileDialog = ({
<Divider />
<EnableTelegramDialog
open={openEnableTelegram}
onClose={() => setOpenEnableTelegram(false)}
tgBotName={tgBotName}
tgToken={tgToken}
onClickEnable={handleClickEnableTelegram}
/>
<ListItem>
<ListItemIcon>
<SendIcon />
</ListItemIcon>
<ListItemText>
{tgEnabled ? (
<Typography color={theme.palette.success.main}>
<b>{t('Telegram enabled')}</b>
</Typography>
) : (
<Button color='primary' onClick={() => setOpenEnableTelegram(true)}>
{t('Enable Telegram Notifications')}
</Button>
)}
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<UserNinjaIcon />
@ -245,7 +287,7 @@ const ProfileDialog = ({
<ListItemText>
<Tooltip
placement='top'
placement='bottom'
enterTouchDelay={0}
title={t(
"Stealth lightning invoices do not contain details about the trade except an order reference. Enable this setting if you don't want to disclose details to a custodial lightning wallet.",

View File

@ -7,3 +7,4 @@ export { default as StoreTokenDialog } from './StoreToken';
export { default as ExchangeSummaryDialog } from './ExchangeSummary';
export { default as ProfileDialog } from './Profile';
export { default as StatsDialog } from './Stats';
export { default as EnableTelegramDialog } from './EnableTelegram';

View File

@ -37,7 +37,6 @@ import { copyToClipboard } from '../utils/clipboard';
// Icons
import PercentIcon from '@mui/icons-material/Percent';
import BookIcon from '@mui/icons-material/Book';
import SendIcon from '@mui/icons-material/Send';
import LockIcon from '@mui/icons-material/Lock';
import LockOpenIcon from '@mui/icons-material/LockOpen';
import BalanceIcon from '@mui/icons-material/Balance';
@ -63,7 +62,6 @@ class TradeBox extends Component {
this.state = {
openConfirmFiatReceived: false,
openConfirmDispute: false,
openEnableTelegram: false,
receiveTab: 0,
address: '',
miningFee: 1.05,
@ -479,65 +477,6 @@ 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 = () => {
const { t } = this.props;
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'>{t('Enable TG Notifications')}</DialogTitle>
<DialogContent>
<div style={{ textAlign: 'center' }}>
<QRCode
bgColor={'rgba(255, 255, 255, 0)'}
fgColor={this.props.theme.palette.text.primary}
value={
'tg://resolve?domain=' +
this.props.data.tg_bot_name +
'&start=' +
this.props.data.tg_token
}
size={275}
/>
</div>
<DialogContentText id='alert-dialog-description'>
{t(
'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 anonymity.',
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClickCloseEnableTelegramDialog}> {t('Go back')} </Button>
<Button onClick={this.handleClickEnableTelegram} autoFocus>
{' '}
{t('Enable')}{' '}
</Button>
</DialogActions>
</Dialog>
);
};
depositHoursMinutes = () => {
const hours = parseInt(this.props.data.escrow_duration / 3600);
const minutes = parseInt((this.props.data.escrow_duration - hours * 3600) / 60);
@ -565,7 +504,6 @@ class TradeBox extends Component {
<Grid container spacing={1}>
{/* Make confirmation sound for HTLC received. */}
{this.Sound('locked-invoice')}
{this.EnableTelegramDialog()}
<Grid item xs={12} align='center'>
<Typography variant='subtitle1'>
<b> {t('Your order is public')} </b> {' ' + this.stepXofY()}
@ -591,18 +529,6 @@ class TradeBox extends Component {
</Typography>
</ListItem>
<Grid item xs={12} align='center'>
{this.props.data.tg_enabled ? (
<Typography color='primary' component='h6' variant='h6' align='center'>
{t('Telegram enabled')}
</Typography>
) : (
<Button color='primary' onClick={this.handleClickOpenTelegramDialog}>
<SendIcon />
{t('Enable Telegram Notifications')}
</Button>
)}
</Grid>
<Divider />
<Grid container>
@ -1409,7 +1335,7 @@ class TradeBox extends Component {
enterTouchDelay={0}
title={
<Trans i18nKey='open_dispute'>
To open a dispute you need to wait{' '}
To open a dispute you need to wait
<Countdown date={expires_at} renderer={this.disputeCountdownRenderer} />
</Trans>
}