Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Задача II.4 Отправка сообщений #209

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from

Conversation

thereareyou123
Copy link
Collaborator

Добавил проверку статуса сообщения и подправил механизм отправки сообщений так, чтобы функция send_messgae_to_user вызывалась один раз

Copy link
Contributor

@teamofroman teamofroman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Подход почти правильный, но надо доработать.

for user in active_users:
if user.is_active and not user.is_block:
await send_message_to_user(context, user.tg_id, message)
for user_id in users_to_notify:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если в момент отправки сообщений бот упадет, то список users_to_notify почит во мраке истории вместе со своим содержимым. И при следующем запуске отправка будет осуществлена всем пользователям.

Список users_to_notify надо где-то сохранять и при каждой отправке фиксировать этот момент.
Например, можно сделать вспомогательную таблицу в БД в которой хранить список пользователей, которым надо отправить сообщение. Если сообщение пользователю отправлено, то можно или ставить признак отправки или удалять запись. При этом надо не забыть вести счетчик пользователей, которым надо отправить сообщения (для каждого сообщения), для того, что бы после отправки всем кому надо установить соответствующий признак сообщению.

if user.is_active and not user.is_block:
await send_message_to_user(context, user.tg_id, message)
for user_id in users_to_notify:
await send_message_to_user(context, user_id, message)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь надо учесть, что если сообщение не отправлено (например, у пользователя указан не корректный tg_id или пользователь заблокировал бот или ошибка сети) надо понять причину и или повторить отправку данному пельзователю (например, при ошибке сети) или отменить для него отправку (например, у пользователя указан не корректный tg_id или пользователь заблокировал бот).

Т.е. надо обрабатывать исключения, которые порождаются при работе функций context.bot.send_photo и context.bot.send_media_group.

@thereareyou123
Copy link
Collaborator Author

Я кажется нащупал нужное решение, но не уверен на 100%, что в нужную сторону направился, хотел бы узнать, правильно ли двигаюсь и что нужно подправить. Заранее благодарю за ответ

@thereareyou123
Copy link
Collaborator Author

Я уточню где именно у меня сомнения. Я вот добавил обработку ситуаций когда бот в блоке у юзера, или юзер удалил аккаунт, или его айдишник не правильно указан. Надо ли добавить еще и обработку других ошибок, при которых сразу запускается повторная отправка сообщения с задержкой. Или оставить в таком виде как я сделал, только добавить сопровождающие комментарии к новому коду

@teamofroman
Copy link
Contributor

teamofroman commented Feb 4, 2025

Надо ли добавить еще и обработку других ошибок, при которых сразу запускается повторная отправка сообщения с задержкой.

Да, надо. Может быть, например, ошибка сети. Тогда надо повторить отправку.

Добавить сопровождающие комментарии к новому коду

Желательно добавить их ко всему своему коду (можно кратко)

Copy link
Contributor

@teamofroman teamofroman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Надо немного поменять логику отправки сообщений (скмотри комментарии в коде).

Еще одно замечание - перед пушем кода в репозиторий его надо протестировать у себя локально. Тогда ошибки с пропуском await ты бы отловил сам.

Плюс, поскольку ты вносишь изменения в модель данных (добавил новую таблицу и поля в существующую) надо сделать файл миграций (его так же включить в пуш)

if message.is_send:
return

existing_statuses = crud_message.get_message_statuses(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В этом случае existing_statuses будет иметь тип Coroutine[Any, Any, Dict[int, MessageStatus]], а не Dict[int, MessageStatus], как планировалось.

Потерял await.

]
session.add_all(new_statuses)
session.commit()
statuses = crud_message.get_message_statuses(session, message_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аналогично, existing_statuses будет иметь тип Coroutine[Any, Any, Dict[int, MessageStatus]], а не Dict[int, MessageStatus], как планировалось.

Потерял await.

f'Ошибка отправки сообщения {error_msg}',
)
await asyncio.sleep(10)
context.job_queue.run_once(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если список пользователей большой и ошибка сети произойдет в начале-середине списка и за ~20 сек остальные сообщения не отправяться и не будет коммита (186 строка), то ряду пользователей сообщение отправится может несколько раз.

Здесь лучше целать не цикл for, а while statuses. В начале цикла делаешь pop (он извлекает элемент из коллекции и удаляет его из нее). Если отправка прошла успешно, то ок. Если нет возвращаешь этот элемент обратно в коллекцию. Цикл будет крутиться, пока вся коллекция не опустеет.

Надо так же предусмотреть ограничение количества попыток отправки, что бы не уйти в бесконечный цикл.

if not message.update_users:
update_data['update_users'] = user_id
# Обновление статуса сообщения через CRUD-функцию
await crud_message.update(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Обновление статуса сообщения надо делать после отправки сообщения всем пользователям. А то получится, что одному из 10 отправилось, а статус отправленного сообщения обновился.

},
name=f'retry_send_mes_{message_id}',
)
await session.commit()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Т.к. у нас не связанные (условно) данные об отправке, то коммит лучше делать в блоке finally, что бы транзакция была подтверждена в любом случае.

Да, мы получаем повышенную нагрузку на БД, но иначе мы опять таки рискуем отправить сообщение пользователю несколько раз.

…ыток отправки и переместил crud обновлящий статус сообщения
op.create_table('message_status',
sa.Column('message_id', sa.BigInteger(), nullable=True),
sa.Column('user_id', sa.BigInteger(), nullable=True),
sa.Column('status', sa.Enum('pending', 'sent'), nullable=True),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавлен параметр name, что бы alembic не ругался

op.alter_column('message_group', 'message_id',
existing_type=sa.BIGINT(),
nullable=False)
op.drop_table('message_status')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавлено, что бы alembic не ругался.
Alembic не умеет сам удалять типы Postgres, поэтому приходится делать это в ручную.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants