Add BtcOffersBot
This commit is contained in:
268
flanabot/bots/btc_offers_bot.py
Normal file
268
flanabot/bots/btc_offers_bot.py
Normal file
@@ -0,0 +1,268 @@
|
||||
from __future__ import annotations # todo0 remove when it's by default
|
||||
|
||||
__all__ = ['BtcOffersBot']
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
from abc import ABC
|
||||
from collections.abc import Callable
|
||||
|
||||
import aiohttp
|
||||
import flanautils
|
||||
from multibot import MultiBot, constants as multibot_constants
|
||||
from websockets.asyncio import client
|
||||
|
||||
import constants
|
||||
from flanabot.models import Chat, Message
|
||||
|
||||
|
||||
# ---------------------------------------------------- #
|
||||
# -------------------- DECORATORS -------------------- #
|
||||
# ---------------------------------------------------- #
|
||||
def preprocess_btc_offers(func: Callable) -> Callable:
|
||||
@functools.wraps(func)
|
||||
async def wrapper(self: BtcOffersBot, message: Message):
|
||||
if message.chat.is_group and not self.is_bot_mentioned(message):
|
||||
return
|
||||
|
||||
eur_mode = (
|
||||
'€' in message.text
|
||||
or
|
||||
bool(
|
||||
flanautils.cartesian_product_string_matching(
|
||||
message.text,
|
||||
constants.KEYWORDS['eur'],
|
||||
multibot_constants.PARSER_MIN_SCORE_DEFAULT
|
||||
)
|
||||
)
|
||||
)
|
||||
usd_mode = (
|
||||
'$' in message.text
|
||||
or
|
||||
bool(
|
||||
flanautils.cartesian_product_string_matching(
|
||||
message.text,
|
||||
constants.KEYWORDS['usd'],
|
||||
multibot_constants.PARSER_MIN_SCORE_DEFAULT
|
||||
)
|
||||
)
|
||||
)
|
||||
premium_mode = (
|
||||
'%' in message.text
|
||||
or
|
||||
bool(
|
||||
flanautils.cartesian_product_string_matching(
|
||||
message.text,
|
||||
constants.KEYWORDS['premium'],
|
||||
multibot_constants.PARSER_MIN_SCORE_DEFAULT
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if len([arg for arg in (eur_mode, usd_mode, premium_mode) if arg]) > 1:
|
||||
await self.send_error(
|
||||
'Indica únicamente uno de los siguientes: precio en euros, precio en dólares o prima.',
|
||||
message
|
||||
)
|
||||
return
|
||||
|
||||
parsed_number = flanautils.text_to_number(message.text)
|
||||
|
||||
if parsed_number and not any((eur_mode, usd_mode, premium_mode)):
|
||||
eur_mode = True
|
||||
|
||||
if eur_mode:
|
||||
query = {'max_price_eur': parsed_number}
|
||||
elif usd_mode:
|
||||
query = {'max_price_usd': parsed_number}
|
||||
elif premium_mode:
|
||||
query = {'max_premium': parsed_number}
|
||||
else:
|
||||
query = {}
|
||||
|
||||
return await func(self, message, query)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------- #
|
||||
# ------------------------------------------ BTC_OFFERS_BOT ------------------------------------------ #
|
||||
# ---------------------------------------------------------------------------------------------------- #
|
||||
class BtcOffersBot(MultiBot, ABC):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._websocket: client.ClientConnection | None = None
|
||||
self._notification_task: asyncio.Task[None] | None = None
|
||||
self._api_endpoint = f"{os.environ['BTC_OFFERS_API_HOST']}:{os.environ['BTC_OFFERS_API_PORT']}/offers"
|
||||
|
||||
# -------------------------------------------------------- #
|
||||
# ------------------- PROTECTED METHODS ------------------ #
|
||||
# -------------------------------------------------------- #
|
||||
def _add_handlers(self):
|
||||
super()._add_handlers()
|
||||
|
||||
self.register(self._on_btc_offers, keywords=constants.KEYWORDS['offer'])
|
||||
self.register(self._on_btc_offers, keywords=constants.KEYWORDS['money'])
|
||||
self.register(self._on_btc_offers, keywords=(constants.KEYWORDS['offer'], constants.KEYWORDS['money']))
|
||||
|
||||
self.register(self._on_notify_btc_offers, keywords=constants.KEYWORDS['notify'])
|
||||
self.register(self._on_notify_btc_offers, keywords=(constants.KEYWORDS['notify'], constants.KEYWORDS['offer']))
|
||||
self.register(self._on_notify_btc_offers, keywords=(constants.KEYWORDS['notify'], constants.KEYWORDS['money']))
|
||||
self.register(self._on_notify_btc_offers, keywords=(constants.KEYWORDS['notify'], constants.KEYWORDS['offer'], constants.KEYWORDS['money']))
|
||||
|
||||
self.register(self._on_stop_btc_offers_notification, keywords=(multibot_constants.KEYWORDS['deactivate'], constants.KEYWORDS['offer']))
|
||||
self.register(self._on_stop_btc_offers_notification, keywords=(multibot_constants.KEYWORDS['deactivate'], constants.KEYWORDS['money']))
|
||||
self.register(self._on_stop_btc_offers_notification, keywords=(multibot_constants.KEYWORDS['deactivate'], constants.KEYWORDS['notify']))
|
||||
self.register(self._on_stop_btc_offers_notification, keywords=(multibot_constants.KEYWORDS['stop'], constants.KEYWORDS['offer']))
|
||||
self.register(self._on_stop_btc_offers_notification, keywords=(multibot_constants.KEYWORDS['stop'], constants.KEYWORDS['money']))
|
||||
self.register(self._on_stop_btc_offers_notification, keywords=(multibot_constants.KEYWORDS['stop'], constants.KEYWORDS['notify']))
|
||||
|
||||
async def _send_offers(self, offers: list[dict], chat: Chat, notifications_disabled=False):
|
||||
offers_parts = []
|
||||
for i, offer in enumerate(offers, start=1):
|
||||
offer_parts = [
|
||||
f'<b>{i}.</b>',
|
||||
f"<b>Plataforma:</b> <code>{offer['exchange']}</code>",
|
||||
f"<b>Id:</b> <code>{offer['id']}</code>"
|
||||
]
|
||||
|
||||
if offer['author']:
|
||||
offer_parts.append(f"<b>Autor:</b> <code>{offer['author']}</code>")
|
||||
|
||||
payment_methods_text = ''.join(f'\n <code>{payment_method}</code>' for payment_method in offer['payment_methods'])
|
||||
|
||||
offer_parts.extend(
|
||||
(
|
||||
f"<b>Cantidad:</b> <code>{offer['amount']}</code>",
|
||||
f"<b>Precio (EUR):</b> <code>{offer['price_eur']:.2f} €</code>",
|
||||
f"<b>Precio (USD):</b> <code>{offer['price_usd']:.2f} $</code>",
|
||||
f"<b>Prima:</b> <code>{offer['premium']:.2f} %</code>",
|
||||
f'<b>Métodos de pago:</b>{payment_methods_text}'
|
||||
)
|
||||
)
|
||||
|
||||
if offer['description']:
|
||||
offer_parts.append(f"<b>Descripción:</b>\n<code><code><code>{offer['description']}</code></code></code>")
|
||||
|
||||
offers_parts.append('\n'.join(offer_parts))
|
||||
|
||||
offers_parts_chunks = flanautils.chunks(offers_parts, 5)
|
||||
|
||||
messages_parts = [
|
||||
[
|
||||
'<b>💰💰💰 OFERTAS BTC 💰💰💰</b>',
|
||||
'',
|
||||
'\n\n'.join(offers_parts_chunks[0])
|
||||
]
|
||||
]
|
||||
|
||||
for offers_parts_chunk in offers_parts_chunks[1:]:
|
||||
messages_parts.append(
|
||||
[
|
||||
'',
|
||||
'\n\n'.join(offers_parts_chunk)
|
||||
]
|
||||
)
|
||||
|
||||
if notifications_disabled:
|
||||
messages_parts[-1].extend(
|
||||
(
|
||||
'',
|
||||
'-' * 70,
|
||||
'<b>ℹ️ Los avisos de ofertas de BTC se han desactivado. Si quieres volver a recibirlos, no dudes en pedírmelo.</b>'
|
||||
)
|
||||
)
|
||||
|
||||
for message_parts in messages_parts:
|
||||
await self.send('\n'.join(message_parts), chat)
|
||||
|
||||
async def _wait_btc_offers_notification(self):
|
||||
while True:
|
||||
data = json.loads(await self._websocket.recv())
|
||||
chat = await self.get_chat(data['chat_id'])
|
||||
chat.btc_offers_max_eur = None
|
||||
chat.save(pull_exclude_fields=('btc_offers_max_eur',))
|
||||
await self._send_offers(data['offers'], chat, notifications_disabled=True)
|
||||
|
||||
# ---------------------------------------------- #
|
||||
# HANDLERS #
|
||||
# ---------------------------------------------- #
|
||||
|
||||
@preprocess_btc_offers
|
||||
async def _on_btc_offers(self, message: Message, query: dict[str, float]):
|
||||
bot_state_message = await self.send('Obteniendo ofertas BTC...', message)
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(f'http://{self._api_endpoint}', params=query) as response:
|
||||
offers = await response.json()
|
||||
|
||||
if offers:
|
||||
await self._send_offers(offers, message.chat)
|
||||
await self.delete_message(bot_state_message)
|
||||
else:
|
||||
await self.edit('No hay ofertas BTC actualmente que cumplan esa condición.', bot_state_message)
|
||||
|
||||
@preprocess_btc_offers
|
||||
async def _on_notify_btc_offers(self, message: Message, query: dict[str, float]):
|
||||
if message.chat.is_group and not self.is_bot_mentioned(message):
|
||||
return
|
||||
|
||||
if max_price_eur := query.get('max_price_eur'):
|
||||
response_text = f'✅ ¡Perfecto! Te avisaré cuando existan ofertas por {max_price_eur:.2f} € o menos.'
|
||||
else:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(constants.YADIO_API_ENDPOINT) as response:
|
||||
yadio_data = await response.json()
|
||||
|
||||
if max_price_usd := query.get('max_price_usd'):
|
||||
max_price_eur = max_price_usd / yadio_data['EUR']['USD']
|
||||
response_text = f'✅ ¡Perfecto! Te avisaré cuando existan ofertas por {max_price_usd:.2f} $ o menos.'
|
||||
else:
|
||||
max_price_eur = yadio_data['BTC'] + query['max_premium'] / 100 * yadio_data['BTC']
|
||||
response_text = f"✅ ¡Perfecto! Te avisaré cuando existan ofertas con una prima del {query['max_premium']:.2f} % o menor."
|
||||
|
||||
await self.send(response_text, message)
|
||||
await self.start_btc_offers_notification(message.chat, max_price_eur)
|
||||
|
||||
async def _on_ready(self):
|
||||
await super()._on_ready()
|
||||
|
||||
for chat in self.Chat.find({
|
||||
'platform': self.platform.value,
|
||||
'btc_offers_max_eur': {'$exists': True, '$ne': None}
|
||||
}):
|
||||
chat = await self.get_chat(chat.id)
|
||||
chat.pull_from_database(overwrite_fields=('_id', 'btc_offers_max_eur'))
|
||||
await self.start_btc_offers_notification(chat, chat.btc_offers_max_eur)
|
||||
|
||||
async def _on_stop_btc_offers_notification(self, message: Message):
|
||||
previous_btc_offers_max_eur = message.chat.btc_offers_max_eur
|
||||
|
||||
await self.stop_btc_offers_notification(message.chat)
|
||||
|
||||
if previous_btc_offers_max_eur:
|
||||
await self.send('🛑 Los avisos de ofertas de BTC se han desactivado.', message)
|
||||
else:
|
||||
await self.send('🤔 No existía ningún aviso de ofertas de BTC configurado.', message)
|
||||
|
||||
# -------------------------------------------------------- #
|
||||
# -------------------- PUBLIC METHODS -------------------- #
|
||||
# -------------------------------------------------------- #
|
||||
async def start_btc_offers_notification(self, chat: Chat, max_price_eur: float):
|
||||
if not self._websocket:
|
||||
self._websocket = await client.connect(f'ws://{self._api_endpoint}')
|
||||
self._notification_task = asyncio.create_task(self._wait_btc_offers_notification())
|
||||
|
||||
chat.btc_offers_max_eur = max_price_eur
|
||||
chat.save()
|
||||
await self._websocket.send(json.dumps({'action': 'start', 'chat_id': chat.id, 'max_price_eur': max_price_eur}))
|
||||
|
||||
async def stop_btc_offers_notification(self, chat: Chat):
|
||||
if not self._websocket:
|
||||
return
|
||||
|
||||
await self._websocket.send(json.dumps({'action': 'stop', 'chat_id': chat.id}))
|
||||
chat.btc_offers_max_eur = None
|
||||
chat.save(pull_exclude_fields=('btc_offers_max_eur',))
|
||||
@@ -15,6 +15,7 @@ from flanautils import return_if_first_empty
|
||||
from multibot import BadRoleError, MessagesFormat, MultiBot, Platform, RegisteredCallback, Role, User, bot_mentioned, constants as multibot_constants, group, ignore_self_message, inline, owner
|
||||
|
||||
from flanabot import constants
|
||||
from flanabot.bots.btc_offers_bot import BtcOffersBot
|
||||
from flanabot.bots.connect_4_bot import Connect4Bot
|
||||
from flanabot.bots.penalty_bot import PenaltyBot
|
||||
from flanabot.bots.poll_bot import PollBot
|
||||
@@ -28,7 +29,7 @@ from flanabot.models import Action, BotAction, ButtonsGroup, Chat, Message
|
||||
# ----------------------------------------------------------------------------------------------------- #
|
||||
# --------------------------------------------- FLANA_BOT --------------------------------------------- #
|
||||
# ----------------------------------------------------------------------------------------------------- #
|
||||
class FlanaBot(Connect4Bot, PenaltyBot, PollBot, ScraperBot, SteamBot, UberEatsBot, WeatherBot, MultiBot, ABC):
|
||||
class FlanaBot(Connect4Bot, BtcOffersBot, PenaltyBot, PollBot, ScraperBot, SteamBot, UberEatsBot, WeatherBot, MultiBot, ABC):
|
||||
Chat = Chat
|
||||
Message = Message
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ STEAM_REGION_CODE_MAPPING = {'eu': 'EUR', 'ru': 'RUB', 'pk': 'USD', 'ua': 'UAH',
|
||||
'kz': 'KZT', 'co': 'COP', 'mx': 'MXN', 'qa': 'QAR', 'sg': 'SGD', 'jp': 'JPY', 'uy': 'UYU',
|
||||
'ae': 'AED', 'kr': 'KRW', 'hk': 'HKD', 'cr': 'CRC', 'nz': 'NZD', 'ca': 'CAD', 'au': 'AUD',
|
||||
'il': 'ILS', 'us': 'USD', 'no': 'NOK', 'uk': 'GBP', 'pl': 'PLN', 'ch': 'CHF'}
|
||||
YADIO_API_ENDPOINT = 'https://api.yadio.io/exrates/EUR'
|
||||
|
||||
BANNED_POLL_PHRASES = (
|
||||
'Deja de dar por culo {presser_name} que no puedes votar aqui',
|
||||
@@ -114,16 +115,17 @@ INSULTS = ('._.', 'aha', 'Aléjate de mi.', 'Ante la duda mi dedo corazón te sa
|
||||
KEYWORDS = {
|
||||
'choose': ('choose', 'elige', 'escoge'),
|
||||
'connect_4': (('conecta', 'connect', 'ralla', 'raya'), ('4', 'cuatro', 'four')),
|
||||
'covid_chart': ('case', 'caso', 'contagiado', 'contagio', 'corona', 'coronavirus', 'covid', 'covid19', 'death',
|
||||
'disease', 'enfermedad', 'enfermos', 'fallecido', 'incidencia', 'jacovid', 'mascarilla', 'muerte',
|
||||
'muerto', 'pandemia', 'sick', 'virus'),
|
||||
'currency_chart': ('argentina', 'bitcoin', 'cardano', 'cripto', 'crypto', 'criptodivisa', 'cryptodivisa',
|
||||
'cryptomoneda', 'cryptocurrency', 'currency', 'dinero', 'divisa', 'ethereum', 'inversion',
|
||||
'moneda', 'pasta'),
|
||||
'dice': ('dado', 'dice'),
|
||||
'eur': ('eur', 'euro', 'euros', '€'),
|
||||
'force': ('force', 'forzar', 'fuerza'),
|
||||
'money': ('bitcoin', 'btc', 'cripto', 'criptomoneda', 'crypto', 'cryptocurrency', 'currency', 'currency', 'dinero',
|
||||
'divisa', 'moneda', 'money', 'precio', 'price', 'satoshi'),
|
||||
'multiple_answer': ('multi', 'multi-answer', 'multiple', 'multirespuesta'),
|
||||
'notify': ('alert', 'alertame', 'alertar', 'avisame', 'avisar', 'aviso', 'inform', 'informame', 'informar',
|
||||
'notificacion', 'notificame', 'notificar', 'notification'),
|
||||
'offer': ('oferta', 'offer', 'orden', 'order', 'post', 'publicacion'),
|
||||
'poll': ('encuesta', 'quiz', 'votacion', 'votar', 'voting'),
|
||||
'premium': ('%', 'premium', 'prima'),
|
||||
'punish': ('acaba', 'aprende', 'ataca', 'atalo', 'azota', 'beating', 'boss', 'castiga', 'castigo', 'condena',
|
||||
'controla', 'destroy', 'destroza', 'duro', 'ejecuta', 'enseña', 'escarmiento', 'execute', 'fuck',
|
||||
'fusila', 'hell', 'humos', 'infierno', 'jefe', 'jode', 'learn', 'leccion', 'lesson', 'manda', 'paliza',
|
||||
@@ -138,6 +140,7 @@ KEYWORDS = {
|
||||
'tunnel': ('canal', 'channel', 'tunel', 'tunnel'),
|
||||
'unpunish': ('absolve', 'forgive', 'innocent', 'inocente', 'perdona', 'spare'),
|
||||
'until': ('hasta', 'until'),
|
||||
'usd': ('$', 'dolar', 'dolares', 'dollar', 'dollars', 'usd'),
|
||||
'vote': ('vote', 'voto'),
|
||||
'weather': ('atmosfera', 'atmosferico', 'calle', 'calor', 'caloret', 'clima', 'climatologia', 'cloud', 'cloudless',
|
||||
'cloudy', 'cold', 'congelar', 'congelado', 'denbora', 'despejado', 'diluvio', 'frio', 'frost', 'hielo',
|
||||
|
||||
@@ -16,6 +16,7 @@ class Chat(MultiBotChat):
|
||||
'scraping_delete_original': True,
|
||||
'ubereats': False
|
||||
})
|
||||
btc_offers_max_eur: float | None = None
|
||||
ubereats: dict = field(default_factory=lambda: {
|
||||
'cookies': [],
|
||||
'last_codes': [],
|
||||
|
||||
Reference in New Issue
Block a user