async def cancel_offer(call: types.CallbackQuery, offer: EscrowOffer): """React to offer cancellation. While first party is transferring, second party can't cancel offer, because we can't be sure that first party hasn't already completed transfer before confirming. """ if offer.trx_id: return await call.answer(i18n("cancel_after_transfer")) if offer.memo: if offer.type == "buy": escrow_user = offer.init elif offer.type == "sell": escrow_user = offer.counter if call.from_user.id != escrow_user["id"]: return await call.answer(i18n("cancel_before_verification")) req = get_escrow_instance(offer[offer.type]).remove_from_queue( offer._id) req["timeout_handler"].cancel() sell_answer = i18n("escrow_cancelled", locale=offer.init["locale"]) buy_answer = i18n("escrow_cancelled", locale=offer.counter["locale"]) offer.cancel_time = time() await offer.delete_document() await call.answer() await tg.send_message(offer.init["id"], sell_answer, reply_markup=start_keyboard()) await tg.send_message(offer.counter["id"], buy_answer, reply_markup=start_keyboard()) sell_state = FSMContext(dp.storage, offer.init["id"], offer.init["id"]) buy_state = FSMContext(dp.storage, offer.counter["id"], offer.counter["id"]) await sell_state.finish() await buy_state.finish()
async def errors_handler(update: types.Update, exception: Exception): """Handle exceptions when calling handlers. Send error notification to special chat and warn user about the error. """ if isinstance(exception, MessageNotModified): return True log.error("Error handling request {}".format(update.update_id), exc_info=True) chat_id = None if update.message: update_type = "message" from_user = update.message.from_user chat_id = update.message.chat.id if update.callback_query: update_type = "callback query" from_user = update.callback_query.from_user chat_id = update.callback_query.message.chat.id if chat_id is not None: try: exceptions_chat_id = config.EXCEPTIONS_CHAT_ID except AttributeError: pass else: await tg.send_message( exceptions_chat_id, "Error handling {} {} from {} ({}) in chat {}\n{}".format( update_type, update.update_id, markdown.link(from_user.mention, from_user.url), from_user.id, chat_id, markdown.code(traceback.format_exc(limit=-3)), ), parse_mode=types.ParseMode.MARKDOWN, ) await tg.send_message( chat_id, i18n("unexpected_error"), reply_markup=start_keyboard(), ) return True
async def claim_transfer_custom_address(message: types.Message, state: FSMContext): """Transfer cashback to custom address.""" data = await state.get_data() await tg.send_message(message.chat.id, i18n("claim_transfer_wait")) try: trx_url = await transfer_cashback(message.from_user.id, data["currency"], message.text) except TransferError: await tg.send_message(message.chat.id, i18n("cashback_transfer_error")) else: await tg.send_message( message.chat.id, markdown.link(i18n("cashback_transferred"), trx_url), parse_mode=ParseMode.MARKDOWN, reply_markup=start_keyboard(), )
async def handle_start_command(message: types.Message, state: FSMContext): """Handle /start. Ask for language if user is new or show menu. """ user = {"id": message.from_user.id, "chat": message.chat.id} result = await database.users.update_one(user, {"$setOnInsert": user}, upsert=True) if not result.matched_count: await tg.send_message( message.chat.id, i18n("choose_language"), reply_markup=locale_keyboard() ) return await state.finish() await tg.send_message( message.chat.id, i18n("help_message"), reply_markup=start_keyboard() )
async def handle_start_command(message: types.Message, state: FSMContext): """Handle /start. Ask for language if user is new or show menu. """ user = { "id": message.from_user.id, "chat": message.chat.id, "mention": message.from_user.mention, "has_username": bool(message.from_user.username), } args = message.text.split() if len(args) > 1: if args[1][0] == "_": search_filter = { "mention": "@" + args[1][1:], "has_username": True } else: search_filter = {"referral_code": args[1]} referrer_user = await database.users.find_one(search_filter) if referrer_user: user["referrer"] = referrer_user["id"] if referrer_user.get("referrer"): user["referrer_of_referrer"] = referrer_user["referrer"] result = await database.users.update_one( { "id": user["id"], "chat": user["chat"] }, {"$setOnInsert": user}, upsert=True) if not result.matched_count: await tg.send_message(message.chat.id, i18n("choose_language"), reply_markup=locale_keyboard()) return await state.finish() await tg.send_message(message.chat.id, i18n("help_message"), reply_markup=start_keyboard())
async def set_order(order: MutableMapping[str, Any], chat_id: int): """Set missing values and finish order creation.""" order["start_time"] = time() if "duration" not in order: order["duration"] = config.ORDER_DURATION_LIMIT order["expiration_time"] = time() + order["duration"] * 24 * 60 * 60 order["notify"] = True if "price_sell" not in order and "sum_buy" in order and "sum_sell" in order: order["price_sell"] = Decimal128( normalize(order["sum_sell"].to_decimal() / order["sum_buy"].to_decimal()) ) order["price_buy"] = Decimal128( normalize(order["sum_buy"].to_decimal() / order["sum_sell"].to_decimal()) ) inserted_order = await database.orders.insert_one(order) order["_id"] = inserted_order.inserted_id await tg.send_message(chat_id, i18n("order_set"), reply_markup=start_keyboard()) await show_order(order, chat_id, order["user_id"], show_id=True) asyncio.create_task(order_notification(order))
async def send_message_to_support(message: types.Message): """Format message and send it to support. Envelope emoji at the beginning is the mark of support ticket. """ if message.from_user.username: username = "******" + message.from_user.username else: username = markdown.link(message.from_user.full_name, message.from_user.url) await tg.send_message( config.SUPPORT_CHAT_ID, emojize(":envelope:") + f" #chat\\_{message.chat.id} {message.message_id}\n{username}:\n" + markdown.escape_md(message.text), parse_mode=types.ParseMode.MARKDOWN, ) await tg.send_message( message.chat.id, i18n("support_response_promise"), reply_markup=start_keyboard(), )
async def validate_offer(call: types.CallbackQuery, offer: EscrowOffer): """Ask support for manual verification of exchange.""" if offer.type == "buy": sender = offer.counter receiver = offer.init currency = offer.sell elif offer.type == "sell": sender = offer.init receiver = offer.counter currency = offer.buy escrow_instance = get_escrow_instance(offer.escrow) answer = "{0}\n{1} sender: {2}{3}\n{1} receiver: {4}{5}\nBank: {6}\nMemo: {7}" answer = answer.format( markdown.link("Unconfirmed escrow.", escrow_instance.trx_url(offer.trx_id)), currency, markdown.link(sender["mention"], User(id=sender["id"]).url), " ({})".format(sender["name"]) if "name" in sender else "", markdown.link(receiver["mention"], User(id=receiver["id"]).url), " ({})".format(receiver["name"]) if "name" in receiver else "", offer.bank, markdown.code(offer.memo), ) await tg.send_message(config.SUPPORT_CHAT_ID, answer, parse_mode=ParseMode.MARKDOWN) await offer.delete_document() await call.answer() await tg.send_message( call.message.chat.id, i18n("request_validation_promise"), reply_markup=start_keyboard(), )
async def subcribe_to_pair( message: types.Message, state: FSMContext, command: Command.CommandObj, ): r"""Manage subscription to pairs. Currency pair is indicated with two space separated arguments after **/subscribe** or **/unsubscribe** in message text. First argument is the currency order's creator wants to sell and second is the currency order's creator wants to buy. Similarly to **/book**, any argument can be replaced with \*, which results in subscribing to pairs with any currency in place of the wildcard. Without arguments commands show list of user's subscriptions. """ source = message.text.upper().split() if len(source) == 1: user = await database.subscriptions.find_one({"id": message.from_user.id}) sublist = "" if user: for i, sub in enumerate(user["subscriptions"]): sublist += "\n{}. {} → {}".format( i + 1, sub["sell"] if sub["sell"] else "*", sub["buy"] if sub["buy"] else "*", ) if sublist: answer = i18n("your_subscriptions") + sublist else: answer = i18n("no_subscriptions") await tg.send_message(message.chat.id, answer, reply_markup=start_keyboard()) return try: sell, buy = source[1], source[2] sub = {"sell": None, "buy": None} if sell != "*": sub["sell"] = sell if buy != "*": sub["buy"] = buy except IndexError: await tg.send_message( message.chat.id, i18n("no_currency_argument"), reply_markup=start_keyboard(), ) return if command.command[0] == "s": update_result = await database.subscriptions.update_one( {"id": message.from_user.id}, { "$setOnInsert": {"chat": message.chat.id}, "$addToSet": {"subscriptions": sub}, }, upsert=True, ) if not update_result.matched_count or update_result.modified_count: await tg.send_message( message.chat.id, i18n("subscription_added"), reply_markup=start_keyboard(), ) else: await tg.send_message( message.chat.id, i18n("subscription_exists"), reply_markup=start_keyboard(), ) elif command.command[0] == "u": delete_result = await database.subscriptions.update_one( {"id": message.from_user.id}, {"$pull": {"subscriptions": sub}} ) if delete_result.modified_count: await tg.send_message( message.chat.id, i18n("subscription_deleted"), reply_markup=start_keyboard(), ) else: await tg.send_message( message.chat.id, i18n("subscription_delete_error"), reply_markup=start_keyboard(), ) else: raise AssertionError(f"Unknown command: {command.command}")
async def default_message(message: types.Message): """React to message which has not passed any previous conditions.""" await tg.send_message(message.chat.id, i18n("unknown_command"), reply_markup=start_keyboard())