Add MAX contacts and direct chat creation
This commit is contained in:
@@ -17,7 +17,7 @@ from typing import Any
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import web
|
||||
from pymax import Client, File, Photo, Video
|
||||
from pymax import Client, ExtraConfig, File, Photo, SyncOverrides, Video
|
||||
|
||||
|
||||
PORT = int(os.environ.get("PORT", "3002"))
|
||||
@@ -253,6 +253,21 @@ def user_avatar_url(user: Any) -> str | None:
|
||||
return first_text(data.get("base_url"), data.get("base_raw_url"))
|
||||
|
||||
|
||||
def contact_result(contact: Any, me_id: int, external_chat_id: Any | None = None) -> dict[str, Any] | None:
|
||||
data = dump_model(contact)
|
||||
contact_id = coerce_int(data.get("id") or getattr(contact, "id", None))
|
||||
if contact_id is None or contact_id <= 0 or contact_id == me_id:
|
||||
return None
|
||||
return {
|
||||
"userId": clean_id(contact_id),
|
||||
"externalChatId": clean_id(external_chat_id if external_chat_id is not None else contact_id ^ me_id),
|
||||
"displayName": user_display_name(contact) or f"MAX {contact_id}",
|
||||
"avatarUrl": user_avatar_url(contact),
|
||||
"phoneNumber": first_text(data.get("phone")),
|
||||
"status": first_text(data.get("status"), data.get("description")),
|
||||
}
|
||||
|
||||
|
||||
def chat_other_user(chat: Any, user_map: dict[str, Any], me_id: int | None) -> Any | None:
|
||||
for user_id in chat_participant_ids(chat):
|
||||
if me_id is not None and user_id == me_id:
|
||||
@@ -391,6 +406,9 @@ class PyMaxRuntime:
|
||||
session_name=SESSION_NAME,
|
||||
sms_code_provider=self.code_provider,
|
||||
password_provider=self.password_provider,
|
||||
extra_config=ExtraConfig(
|
||||
sync=SyncOverrides(chats_sync=-1, contacts_sync=-1),
|
||||
),
|
||||
)
|
||||
|
||||
@client.on_start()
|
||||
@@ -505,6 +523,10 @@ def normalize_chat_kind(chat: Any) -> str:
|
||||
return "MaxDialog"
|
||||
|
||||
|
||||
def is_direct_dialog(chat: Any) -> bool:
|
||||
return value_name(getattr(chat, "type", "")).upper() == "DIALOG"
|
||||
|
||||
|
||||
def normalize_attachment_kind(att: Any) -> str:
|
||||
data = dump_model(att)
|
||||
raw_type = value_name(data.get("type") or data.get("_type") or getattr(att, "type", "")).lower()
|
||||
@@ -729,19 +751,35 @@ async def normalize_chat_update(
|
||||
|
||||
async def fetch_chat_pages(client: Client, limit: int) -> list[Any]:
|
||||
chats: list[Any] = []
|
||||
seen_chat_ids: set[str] = set()
|
||||
seen_markers: set[int] = set()
|
||||
marker: int | None = None
|
||||
while len(chats) < limit:
|
||||
page = await client.fetch_chats(marker=marker)
|
||||
page = page or []
|
||||
if not page:
|
||||
break
|
||||
chats.extend(page)
|
||||
|
||||
added = 0
|
||||
for chat in page:
|
||||
chat_id = clean_id(getattr(chat, "id", None))
|
||||
if not chat_id or chat_id in seen_chat_ids:
|
||||
continue
|
||||
seen_chat_ids.add(chat_id)
|
||||
chats.append(chat)
|
||||
added += 1
|
||||
if len(chats) >= limit:
|
||||
break
|
||||
|
||||
times = [getattr(chat, "last_event_time", None) for chat in page if getattr(chat, "last_event_time", None)]
|
||||
if not times:
|
||||
if not times or added == 0:
|
||||
break
|
||||
marker = min(times) - 1
|
||||
if len(page) < 50:
|
||||
|
||||
next_marker = min(times) - 1
|
||||
if next_marker in seen_markers or (marker is not None and next_marker >= marker):
|
||||
break
|
||||
seen_markers.add(next_marker)
|
||||
marker = next_marker
|
||||
return chats[:limit]
|
||||
|
||||
|
||||
@@ -891,6 +929,33 @@ async def updates(_request: web.Request) -> web.Response:
|
||||
return json_response(result)
|
||||
|
||||
|
||||
@route_errors
|
||||
async def contacts(_request: web.Request) -> web.Response:
|
||||
client = await runtime.get_client()
|
||||
me_id = get_me_user_id(client)
|
||||
if me_id is None:
|
||||
return json_response({"success": False, "error": "MAX profile has no user id."}, status=503)
|
||||
|
||||
by_user_id: dict[str, dict[str, Any]] = {}
|
||||
for contact in client.contacts:
|
||||
normalized = contact_result(contact, me_id) if contact is not None else None
|
||||
if normalized is not None:
|
||||
by_user_id[normalized["userId"]] = normalized
|
||||
|
||||
chats = await fetch_chat_pages(client, CHAT_FETCH_LIMIT)
|
||||
direct_chats = [chat for chat in chats if is_direct_dialog(chat)]
|
||||
user_map = await build_user_map(client, direct_chats)
|
||||
for chat in direct_chats:
|
||||
contact = chat_other_user(chat, user_map, me_id)
|
||||
normalized = contact_result(contact, me_id, getattr(chat, "id", None)) if contact is not None else None
|
||||
if normalized is not None:
|
||||
by_user_id.setdefault(normalized["userId"], normalized)
|
||||
|
||||
result = list(by_user_id.values())
|
||||
result.sort(key=lambda item: item["displayName"].casefold())
|
||||
return json_response(result)
|
||||
|
||||
|
||||
@route_errors
|
||||
async def chat_history(request: web.Request) -> web.Response:
|
||||
data = await read_json(request)
|
||||
@@ -1017,6 +1082,7 @@ def create_app() -> web.Application:
|
||||
app.router.add_post("/login/code", login_code)
|
||||
app.router.add_get("/snapshot", snapshot)
|
||||
app.router.add_get("/updates", updates)
|
||||
app.router.add_get("/contacts", contacts)
|
||||
app.router.add_post("/chat/history", chat_history)
|
||||
app.router.add_post("/chat/resolve-url", resolve_chat_url)
|
||||
app.router.add_post("/channels/search", channels_search)
|
||||
|
||||
Reference in New Issue
Block a user