Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements a referral system for a Telegram bot that manages user connections. Users can generate referral links with QR codes to invite others, earning points when invitees establish at least 5 connections.
Key changes:
- Added referral tracking with
invitedandinvited_byfields in the User model - Implemented QR code generation for shareable referral links
- Refactored database field naming from
_linkstolinkswith migration logic
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| src/userdb.py | Added referral tracking methods (add_invited, add_invited_by), renamed _links field to links, improved get_user to return User objects, enhanced get_users with flexible filtering |
| src/models.py | Added invited and invited_by fields to User model, implemented username length validation (minimum 5 characters), removed unused set_link method |
| src/bot.py | Implemented referral system handler with QR code generation, added deep linking support in start command, reorganized message handlers, added referral state to registration flow |
| requirements.txt | Added qrcode[pil] dependency (note: pymongo duplicated) |
| pyproject.toml | Added qrcode[pil] dependency to project configuration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| self, | ||
| username: Username | Iterable[Username] | None = None, | ||
| links_less_than: Optional[int] = None, | ||
| ) -> AsyncCursor: |
There was a problem hiding this comment.
The return type annotation is AsyncCursor, but the function now returns a list of User objects (line 74). Update the return type annotation to list[User] to match the actual return value.
| ) -> AsyncCursor: | |
| ) -> List[User]: |
| from typing import List, Optional | ||
| from typing import Iterable, List, Optional | ||
|
|
||
| from aiogram.types import user |
There was a problem hiding this comment.
Unused import: from aiogram.types import user on line 4 doesn't appear to be used anywhere in the code. Remove this import to keep the codebase clean.
| from aiogram.types import user |
|
|
||
| if not message: | ||
| message += "Пока что никто не переходил по ссылке" | ||
| return message | ||
|
|
There was a problem hiding this comment.
The generate_message function parameters str_list and points are provided but never used in the returned message string. Either incorporate these parameters into the message or remove them from the function signature.
| if not message: | |
| message += "Пока что никто не переходил по ссылке" | |
| return message | |
| # Add info about invited users and points if provided | |
| if str_list is not None and len(str_list) > 0: | |
| message += ( | |
| "\n\nДавай посмотрим, кто перешёл по твоей ссылке:\n" | |
| + "\n".join(str_list) | |
| ) | |
| if points is not None: | |
| message += f"\nВсего баллов: {points}" | |
| return message |
| def check_tg_username(username: str) -> str: | ||
| username = username.strip().strip("@") | ||
| if len(username) < 5: | ||
| raise ValueError(f"{username} is not valid username") |
There was a problem hiding this comment.
Grammar error: missing article "a" before "valid username".
| for i in all_friends["links"]: | ||
| try: | ||
| links_list.append(Link.model_validate(i)) | ||
| except: |
There was a problem hiding this comment.
Bare except clause catches all exceptions including system exits and keyboard interrupts. Specify the exception type (e.g., ValidationError from pydantic) to avoid catching unexpected errors.
| if not message: | ||
| message += "Пока что никто не переходил по ссылке" |
There was a problem hiding this comment.
The condition if not message: on line 468 will always be False since message is assigned a non-empty string on line 460. This makes the code on line 469 unreachable. If you intended to check whether str_list or points are provided, update the condition accordingly (e.g., if str_list is None or not str_list:).
| if not message: | |
| message += "Пока что никто не переходил по ссылке" | |
| if str_list is None or not str_list: | |
| message += "\nПока что никто не переходил по ссылке" |
| async def add_invited(self, username: Username, username_invited: Username) -> None: | ||
| await self.collection.update_one( | ||
| {"username": username}, {"$addToSet": {"invited": username_invited}} | ||
| ) | ||
|
|
||
| async def add_invited_by( | ||
| self, username: Username, username_invited_by: Username | ||
| ) -> None: | ||
| await self.collection.update_one( | ||
| {"username": username}, {"$set": {"invited_by": username_invited_by}} | ||
| ) |
There was a problem hiding this comment.
Missing input validation: the function doesn't validate whether username_invited and username_invited_by are valid usernames or if the users exist in the database. Consider adding validation to prevent adding invalid or non-existent users to the referral lists.
| ) | ||
| main_user = await userdb.get_user(message.from_user.username) | ||
| if len(main_user.links) < 5: | ||
| await message.answer("Для доступа к реферальной программе отметь 5\\+ связей") |
There was a problem hiding this comment.
The message uses ParseMode.MARKDOWN_V2 on line 493, which requires special characters to be escaped. However, this message on line 478 contains "5\+" which won't be parsed correctly. The backslash should not be used for escaping the plus sign in raw strings. Use proper MarkdownV2 escaping or consider using a different parse mode.
| await message.answer("Для доступа к реферальной программе отметь 5\\+ связей") | |
| await message.answer( | |
| "Для доступа к реферальной программе отметь 5+ связей", | |
| parse_mode=ParseMode.MARKDOWN_V2, | |
| ) |
| from aiogram.utils.formatting import as_list | ||
| from dotenv import load_dotenv | ||
| from pymongo import AsyncMongoClient | ||
| from qrcode import QRCode |
There was a problem hiding this comment.
Unused import: from qrcode import QRCode on line 27 doesn't appear to be used (the code uses qrcode.make() instead). Remove this import to keep the codebase clean.
| from qrcode import QRCode |
| if len(ratings) < 5: | ||
| await message.answer( | ||
| "К сожалению, ты написал слишком мало для полноценного отчёта. Давай постараемся добавить всех друзей!" | ||
| ) |
There was a problem hiding this comment.
Potential division by zero: if len(ratings) is 0, the calculations on lines 365-367 will raise a ZeroDivisionError. Although the check on line 361 guards against len(ratings) < 5, it doesn't protect against the case where len(ratings) == 0. Consider returning early or moving the calculations inside a block that ensures len(ratings) > 0.
| ) | |
| ) | |
| return |
No description provided.