Skip to content

Commit

Permalink
Fix errors when users absent from the database use bot
Browse files Browse the repository at this point in the history
As a consequence of losing data and loading an older dump we lost some
of our users as well, so when they trigger some of the handlers which
assume that user already is in the database, because bot adds them on
/start, it raises various exceptions.

This is a hack which solves the problem by replacing whatever user was
doing with the /start command as if user is new.

Signed-off-by: alfred richardsn <rchrdsn@protonmail.ch>
  • Loading branch information
r4rdsn committed Jul 3, 2020
1 parent 69a91ae commit 9255ead
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 16 deletions.
40 changes: 39 additions & 1 deletion src/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import asyncio
import logging
import typing
from time import time

from aiogram import Bot
from aiogram import types
Expand All @@ -27,6 +28,7 @@

from src.config import config
from src.database import database
from src.database import database_user
from src.database import MongoStorage
from src.i18n import i18n

Expand Down Expand Up @@ -81,8 +83,44 @@ async def request(self, method, data=None, *args, **kwargs):
return result


class DispatcherManual(Dispatcher):
"""Dispatcher with user availability in database check."""

async def process_update(self, update: types.Update):
"""Process update object with user availability in database check.
If bot doesn't know the user, it pretends they sent /start message.
"""
user = None
if update.message:
user = update.message.from_user
chat = update.message.chat
elif update.callback_query and update.callback_query.message:
user = update.callback_query.from_user
chat = update.callback_query.message.chat
if user:
db_user = await database.users.find_one({"id": user.id, "chat": chat.id})
if db_user is None:
if update.message:
update.message.text = "/start"
elif update.callback_query:
update = types.Update(
update_id=update.update_id,
message={
"message_id": -1,
"from": user.to_python(),
"chat": chat.to_python(),
"date": int(time()),
"text": "/start",
},
)
await update.callback_query.answer()
database_user.set(db_user)
return await super().process_update(update)


tg = TellerBot(None, loop=asyncio.get_event_loop(), validate_token=False)
dp = Dispatcher(tg)
dp = DispatcherManual(tg)


def setup():
Expand Down
3 changes: 3 additions & 0 deletions src/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with TellerBot. If not, see <https://www.gnu.org/licenses/>.
import typing
from contextvars import ContextVar

from aiogram.dispatcher.storage import BaseStorage
from motor.motor_asyncio import AsyncIOMotorClient
Expand All @@ -34,6 +35,8 @@
client = AsyncIOMotorClient(config.DATABASE_HOST)
database = client[config.DATABASE_NAME]

database_user: ContextVar[typing.Mapping[str, typing.Any]] = ContextVar("database_user")

STATE_KEY = "state"


Expand Down
10 changes: 5 additions & 5 deletions src/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
state_handlers,
)
from src.bot import tg
from src.database import database, STATE_KEY
from src.database import database, database_user, STATE_KEY
from src.i18n import i18n
from src.money import normalize

Expand Down Expand Up @@ -102,7 +102,7 @@ async def orders_list(
:param message_id: Telegram ID of message to edit.
:param invert: Invert all prices.
"""
user = await database.users.find_one({"id": types.User.get_current().id})
user = database_user.get()
if invert is None:
invert = user.get("invert_book", False)
else:
Expand All @@ -116,13 +116,13 @@ async def orders_list(
types.InlineKeyboardButton(
emojize(":arrow_left:"),
callback_data="{} {} {}".format(
buttons_data, start - config.ORDERS_COUNT, int(invert)
buttons_data, start - config.ORDERS_COUNT, 1 if invert else 0
),
),
types.InlineKeyboardButton(
emojize(":arrow_right:"),
callback_data="{} {} {}".format(
buttons_data, start + config.ORDERS_COUNT, int(invert)
buttons_data, start + config.ORDERS_COUNT, 1 if invert else 0
),
),
)
Expand Down Expand Up @@ -252,7 +252,7 @@ async def show_order(

new_edit_msg = None
if invert is None:
user = await database.users.find_one({"id": user_id})
user = database_user.get()
invert = user.get("invert_order", False)
else:
user = await database.users.find_one_and_update(
Expand Down
24 changes: 14 additions & 10 deletions src/handlers/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from src.bot import tg
from src.config import config
from src.database import database
from src.database import database_user
from src.database import STATE_KEY
from src.escrow import get_escrow_instance
from src.escrow.escrow_offer import EscrowOffer
Expand Down Expand Up @@ -344,13 +345,16 @@ async def escrow_button(call: types.CallbackQuery, order: OrderType):
i18n("cancel"), callback_data=f"init_cancel {offer_id}"
)
)
projection = {"id": True, "locale": True, "mention": True}
escrow_type = "buy" if get_escrow_instance(order["buy"]) else "sell"
init_user = await database.users.find_one(
{"id": call.from_user.id}, projection=projection
)
user = database_user.get()
init_user = {
"id": user["id"],
"locale": user["locale"],
"mention": user["mention"],
}
counter_user = await database.users.find_one(
{"id": order["user_id"]}, projection=projection
{"id": order["user_id"]},
projection={"id": True, "locale": True, "mention": True},
)
await database.escrow.delete_many({"init.send_address": {"$exists": False}})
offer = EscrowOffer(
Expand Down Expand Up @@ -399,7 +403,7 @@ async def edit_button(call: types.CallbackQuery):
answer = i18n("send_new_sell_amount")
keyboard.row(unset_button)
elif field == "price":
user = await database.users.find_one({"id": call.from_user.id})
user = database_user.get()
answer = i18n("new_price {of_currency} {per_currency}")
if user.get("invert_order", False):
answer = answer.format(of_currency=order["buy"], per_currency=order["sell"])
Expand Down Expand Up @@ -433,7 +437,7 @@ async def edit_button(call: types.CallbackQuery):
if not answer:
return

user = await database.users.find_one({"id": call.from_user.id})
user = database_user.get()
if "edit" in user:
await tg.delete_message(call.message.chat.id, user["edit"]["message_id"])
result = await tg.send_message(call.message.chat.id, answer, reply_markup=keyboard,)
Expand Down Expand Up @@ -482,7 +486,7 @@ async def finish_edit(user, update_dict):
)
async def default_duration(call: types.CallbackQuery, state: FSMContext):
"""Repeat default duration."""
user = await database.users.find_one({"id": call.from_user.id})
user = database_user.get()
order = await database.orders.find_one({"_id": user["edit"]["order_id"]})
await call.answer()
await finish_edit(
Expand All @@ -505,7 +509,7 @@ async def default_duration(call: types.CallbackQuery, state: FSMContext):
)
async def unset_button(call: types.CallbackQuery, state: FSMContext):
"""React to "Unset" button by unsetting the edit field."""
user = await database.users.find_one({"id": call.from_user.id})
user = database_user.get()
field = user["edit"]["field"]
if field == "price":
unset_dict = {"price_buy": True, "price_sell": True}
Expand All @@ -522,7 +526,7 @@ async def unset_button(call: types.CallbackQuery, state: FSMContext):
@private_handler(state=states.field_editing)
async def edit_field(message: types.Message, state: FSMContext):
"""Ask new value of chosen order's field during editing."""
user = await database.users.find_one({"id": message.from_user.id})
user = database_user.get()
edit = user["edit"]
field = edit["field"]
invert = user.get("invert_order", False)
Expand Down

0 comments on commit 9255ead

Please sign in to comment.