Skip to content

Conversation

@ink-developer
Copy link
Collaborator

@ink-developer ink-developer commented Dec 30, 2025

Описание

Кратко, что делает этот PR. Например, добавляет новый метод, исправляет баг, улучшает документацию.

Новые методы

read_message
get_current_web_version (pymax.utils)

Измененный методы

start
close

Новые параметры

session_name

Измененные типы

BaseFile
Photo
File
Video

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

Тип изменений

  • Исправление бага
  • Новая функциональность
  • Улучшение документации
  • Рефакторинг

Связанные задачи / Issue

Ссылка на issue, если есть: #

Тестирование

Покажите пример кода, который проверяет изменения:

import pymax

# пример использования нового функционала

Summary by CodeRabbit

  • New Features

    • Profile photo upload and change; mark individual messages as read; automatic read-receipt notifications
    • Join/resolve groups via shareable links; contact sharing in messages; contact list exposure
    • Session customization (session_name) and web-version utility for diagnostics
  • Updated Types / Media

    • File/Photo/Video accept in-memory raw bytes; messages support contact attachments and new read-state type
  • Documentation

    • Quickstart and client-choice guides updated (QR login emphasized; socket client flow documented)
  • Misc

    • Improved User-Agent generation for more realistic device parameters

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 30, 2025

📝 Walkthrough

Walkthrough

Bumps version to 1.2.4 and adds ua-generator; restructures client/transport interfaces (BaseClient/BaseTransport/ClientProtocol), adds device-type and session handling, read/contact attach APIs, raw file support, UA/randomized constants, web-version utility, and extensive docs updates.

Changes

Cohort / File(s) Change Summary
Version & Dependency
pyproject.toml
Bumped version 1.2.31.2.4; added runtime dependency ua-generator>=2.0.19; updated project URLs to MaxApiTeam/PyMax.
Docs (index, clients, quickstart, examples, guides, release notes, conf, installation)
redocs/source/index.rst, redocs/source/clients.rst, redocs/source/quickstart.rst, redocs/source/examples.rst, redocs/source/guides.rst, redocs/source/release_notes.rst, redocs/source/conf.py, redocs/source/installation.rst
New release_notes to toctree; detailed docs for MaxClient vs SocketMaxClient, token-reuse workflow, contact/profile/group features, examples adjusted (Filters usage, attach URL handling), doc release bumped, and repo URL updates.
Core lifecycle & session
src/pymax/core.py
Added allowed_device_types, session_name param and SESSION_STORAGE_DB, session DB path handling, _stop_event lifecycle control, _default_headers() and _validate_device_type(), and reconnection loop updated to await stop event (FIRST_COMPLETED).
Interface & protocol split
src/pymax/interfaces.py, src/pymax/protocols.py
Introduced BaseClient and BaseTransport (interfaces moved/expanded); added safe-task helpers, dispatch/queueing abstractions, async context manager; new abstract ClientProtocol class with core state and abstract methods (_send_and_wait, _get_chat, _queue_message, _create_safe_task).
Mixins — transport alignment & surface reduction
src/pymax/mixins/socket.py, src/pymax/mixins/websocket.py
Mixins now inherit from transport abstractions (BaseTransport); removed large amounts of internal handshake/queue/dispatch logic from mixins (moved to transport/interface layer), simplifying mixin internals.
Mixin import updates & minor formatting
src/pymax/mixins/*.py
Updated imports to use pymax.protocols and pymax.utils; various small formatting/signature consolidations in auth, channel, group, handler, scheduler, self, telemetry, user mixins.
Message read & payloads
src/pymax/mixins/message.py, src/pymax/payloads.py, src/pymax/static/enum.py, src/pymax/types.py
Added ReadMessagesPayload and new enum ReadAction; added ReadState type; added MessageMixin.read_message(chat_id, message_id) -> ReadState; minor signature formatting for pin_message.
Files & raw upload support
src/pymax/files.py
BaseFile, Photo, Video, File constructors accept optional raw: bytes; BaseFile.read() short-circuits to raw when present; Photo gains name/file_name resolution.
Contacts / profile / group APIs
src/pymax/core.py, redocs/source/clients.rst, redocs/source/release_notes.rst
New MaxClient public API: change_profile(first_name, last_name, description, photo), resolve_group_by_link(link), and contacts: list[User] property; docs updated to describe contact attach and profile features.
Utilities & error handling relocation
src/pymax/utils.py, src/pymax/mixins/utils.py
Removed old MixinsUtils (mixins/utils.py); added new MixinsUtils in src/pymax/utils.py with handle_error() and get_current_web_version() (JS chunk fetch/parse).
Constants & UA generation
src/pymax/static/constant.py
Added DEVICE_NAMES, SCREEN_SIZES, OS_VERSIONS, TIMEZONES, SESSION_STORAGE_DB; randomized defaults (device name, OS version, client session id, timezone); DEFAULT_USER_AGENT now from ua_generator.generate().text; bumped DEFAULT_APP_VERSION.
Project config removed
mkdocs.yml
Deleted MkDocs configuration file (site/theme/plugins/markdown extensions and social links removed).
README metadata
README.md
Updated contributors repo link to MaxApiTeam/PyMax.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Socket as SocketMaxClient
    participant Auth as Auth Server (TCP)
    participant DB as Session DB (session.db)
    participant WS as MaxClient (WebSocket)

    Note over Socket,Auth: Phone-based auth via SocketMaxClient
    Socket->>Auth: register/login (phone + device_type DESKTOP/ANDROID/IOS)
    Auth-->>Socket: returns token
    Socket->>DB: persist token (session_name / session.db)
    Note over WS,DB: WebSocket fast-connect using persisted token
    WS->>DB: read token (session_name)
    WS->>Auth: connect WebSocket with token + headers (device_type WEB, UA)
    Auth-->>WS: authenticated WS session
    Note over WS: now use MaxClient features (messages, contacts, change_profile)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Dev/1.2.1 #30 — Overlaps on client lifecycle, transport/interface refactor and device_type/default headers changes.
  • Dev/1.2.3 #33 — Related to profile/group APIs, contact-attach updates, and related public API additions.
  • 1.1.21 #27 — Related protocol/interface work and handler/scheduler surface changes.

Poem

🐰 I hop through bytes and headers bright,
New transports hum into the night,
Tokens stored and profiles neat,
Contacts, reads — carrots to eat,
Version bumped — the code takes flight! 🚀

Pre-merge checks

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.35% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The PR title 'Dev/1.2.4' is too vague and generic, using a version branch naming convention rather than describing the actual changes made. Replace with a descriptive title that highlights the main changes, such as 'Refactor client lifecycle and add message read tracking' or 'v1.2.4: Enhance security and add read_message method'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description lists new methods and parameters but lacks detail on what changes were made, their purpose, and the rationale behind the refactoring efforts mentioned.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/pymax/files.py (3)

12-23: Critical: Validation logic prevents raw-only initialization.

The validation at lines 19-23 requires either url or path to be provided, but the new raw parameter should allow initialization with raw bytes alone. Currently, BaseFile(raw=b"...") will raise ValueError("Either url or path must be provided."), making the raw-bytes feature unusable.

🔎 Proposed fix
     def __init__(
         self, raw: bytes | None = None, *, url: str | None = None, path: str | None = None
     ) -> None:
         self.raw = raw
         self.url = url
         self.path = path
 
-        if self.url is None and self.path is None:
+        if self.url is None and self.path is None and self.raw is None:
             raise ValueError("Either url or path must be provided.")
 
         if self.url and self.path:
             raise ValueError("Only one of url or path must be provided.")

Consider also validating that only one of raw, url, or path is provided to prevent ambiguous initialization.


94-105: Critical: Cannot initialize Video with raw-only data.

The validation at lines 103-104 raises ValueError if file_name is empty, but file_name will be empty when only raw is provided (without path or url). This makes Video(raw=b"...") completely unusable and contradicts the PR objective of supporting raw byte initialization.

🔎 Proposed fix
     def __init__(
         self, raw: bytes | None = None, *, url: str | None = None, path: str | None = None
     ) -> None:
-        self.file_name: str = ""
+        self.file_name: str = "video"  # default for raw bytes
         if path:
             self.file_name = Path(path).name
         elif url:
             self.file_name = Path(url).name
 
-        if not self.file_name:
-            raise ValueError("Either url or path must be provided.")
         super().__init__(raw=raw, url=url, path=path)

Consider accepting an optional name parameter for better control over raw-bytes file names.


113-125: Critical: Cannot initialize File with raw-only data.

The validation at lines 122-123 raises ValueError if file_name is empty, but file_name will be empty when only raw is provided. This makes File(raw=b"...") unusable, identical to the issue in Video.__init__.

🔎 Proposed fix
     def __init__(
         self, raw: bytes | None = None, *, url: str | None = None, path: str | None = None
     ) -> None:
-        self.file_name: str = ""
+        self.file_name: str = "file"  # default for raw bytes
         if path:
             self.file_name = Path(path).name
         elif url:
             self.file_name = Path(url).name
 
-        if not self.file_name:
-            raise ValueError("Either url or path must be provided.")
-
         super().__init__(raw=raw, url=url, path=path)

Consider accepting an optional name parameter for better control over raw-bytes file names.

src/pymax/mixins/websocket.py (1)

131-133: Generic RuntimeError loses exception context.

Raising RuntimeError("Send and wait failed") discards the original exception. Consider using raise ... from or re-raising the original exception to preserve the traceback.

🔎 Proposed fix
         except Exception:
             self.logger.exception("Send and wait failed (opcode=%s, seq=%s)", opcode, msg["seq"])
-            raise RuntimeError("Send and wait failed")
+            raise
         finally:
             self._pending.pop(msg["seq"], None)
src/pymax/mixins/socket.py (1)

85-87: Sequence number overflow will cause unhandled OverflowError.

_seq increments without bound (interfaces.py line 188), but seq is packed into a single byte at line 87. After 255 messages, seq.to_bytes(1, "big") will raise OverflowError. The exception is not caught in the try-except block (lines 253-274), which only handles SSL and connection errors. The protocol does not implement wrapping behavior (no modulo logic found), and _seq is not reset on reconnection. This will crash the send operation after 256 messages.

🧹 Nitpick comments (13)
src/pymax/static/constant.py (1)

78-86: Module-level randomization may not provide per-client diversity.

Lines 78, 81, 84-85 use choice() and randint() at module initialization, which means all client instances in the same Python process will share the same "random" values. If the intent is to have different user-agents per client session, consider moving this randomization to instance initialization rather than module constants.

Regarding the static analysis S311 warnings: these are false positives in this context since the random values are for user-agent diversity, not cryptographic security.

Example approach for per-client randomization

If per-client diversity is desired, consider making these factory functions rather than constants, or move the randomization to client initialization:

def get_default_device_name() -> str:
    return choice(DEVICE_NAMES)

def get_default_os_version() -> str:
    return choice(OS_VERSIONS)

# etc.

Then use these functions during client instantiation instead of at module load time.

src/pymax/mixins/socket.py (2)

15-17: Unused import: ClientProtocol.

ClientProtocol is imported but not used directly in this file. SocketMixin inherits from BaseTransport, which already extends ClientProtocol.

🔎 Proposed fix
 from pymax.interfaces import BaseTransport
 from pymax.payloads import BaseWebSocketMessage, SyncPayload, UserAgentPayload
-from pymax.protocols import ClientProtocol
 from pymax.static.constant import (

108-116: ANSI escape codes in log message.

The warning message contains ANSI escape codes (\033[0;31m) which won't render properly in log files or non-TTY environments. Consider using plain text or the logger's built-in formatting.

🔎 Proposed fix
         if sys.version_info[:2] == (3, 12):
             self.logger.warning(
                 """
 ===============================================================
-         ⚠️⚠️ \033[0;31mWARNING: Python 3.12 detected!\033[0m ⚠️⚠️
+         ⚠️⚠️ WARNING: Python 3.12 detected! ⚠️⚠️
 Socket connections may be unstable, SSL issues are possible.
 ===============================================================
     """
             )
src/pymax/mixins/websocket.py (2)

26-27: Exception raised as class, not instance.

WebSocketNotConnectedError is raised as a class reference instead of an instance. This works but is inconsistent with Python conventions and the exception's __init__ that sets the message.

🔎 Proposed fix
         if self._ws is None or not self.is_connected:
             self.logger.critical("WebSocket not connected when access attempted")
-            raise WebSocketNotConnectedError
+            raise WebSocketNotConnectedError()
         return self._ws

88-90: Exception set as class, not instance.

Same issue as above - WebSocketNotConnectedError should be instantiated when setting as exception on futures.

🔎 Proposed fix
                 for fut in self._pending.values():
                     if not fut.done():
-                        fut.set_exception(WebSocketNotConnectedError)
+                        fut.set_exception(WebSocketNotConnectedError())
                 self._pending.clear()
src/pymax/utils.py (1)

69-70: Unused variable e (static analysis hint).

The exception variable e is assigned but never used.

🔎 Proposed fix
         try:
             main_chunk_code = requests.get(main_chunk_url, timeout=10).text
-        except requests.exceptions.RequestException as e:
+        except requests.RequestException:
             return None
src/pymax/protocols.py (1)

94-117: Type inconsistency: opcode parameter types differ between abstract methods.

_send_and_wait uses opcode: Opcode (enum), while _queue_message uses opcode: int. This inconsistency could lead to type confusion. Consider using Opcode consistently.

🔎 Proposed fix
     @abstractmethod
     async def _queue_message(
         self,
-        opcode: int,
+        opcode: Opcode,
         payload: dict[str, Any],
         cmd: int = 0,
         timeout: float = DEFAULT_TIMEOUT,
         max_retries: int = 3,
     ) -> Message | None:
         pass
src/pymax/core.py (2)

41-41: Mutable class attributes should use ClassVar.

Static analysis correctly identifies that mutable class attributes (allowed_device_types) should be annotated with typing.ClassVar to indicate they are not instance attributes.

🔎 Proposed fix
+from typing import ClassVar
+
 class MaxClient(ApiMixin, WebSocketMixin, BaseClient):
-    allowed_device_types: set[str] = {"WEB"}
+    allowed_device_types: ClassVar[set[str]] = {"WEB"}
     ...

 class SocketMaxClient(SocketMixin, MaxClient):
-    allowed_device_types = {"ANDROID", "IOS", "DESKTOP"}
+    allowed_device_types: ClassVar[set[str]] = {"ANDROID", "IOS", "DESKTOP"}

Also applies to: 334-334


306-318: Unused variables done and e (static analysis hints).

The done set from asyncio.wait and exception variable e are assigned but never used.

🔎 Proposed fix
-                done, pending = await asyncio.wait(
+                _, pending = await asyncio.wait(
                     [wait_task, stop_task], return_when=asyncio.FIRST_COMPLETED
                 )
                 ...
-            except Exception as e:
+            except Exception:
                 self.logger.exception("Client start iteration failed")
src/pymax/interfaces.py (4)

317-378: Optional: Simplify exception logging.

The reaction, chat update, and raw receive handlers are well-implemented. However, Lines 344, 362, and 371 include redundant exception objects in logging.exception() calls. The exception() method already captures exception info automatically.

🔎 Proposed fix
-                self.logger.exception("Error in on_reaction_change_handler: %s", e)
+                self.logger.exception("Error in on_reaction_change_handler")
-                self.logger.exception("Error in on_chat_update_handler: %s", e)
+                self.logger.exception("Error in on_chat_update_handler")
-                self.logger.exception("Error in on_raw_receive_handler: %s", e)
+                self.logger.exception("Error in on_raw_receive_handler")

380-386: Optional: Simplify exception logging.

The task exception logging helper works correctly, but Line 386 includes a redundant exception object in the logging.exception() call, which automatically captures exception info.

🔎 Proposed fix
-            self.logger.exception("Error retrieving task exception: %s", e)
+            self.logger.exception("Error retrieving task exception")

428-428: TODO: Consider persistent message queue.

The comment suggests implementing a persistent message queue. This could improve reliability by surviving client restarts.

Would you like me to open an issue to track this enhancement?


508-509: Remove unused variable assignment.

The error variable is assigned on Line 508 but never used. The walrus operator captures the error value, but it's immediately passed to MixinsUtils.handle_error(), making the local binding unnecessary.

🔎 Proposed fix
-            if error := raw_payload.get("error"):
+            if raw_payload.get("error"):
                 MixinsUtils.handle_error(data)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69dac16 and 8b0413c.

📒 Files selected for processing (29)
  • pyproject.toml
  • redocs/source/clients.rst
  • redocs/source/conf.py
  • redocs/source/examples.rst
  • redocs/source/guides.rst
  • redocs/source/index.rst
  • redocs/source/quickstart.rst
  • redocs/source/release_notes.rst
  • src/pymax/core.py
  • src/pymax/files.py
  • src/pymax/interfaces.py
  • src/pymax/mixins/auth.py
  • src/pymax/mixins/channel.py
  • src/pymax/mixins/group.py
  • src/pymax/mixins/handler.py
  • src/pymax/mixins/message.py
  • src/pymax/mixins/scheduler.py
  • src/pymax/mixins/self.py
  • src/pymax/mixins/socket.py
  • src/pymax/mixins/telemetry.py
  • src/pymax/mixins/user.py
  • src/pymax/mixins/utils.py
  • src/pymax/mixins/websocket.py
  • src/pymax/payloads.py
  • src/pymax/protocols.py
  • src/pymax/static/constant.py
  • src/pymax/static/enum.py
  • src/pymax/types.py
  • src/pymax/utils.py
💤 Files with no reviewable changes (3)
  • redocs/source/quickstart.rst
  • redocs/source/guides.rst
  • src/pymax/mixins/utils.py
🧰 Additional context used
🧬 Code graph analysis (14)
src/pymax/utils.py (1)
src/pymax/exceptions.py (2)
  • Error (60-86)
  • RateLimitError (89-97)
src/pymax/mixins/telemetry.py (3)
src/pymax/protocols.py (1)
  • ClientProtocol (32-123)
src/pymax/static/enum.py (1)
  • Opcode (4-146)
src/pymax/payloads.py (1)
  • NavigationEventPayload (258-263)
src/pymax/payloads.py (1)
src/pymax/static/enum.py (1)
  • ReadAction (221-223)
src/pymax/mixins/channel.py (3)
src/pymax/protocols.py (1)
  • ClientProtocol (32-123)
src/pymax/types.py (2)
  • Channel (942-949)
  • Member (167-213)
src/pymax/utils.py (1)
  • MixinsUtils (11-90)
src/pymax/mixins/group.py (2)
src/pymax/protocols.py (1)
  • ClientProtocol (32-123)
src/pymax/utils.py (1)
  • MixinsUtils (11-90)
src/pymax/mixins/scheduler.py (1)
src/pymax/protocols.py (1)
  • ClientProtocol (32-123)
src/pymax/protocols.py (5)
src/pymax/payloads.py (1)
  • UserAgentPayload (42-53)
src/pymax/static/enum.py (1)
  • Opcode (4-146)
src/pymax/interfaces.py (1)
  • _send_and_wait (174-180)
src/pymax/mixins/socket.py (1)
  • _send_and_wait (238-290)
src/pymax/mixins/websocket.py (1)
  • _send_and_wait (103-135)
src/pymax/mixins/auth.py (3)
src/pymax/protocols.py (1)
  • ClientProtocol (32-123)
src/pymax/static/enum.py (3)
  • AuthType (173-177)
  • DeviceType (186-189)
  • Opcode (4-146)
src/pymax/utils.py (1)
  • MixinsUtils (11-90)
src/pymax/mixins/self.py (2)
src/pymax/protocols.py (1)
  • ClientProtocol (32-123)
src/pymax/utils.py (1)
  • MixinsUtils (11-90)
src/pymax/mixins/message.py (4)
src/pymax/payloads.py (1)
  • ReadMessagesPayload (363-367)
src/pymax/static/enum.py (2)
  • Opcode (4-146)
  • ReadAction (221-223)
src/pymax/types.py (18)
  • Message (663-765)
  • ReadState (1198-1220)
  • from_dict (34-35)
  • from_dict (72-78)
  • from_dict (146-156)
  • from_dict (190-205)
  • from_dict (246-260)
  • from_dict (284-292)
  • from_dict (323-332)
  • from_dict (367-376)
  • from_dict (415-426)
  • from_dict (452-459)
  • from_dict (483-487)
  • from_dict (502-511)
  • from_dict (532-540)
  • from_dict (558-559)
  • from_dict (577-582)
  • from_dict (601-602)
src/pymax/interfaces.py (1)
  • _send_and_wait (174-180)
src/pymax/mixins/socket.py (3)
src/pymax/interfaces.py (1)
  • BaseTransport (167-552)
src/pymax/payloads.py (3)
  • BaseWebSocketMessage (34-39)
  • SyncPayload (68-90)
  • UserAgentPayload (42-53)
src/pymax/protocols.py (1)
  • ClientProtocol (32-123)
src/pymax/core.py (2)
src/pymax/exceptions.py (1)
  • WebSocketNotConnectedError (13-20)
src/pymax/payloads.py (1)
  • UserAgentPayload (42-53)
src/pymax/mixins/websocket.py (5)
src/pymax/exceptions.py (1)
  • WebSocketNotConnectedError (13-20)
src/pymax/interfaces.py (1)
  • BaseTransport (167-552)
src/pymax/payloads.py (1)
  • UserAgentPayload (42-53)
src/pymax/static/enum.py (1)
  • Opcode (4-146)
src/pymax/types.py (1)
  • Chat (837-939)
src/pymax/mixins/handler.py (1)
src/pymax/protocols.py (1)
  • ClientProtocol (32-123)
🪛 Ruff (0.14.10)
src/pymax/utils.py

69-69: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

src/pymax/static/constant.py

78-78: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


81-81: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


84-84: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


85-85: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)

src/pymax/core.py

41-41: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


43-43: String contains ambiguous с (CYRILLIC SMALL LETTER ES). Did you mean c (LATIN SMALL LETTER C)?

(RUF001)


193-196: Avoid specifying long messages outside the exception class

(TRY003)


306-306: Unpacked variable done is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


318-318: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


334-334: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

src/pymax/interfaces.py

209-209: Do not catch blind exception: Exception

(BLE001)


251-251: Do not catch blind exception: Exception

(BLE001)


344-344: Redundant exception object included in logging.exception call

(TRY401)


362-362: Redundant exception object included in logging.exception call

(TRY401)


371-371: Redundant exception object included in logging.exception call

(TRY401)


386-386: Redundant exception object included in logging.exception call

(TRY401)


444-444: Do not catch blind exception: Exception

(BLE001)


469-472: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


508-508: Local variable error is assigned to but never used

(F841)


546-546: Use raise without specifying exception name

Remove exception name

(TRY201)

🔇 Additional comments (43)
src/pymax/files.py (1)

27-28: The short-circuit implementation is correct and does not bypass validation. Photo objects require either url or path per BaseFile.__init__() constraints (lines 19-23), and validate_photo() validates based on these required attributes, not raw bytes. The raw parameter is simply an optional optimization to avoid redundant I/O operations when bytes are already available upstream.

Likely an incorrect or invalid review comment.

redocs/source/examples.rst (2)

41-41: Verify the filter change is intentional and update the example description if needed.

The Greeter Bot example changed from Filters.private() to Filters.chat(123), which fundamentally alters the behavior—it now only greets in chat ID 123 rather than all private chats. The example description still says "Приветствует новых пользователей" (Greets new users) without mentioning the chat-specific restriction.


153-154: Simplified file attachment example looks good.

The updated example now directly prints attach.base_url instead of making an additional API call, which is cleaner and more straightforward for documentation purposes.

src/pymax/static/constant.py (3)

1-1: Imports look good.

The addition of choice and randint from the random module is appropriate for the randomization logic introduced in this file.


10-66: New constant collections added for user-agent diversification.

The addition of DEVICE_NAMES, SCREEN_SIZES, OS_VERSIONS, and TIMEZONES provides a good foundation for generating diverse user agents and avoiding fingerprinting.


82-82: The concerns about ua_generator.generate() are not applicable to this codebase. The ua-generator library is a lightweight, pure-Python utility that performs only in-memory string construction with no I/O, no external dependencies, and no special initialization requirements. Calling it at module import time has negligible performance impact. Additionally, while DEFAULT_USER_AGENT is a module-level constant shared across the process, this is intentional design—the value is used as a default in Pydantic models and can be overridden on a per-request basis. The pattern is consistent with other constants in the file (e.g., DEFAULT_OS_VERSION, DEFAULT_CLIENT_SESSION_ID).

Likely an incorrect or invalid review comment.

src/pymax/mixins/self.py (2)

19-19: Import path update aligns with architectural refactoring.

The ClientProtocol import has been correctly updated from pymax.interfaces to pymax.protocols as part of the broader protocol reorganization in this PR.


22-22: MixinsUtils import path updated correctly.

The import has been correctly updated from pymax.mixins.utils to pymax.utils, aligning with the utility module reorganization.

src/pymax/mixins/scheduler.py (1)

6-6: Import path update is correct.

The ClientProtocol import has been properly updated to use pymax.protocols, consistent with the architectural changes across the codebase.

src/pymax/mixins/handler.py (2)

5-5: Import path correctly updated.

The ClientProtocol import has been properly updated to pymax.protocols, consistent with the architectural refactoring.


65-65: Minor formatting improvements enhance readability.

The consolidation of multi-line statements to single lines (debug logging and task scheduling) improves code conciseness without affecting functionality.

Also applies to: 89-89, 178-178

src/pymax/mixins/auth.py (2)

11-11: Import path update is correct.

The ClientProtocol import has been properly updated from pymax.interfaces to pymax.protocols, aligning with the protocol reorganization.


14-14: MixinsUtils import path updated correctly.

The import has been correctly updated from pymax.mixins.utils to pymax.utils, consistent with the utility module reorganization across the codebase.

pyproject.toml (1)

22-22: ua-generator 2.0.19 is the latest stable release with no known security vulnerabilities.

src/pymax/payloads.py (1)

18-18: LGTM! New payload model follows established patterns.

The ReadMessagesPayload addition is consistent with other payload models in the file and properly integrates with the new ReadAction enum.

Also applies to: 361-367

redocs/source/index.rst (1)

38-49: Documentation updates appropriately reflect new features.

The addition of release notes and feature descriptions aligns well with the new functionality introduced in this PR.

src/pymax/mixins/user.py (1)

9-9: Import refactoring aligns with new module structure.

The updates to import paths (ClientProtocol from pymax.protocols, MixinsUtils from pymax.utils) are consistent with the broader architectural refactoring in this PR.

Also applies to: 12-12, 125-125

src/pymax/types.py (1)

1196-1220: Well-structured type class following project conventions.

The ReadState class implementation is consistent with other type classes in the file, with proper initialization, serialization, and string representation methods.

src/pymax/mixins/channel.py (1)

8-8: Import updates consistent with architectural refactoring.

Changes align with the project-wide module restructuring, moving to the new pymax.protocols and pymax.utils imports.

Also applies to: 15-15, 116-116

src/pymax/mixins/group.py (1)

19-19: Import refactoring matches project-wide pattern.

The import path updates are consistent with the architectural changes moving ClientProtocol and MixinsUtils to their new module locations.

Also applies to: 22-22

src/pymax/static/enum.py (1)

219-223: New enum follows established conventions.

The ReadAction enum is properly structured and consistent with other enum definitions in the file.

src/pymax/mixins/telemetry.py (1)

12-12: Refactoring aligns with new module structure.

Import and formatting updates are consistent with the broader architectural changes in this PR.

Also applies to: 17-17

redocs/source/clients.rst (1)

87-111: LGTM! Documentation for new API methods is clear and consistent.

The new documentation for change_profile(), resolve_group_by_link(), and client.contacts property follows the existing style and provides useful examples.

src/pymax/mixins/socket.py (1)

276-284: Reconnection logic in _send_and_wait is reasonable but note recursion risk.

The reconnection attempt within _send_and_wait is appropriate, and re-raising SocketNotConnectedError after the attempt ensures the caller can handle the failure. However, be aware that rapid consecutive failures could cause stack depth issues if callers retry immediately.

src/pymax/mixins/message.py (2)

853-879: New read_message method looks good, but missing payload validation.

The implementation is clean and follows the existing patterns. However, line 879 accesses data["payload"] directly which could raise KeyError if the response is malformed. Consider using .get("payload", {}) with appropriate fallback handling.

🔎 Suggested defensive fix
         self.logger.debug("read_message success")
-        return ReadState.from_dict(data["payload"])
+        payload = data.get("payload")
+        if not payload:
+            raise Error("no_payload", "Payload missing in response", "Read Error")
+        return ReadState.from_dict(payload)

26-45: Import additions for read_message functionality are appropriate.

The new imports (ReadMessagesPayload, ReadAction, ReadState, MixinsUtils) are all used by the new read_message method and related error handling.

redocs/source/release_notes.rst (1)

1-2: Version mismatch between PR and release notes.

The PR is titled "Dev/1.2.4" but the release notes document version 1.2.3. Please verify if this is intentional (documenting the previous release) or if the version should be updated to 1.2.4.

Also applies to: 58-61

src/pymax/protocols.py (1)

32-92: Well-structured abstract protocol class.

The ClientProtocol ABC properly defines the contract for client implementations with comprehensive state initialization and clear abstract method signatures. The use of TYPE_CHECKING for conditional imports is a good practice.

src/pymax/core.py (2)

275-330: Improved lifecycle management with _stop_event.

The refactored start() method using _stop_event for graceful shutdown is a significant improvement over direct task cancellation. The reconnection loop with proper cleanup is well-structured.


187-196: Device type validation is a good security/correctness addition.

The _validate_device_type() method ensures that clients only use supported device types, preventing configuration errors. The separate allowed_device_types for MaxClient (WEB) and SocketMaxClient (ANDROID, IOS, DESKTOP) aligns with the authentication requirements documented in clients.rst.

src/pymax/utils.py (1)

58-90: This function appears to be unused and should be removed or refactored.

get_current_web_version() is currently not called anywhere in the codebase. However, if it is intended for future use, the synchronous requests.get() calls (lines 61, 68) are incompatible with the async architecture used throughout the rest of the codebase. Either remove this method if it's dead code, or convert it to use aiohttp like the rest of the project.

src/pymax/interfaces.py (12)

1-32: LGTM!

The imports are well-organized and include all necessary dependencies for the abstract base classes and WebSocket handling implementation.


36-46: LGTM!

The lazy logger setup with ColoredFormatter is well-implemented with proper guards against duplicate handler registration.


48-69: LGTM!

The safe execution wrappers provide good error isolation for background tasks with proper exception handling and lifecycle tracking.


71-107: LGTM!

The cleanup logic is comprehensive and properly handles task cancellation, pending futures, and WebSocket closure with appropriate exception handling for cleanup contexts.


109-144: LGTM!

The utility methods and async context manager implementation are correct. The idle() method properly uses an Event for indefinite waiting, and the inspect method provides useful debugging output.


146-164: LGTM!

The abstract method declarations properly define the contract for subclasses implementing the client lifecycle.


167-198: LGTM!

The abstract transport methods and message creation logic are well-implemented with proper sequence tracking and debug logging.


200-229: LGTM!

The interactive ping loop and handshake implementation are correct. The broad exception handling in the ping loop (Line 209) is appropriate for a keep-alive mechanism that should not crash the client.


231-262: LGTM!

The message handler processing, JSON parsing, and pending response matching logic are correctly implemented with appropriate error handling.


264-315: LGTM!

The queue handling, file upload notifications, and message notification processing are well-structured with proper status-based handler dispatch.


478-486: LGTM!

The retry delay logic properly differentiates between error types with appropriate backoff strategies.


488-552: LGTM on overall sync implementation!

The sync method properly populates all entity collections (dialogs, chats, channels, contacts, profile) from the LOGIN response with comprehensive error handling and connection cleanup.

Comment on lines +448 to +455
if self._error_count > 10:
self._circuit_breaker = True
self.logger.warning(
"Circuit breaker activated due to %d consecutive errors",
self._error_count,
)
await self._outgoing.put(message)
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Review circuit breaker re-queue logic.

When the circuit breaker activates (line 449), the message is re-queued (line 454) without incrementing retry_count or checking max_retries. If errors persist, this could lead to the same message being re-queued indefinitely once the breaker resets every 60 seconds.

Consider either dropping the message after the breaker activates a certain number of times, or incrementing the retry count before re-queuing.

🔎 Proposed fix to respect max_retries
                    if self._error_count > 10:
                        self._circuit_breaker = True
                        self.logger.warning(
                            "Circuit breaker activated due to %d consecutive errors",
                            self._error_count,
                        )
+                       if retry_count < max_retries:
+                           message["retry_count"] = retry_count + 1
-                       await self._outgoing.put(message)
+                           await self._outgoing.put(message)
+                       else:
+                           self.logger.error("Message dropped: circuit breaker active and max retries exceeded")
                        continue

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/pymax/interfaces.py around lines 448 to 455, the circuit breaker path
currently re-queues the message without updating retry state, risking endless
re-delivery; modify this block to increment the message's retry_count
(initialize to 0 if missing) and check against max_retries before re-queuing: if
retry_count >= max_retries, drop the message and log a warning/error; otherwise
increment retry_count, optionally record last_retry_time, then put the message
back on _outgoing; ensure any persistence or ack semantics used elsewhere are
respected when dropping vs re-queuing.

Comment on lines +469 to +472
self.logger.error(
"Message failed after %d retries, dropping",
max_retries,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use logging.exception for consistency.

This error block should use logging.exception instead of logging.error to automatically capture the exception traceback, consistent with other error handling in this file.

🔎 Proposed fix
-                        self.logger.error(
-                            "Message failed after %d retries, dropping",
-                            max_retries,
-                        )
+                        self.logger.exception(
+                            "Message failed after %d retries, dropping: %d",
+                            max_retries,
+                        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self.logger.error(
"Message failed after %d retries, dropping",
max_retries,
)
self.logger.exception(
"Message failed after %d retries, dropping",
max_retries,
)
🧰 Tools
🪛 Ruff (0.14.10)

469-472: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 Prompt for AI Agents
In src/pymax/interfaces.py around lines 469 to 472, the error logging uses
self.logger.error which does not capture the exception traceback; replace it
with self.logger.exception and pass the same formatted message and max_retries
so the exception traceback is logged consistently with other error handling in
this file.

Comment on lines +65 to +72
main_chunk_import = html.split("import(")[2].split(")")[0].strip("\"'")
main_chunk_url = f"https://web.max.ru{main_chunk_import}"
try:
main_chunk_code = requests.get(main_chunk_url, timeout=10).text
except requests.exceptions.RequestException as e:
return None

arr = main_chunk_code.split("\n")[0].split("[")[1].split("]")[0].split(",")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fragile HTML/JS parsing prone to IndexError.

The parsing logic uses hardcoded indices (split("import(")[2], split("[")[1]) which will raise IndexError if the page structure changes. Consider wrapping in try/except or using more robust parsing.

🔎 Proposed fix
+    @staticmethod
+    def get_current_web_version() -> str | None:
         try:
             html = requests.get("https://web.max.ru/", timeout=10).text
         except requests.RequestException:
             return None
 
-        main_chunk_import = html.split("import(")[2].split(")")[0].strip("\"'")
+        try:
+            main_chunk_import = html.split("import(")[2].split(")")[0].strip("\"'")
+        except IndexError:
+            return None
+
         main_chunk_url = f"https://web.max.ru{main_chunk_import}"
         try:
             main_chunk_code = requests.get(main_chunk_url, timeout=10).text
-        except requests.exceptions.RequestException as e:
+        except requests.RequestException:
             return None
 
-        arr = main_chunk_code.split("\n")[0].split("[")[1].split("]")[0].split(",")
+        try:
+            arr = main_chunk_code.split("\n")[0].split("[")[1].split("]")[0].split(",")
+        except IndexError:
+            return None

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.10)

69-69: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

🤖 Prompt for AI Agents
In src/pymax/utils.py around lines 65 to 72, the chain of hardcoded splits
(e.g., split("import(")[2], split("[")[1]) is fragile and will raise IndexError
if the HTML/JS shape changes; update the code to defensively parse by first
checking that each split yields the expected number of parts (or use a single
robust regex to extract the import(...) argument and the bracketed list), wrap
the parsing in a try/except that catches IndexError (and ValueError) in addition
to the existing RequestException, return None on failure, and optionally log the
parsing failure with context so callers can diagnose malformed pages.

…ocs, улучшение документации и добавление новых функций в релиз 1.2.4
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pymax/files.py (1)

12-23: BaseFile validation contradicts raw-only initialization intent.

The constructor requires either url or path (lines 19-23), but the read() method (lines 27-29) is designed to short-circuit when raw is provided. This means Photo(raw=b"...") will fail at validation, even though the read() method would handle it correctly.

If raw-only initialization should be supported:

🔎 Proposed fix
     def __init__(
         self, raw: bytes | None = None, *, url: str | None = None, path: str | None = None
     ) -> None:
         self.raw = raw
         self.url = url
         self.path = path

-        if self.url is None and self.path is None:
+        if self.raw is None and self.url is None and self.path is None:
             raise ValueError("Either url or path must be provided.")

         if self.url and self.path:
             raise ValueError("Only one of url or path must be provided.")
♻️ Duplicate comments (2)
src/pymax/interfaces.py (2)

448-455: Circuit breaker re-queues message without incrementing retry count.

When the circuit breaker activates, the message is re-queued (line 454) without incrementing retry_count. This could lead to indefinite re-queueing once the breaker resets.


469-472: Use logging.exception instead of logging.error.

This error block should use logging.exception to capture the exception traceback, consistent with other error handling in this file. Static analysis also flags this (TRY400).

🧹 Nitpick comments (3)
src/pymax/files.py (1)

54-71: Empty file_name fallback may cause downstream issues.

While the name parameter addition addresses the previous review concern about AttributeError, defaulting to an empty string (line 69) may cause problems with file upload APIs that expect meaningful filenames.

Consider using a sensible default like "photo" or requiring name when using raw bytes:

🔎 Proposed improvement
         elif name:
             self.file_name = name
         else:
-            self.file_name = ""
+            self.file_name = "photo"
src/pymax/interfaces.py (2)

338-345: Minor: Redundant exception object in logging.exception.

The exception object e is passed explicitly but logging.exception already captures it automatically. This pattern repeats at lines 344, 362, 371, and 386.

🔎 Proposed fix
             except Exception as e:
-                self.logger.exception("Error in on_reaction_change_handler: %s", e)
+                self.logger.exception("Error in on_reaction_change_handler")

504-546: Minor: Unused assigned variables.

  • Line 508: error is assigned via walrus operator but never used (only checked for truthiness)
  • Line 540: e is assigned but never used

These can be simplified:

🔎 Proposed fix
-            if error := raw_payload.get("error"):
+            if raw_payload.get("error"):
                 MixinsUtils.handle_error(data)
-        except Exception as e:
+        except Exception:
             self.logger.exception("Sync failed")
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b0413c and 193699e.

📒 Files selected for processing (8)
  • redocs/source/clients.rst
  • redocs/source/conf.py
  • redocs/source/index.rst
  • redocs/source/installation.rst
  • redocs/source/quickstart.rst
  • redocs/source/release_notes.rst
  • src/pymax/files.py
  • src/pymax/interfaces.py
🧰 Additional context used
🪛 Ruff (0.14.10)
src/pymax/interfaces.py

209-209: Do not catch blind exception: Exception

(BLE001)


251-251: Do not catch blind exception: Exception

(BLE001)


344-344: Redundant exception object included in logging.exception call

(TRY401)


362-362: Redundant exception object included in logging.exception call

(TRY401)


371-371: Redundant exception object included in logging.exception call

(TRY401)


386-386: Redundant exception object included in logging.exception call

(TRY401)


444-444: Do not catch blind exception: Exception

(BLE001)


469-472: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


508-508: Local variable error is assigned to but never used

(F841)


540-540: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

🔇 Additional comments (13)
redocs/source/installation.rst (1)

52-62: LGTM!

The repository URLs have been consistently updated to MaxApiTeam/PyMax.git in both installation examples (pip and uv methods).

src/pymax/files.py (1)

102-134: Video and File do not support raw-only initialization.

Unlike Photo, both Video (lines 112-113) and File (lines 131-132) raise ValueError if no path or url is provided, blocking raw-only initialization. This creates an inconsistent API where Photo supports raw bytes but Video/File do not.

If raw-only should be supported for all file types, consider adding a name parameter similar to Photo:

🔎 Proposed fix for Video
 class Video(BaseFile):
     def __init__(
-        self, raw: bytes | None = None, *, url: str | None = None, path: str | None = None
+        self, raw: bytes | None = None, *, url: str | None = None, path: str | None = None, name: str | None = None
     ) -> None:
         self.file_name: str = ""
         if path:
             self.file_name = Path(path).name
         elif url:
             self.file_name = Path(url).name
+        elif name:
+            self.file_name = name
+        else:
+            self.file_name = "video"

-        if not self.file_name:
+        if not self.file_name and raw is None:
             raise ValueError("Either url or path must be provided.")
         super().__init__(raw=raw, url=url, path=path)
src/pymax/interfaces.py (2)

35-108: LGTM!

The BaseClient class provides well-structured lifecycle management, safe task execution, and proper cleanup logic. The async context manager implementation is correct.


200-211: Catching bare Exception is intentional for ping resilience.

The broad exception catch at line 209 with exc_info=True logging is acceptable here since interactive ping failures shouldn't crash the connection loop.

redocs/source/conf.py (1)

9-9: LGTM!

The release version has been updated to "1.2.4", resolving the previous version mismatch with pyproject.toml.

redocs/source/index.rst (2)

38-49: LGTM!

The toctree now includes release notes, and the new features list accurately reflects the v1.2.4 additions (profile photo upload, group resolution by link, contact support, contact list management).


106-117: LGTM!

Repository references consistently updated to MaxApiTeam/PyMax for both Star History and Contributors badges.

redocs/source/quickstart.rst (2)

13-30: LGTM!

Clear and helpful client selection guidance. The comparison between MaxClient (WebSocket/QR) and SocketMaxClient (TCP/phone) helps users choose the right client for their use case.


62-62: LGTM!

The startup instruction correctly reflects the MaxClient flow which uses QR code authentication.

redocs/source/clients.rst (2)

4-31: LGTM!

Excellent comparison table clearly showing the differences between MaxClient and SocketMaxClient. The feature matrix helps users make informed decisions about which client to use.


237-252: LGTM!

Excellent workflow example demonstrating how to obtain a token via SocketMaxClient and reuse it with MaxClient for faster WebSocket connections.

redocs/source/release_notes.rst (2)

1-84: LGTM!

Comprehensive release notes for v1.2.4 documenting:

  • New raw bytes support in file classes
  • Automatic read-receipt notifications
  • session_name parameter
  • get_current_web_version() utility
  • Enhanced User-Agent generation with ua-generator
  • New read_message method

The changelog is well-organized and provides useful context for users upgrading.


88-143: LGTM!

The v1.2.3 release notes section documents new features (change_profile, resolve_group_by_link), new types (ContactAttach), and the new contacts property.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
redocs/source/clients.rst (1)

103-109: Photo placeholder example could be more concrete.

The change_profile() example uses photo=Photo(...) as a placeholder. While acceptable for documentation, consider providing a more explicit example of how to construct a Photo object (e.g., from file, from URL, etc.) to improve clarity for new users.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 193699e and 5054fd9.

📒 Files selected for processing (4)
  • README.md
  • mkdocs.yml
  • pyproject.toml
  • redocs/source/clients.rst
💤 Files with no reviewable changes (1)
  • mkdocs.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml
🔇 Additional comments (3)
redocs/source/clients.rst (3)

243-251: Token reuse workflow is well-documented.

The example clearly shows how to extract a token from SocketMaxClient and reuse it with MaxClient for faster WebSocket connection, which is a valuable pattern for users.


103-109: Missing import for Photo in change_profile example.

The example uses Photo(...) but doesn't show where it's imported from. Add an import statement to clarify: from pymax.types import Photo (or the correct module path).


203-207: Verify completeness of SocketMaxClient initialization examples.

The PR objectives mention a new session_name parameter. Consider adding this parameter to the SocketMaxClient examples (if it's a user-facing option) to ensure documentation completeness.

Also applies to: 218-224

Comment on lines +168 to 169
<a href="https://github.com/MaxApiTeam/PyMax/graphs/contributors">
<img src="https://contrib.rocks/image?repo=ink-developer/PyMax" />
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent repository URL migration in contributors section.

Line 168 was updated to reference MaxApiTeam/PyMax, but the image source on Line 169 still points to the old organization (ink-developer/PyMax). This mismatch will cause the contributors badge to render stale data.

Additionally, Line 157 (Star History badge) also contains references to the old repository (ink-developer/PyMax appears twice in the URL) and may need similar updates for consistency.

🔎 Proposed fix to complete the repository migration
- <a href="https://github.com/MaxApiTeam/PyMax/graphs/contributors">
-   <img src="https://contrib.rocks/image?repo=ink-developer/PyMax" />
+ <a href="https://github.com/MaxApiTeam/PyMax/graphs/contributors">
+   <img src="https://contrib.rocks/image?repo=MaxApiTeam/PyMax" />

Also update Line 157:

-[![Star History Chart](https://api.star-history.com/svg?repos=ink-developer/PyMax&type=date&legend=top-left)](https://www.star-history.com/#ink-developer/PyMax&type=date&legend=top-left)
+[![Star History Chart](https://api.star-history.com/svg?repos=MaxApiTeam/PyMax&type=date&legend=top-left)](https://www.star-history.com/#MaxApiTeam/PyMax&type=date&legend=top-left)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a href="https://github.com/MaxApiTeam/PyMax/graphs/contributors">
<img src="https://contrib.rocks/image?repo=ink-developer/PyMax" />
<a href="https://github.com/MaxApiTeam/PyMax/graphs/contributors">
<img src="https://contrib.rocks/image?repo=MaxApiTeam/PyMax" />
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

169-169: Images should have alternate text (alt text)

(MD045, no-alt-text)

🤖 Prompt for AI Agents
In README.md around lines 157 and 168-169, the contributors image and the Star
History badge still reference the old repository path ink-developer/PyMax while
other links use MaxApiTeam/PyMax; update the badge and image URLs to
consistently use MaxApiTeam/PyMax (replace all occurrences of
ink-developer/PyMax in those lines/URLs with MaxApiTeam/PyMax) so the
contributors image and star history reflect the migrated repository.

@ink-developer ink-developer merged commit e10c051 into main Dec 30, 2025
1 check passed
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