Switch server bridge to PyMax
This commit is contained in:
@@ -15,7 +15,7 @@ services:
|
|||||||
QMax__PairingCode: ${QMAX_PAIRING_CODE}
|
QMax__PairingCode: ${QMAX_PAIRING_CODE}
|
||||||
QMax__MaxPhoneNumber: ${QMAX_MAX_PHONE_NUMBER}
|
QMax__MaxPhoneNumber: ${QMAX_MAX_PHONE_NUMBER}
|
||||||
QMax__MaxMode: Worker
|
QMax__MaxMode: Worker
|
||||||
QMax__MaxWorkerBaseUrl: http://qmax-max-worker:3001
|
QMax__MaxWorkerBaseUrl: http://qmax-pymax-worker:3002
|
||||||
QMax__CorsAllowedOrigins: ${QMAX_CORS_ALLOWED_ORIGINS:-}
|
QMax__CorsAllowedOrigins: ${QMAX_CORS_ALLOWED_ORIGINS:-}
|
||||||
QMax__PushEnabled: ${QMAX_PUSH_ENABLED:-true}
|
QMax__PushEnabled: ${QMAX_PUSH_ENABLED:-true}
|
||||||
QMax__PushShowPreview: ${QMAX_PUSH_SHOW_PREVIEW:-true}
|
QMax__PushShowPreview: ${QMAX_PUSH_SHOW_PREVIEW:-true}
|
||||||
@@ -28,7 +28,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:18080:8080"
|
- "127.0.0.1:18080:8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
- qmax-max-worker
|
- qmax-pymax-worker
|
||||||
networks:
|
networks:
|
||||||
- qmax
|
- qmax
|
||||||
|
|
||||||
|
|||||||
+271
-23
@@ -8,6 +8,7 @@ import pathlib
|
|||||||
import stat
|
import stat
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import urllib.parse
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -84,6 +85,169 @@ def value_name(value: Any) -> str:
|
|||||||
return str(raw) if raw is not None else ""
|
return str(raw) if raw is not None else ""
|
||||||
|
|
||||||
|
|
||||||
|
def coerce_int(value: Any) -> int | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(str(value))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def first_text(*values: Any) -> str | None:
|
||||||
|
for value in values:
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
text = str(value).strip()
|
||||||
|
if text:
|
||||||
|
return text
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def unique_ints(values: list[int | None]) -> list[int]:
|
||||||
|
seen: set[int] = set()
|
||||||
|
result: list[int] = []
|
||||||
|
for value in values:
|
||||||
|
if value is None or value in seen:
|
||||||
|
continue
|
||||||
|
seen.add(value)
|
||||||
|
result.append(value)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def find_first_key(value: Any, keys: set[str]) -> Any:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
for key in keys:
|
||||||
|
if value.get(key):
|
||||||
|
return value[key]
|
||||||
|
for item in value.values():
|
||||||
|
found = find_first_key(item, keys)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
elif isinstance(value, list):
|
||||||
|
for item in value:
|
||||||
|
found = find_first_key(item, keys)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extension_from_url(url: str | None) -> str:
|
||||||
|
if not url:
|
||||||
|
return ""
|
||||||
|
parsed = urllib.parse.urlparse(url)
|
||||||
|
suffix = pathlib.Path(parsed.path).suffix.lower()
|
||||||
|
return suffix if 0 < len(suffix) <= 16 else ""
|
||||||
|
|
||||||
|
|
||||||
|
def get_me_user_id(client: Client) -> int | None:
|
||||||
|
me = getattr(client, "me", None)
|
||||||
|
data = dump_model(me)
|
||||||
|
contact = data.get("contact") if isinstance(data.get("contact"), dict) else {}
|
||||||
|
return coerce_int(contact.get("id") or data.get("id") or getattr(me, "id", None))
|
||||||
|
|
||||||
|
|
||||||
|
def chat_participant_ids(chat: Any) -> list[int]:
|
||||||
|
data = dump_model(chat)
|
||||||
|
ids: list[int | None] = [coerce_int(data.get("owner"))]
|
||||||
|
participants = data.get("participants")
|
||||||
|
if isinstance(participants, dict):
|
||||||
|
ids.extend(coerce_int(key) for key in participants.keys())
|
||||||
|
elif isinstance(participants, list):
|
||||||
|
for item in participants:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
ids.append(coerce_int(item.get("user_id") or item.get("id") or item.get("contact_id")))
|
||||||
|
else:
|
||||||
|
ids.append(coerce_int(item))
|
||||||
|
return unique_ints(ids)
|
||||||
|
|
||||||
|
|
||||||
|
def message_sender_ids(messages: list[Any]) -> list[int]:
|
||||||
|
return unique_ints([coerce_int(getattr(message, "sender", None)) for message in messages])
|
||||||
|
|
||||||
|
|
||||||
|
async def build_user_map(client: Client, chats: list[Any], messages: list[Any] | None = None) -> dict[str, Any]:
|
||||||
|
ids: list[int | None] = []
|
||||||
|
for chat in chats:
|
||||||
|
ids.extend(chat_participant_ids(chat))
|
||||||
|
if messages:
|
||||||
|
ids.extend(message_sender_ids(messages))
|
||||||
|
|
||||||
|
user_ids = [value for value in unique_ints(ids) if value > 0]
|
||||||
|
users: dict[str, Any] = {}
|
||||||
|
for offset in range(0, len(user_ids), 100):
|
||||||
|
batch = user_ids[offset:offset + 100]
|
||||||
|
if not batch:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
fetched = await client.get_users(batch)
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
fetched = await client.fetch_users(batch)
|
||||||
|
for user in fetched or []:
|
||||||
|
user_id = coerce_int(dump_model(user).get("id") or getattr(user, "id", None))
|
||||||
|
if user_id is not None:
|
||||||
|
users[str(user_id)] = user
|
||||||
|
return users
|
||||||
|
|
||||||
|
|
||||||
|
def user_display_name(user: Any) -> str | None:
|
||||||
|
data = dump_model(user)
|
||||||
|
names = data.get("names") if isinstance(data.get("names"), list) else []
|
||||||
|
for preferred in ("CUSTOM", "ONEME"):
|
||||||
|
for name in names:
|
||||||
|
if not isinstance(name, dict):
|
||||||
|
continue
|
||||||
|
if str(name.get("type") or "").upper() == preferred:
|
||||||
|
value = first_text(name.get("name"), " ".join(
|
||||||
|
part for part in [str(name.get("first_name") or "").strip(), str(name.get("last_name") or "").strip()]
|
||||||
|
if part
|
||||||
|
))
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
for name in names:
|
||||||
|
if isinstance(name, dict):
|
||||||
|
value = first_text(name.get("name"), name.get("first_name"))
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
value = first_text(
|
||||||
|
data.get("name"),
|
||||||
|
" ".join(part for part in [str(data.get("first_name") or "").strip(), str(data.get("last_name") or "").strip()] if part),
|
||||||
|
data.get("phone"),
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def user_avatar_url(user: Any) -> str | None:
|
||||||
|
data = dump_model(user)
|
||||||
|
return first_text(data.get("base_url"), data.get("base_raw_url"))
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
continue
|
||||||
|
user = user_map.get(str(user_id))
|
||||||
|
if user is not None:
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_chat_title(chat: Any, user_map: dict[str, Any], me_id: int | None) -> str:
|
||||||
|
title = first_text(getattr(chat, "title", None))
|
||||||
|
if title:
|
||||||
|
return title
|
||||||
|
other = chat_other_user(chat, user_map, me_id)
|
||||||
|
return user_display_name(other) or f"MAX {chat.id}"
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_chat_avatar(chat: Any, user_map: dict[str, Any], me_id: int | None) -> str | None:
|
||||||
|
chat_avatar = first_text(getattr(chat, "base_icon_url", None), getattr(chat, "base_raw_icon_url", None))
|
||||||
|
if chat_avatar:
|
||||||
|
return chat_avatar
|
||||||
|
other = chat_other_user(chat, user_map, me_id)
|
||||||
|
return user_avatar_url(other)
|
||||||
|
|
||||||
|
|
||||||
class DeferredCodeProvider:
|
class DeferredCodeProvider:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._future: asyncio.Future[str] | None = None
|
self._future: asyncio.Future[str] | None = None
|
||||||
@@ -288,24 +452,83 @@ def normalize_attachment_kind(att: Any) -> str:
|
|||||||
return raw_type or class_name or "attachment"
|
return raw_type or class_name or "attachment"
|
||||||
|
|
||||||
|
|
||||||
|
def attachment_preview(attaches: list[Any]) -> str | None:
|
||||||
|
if not attaches:
|
||||||
|
return None
|
||||||
|
kinds = [normalize_attachment_kind(att) for att in attaches]
|
||||||
|
if any(kind == "photo" for kind in kinds):
|
||||||
|
return "\u0424\u043e\u0442\u043e"
|
||||||
|
if any(kind == "video" for kind in kinds):
|
||||||
|
return "\u0412\u0438\u0434\u0435\u043e"
|
||||||
|
if any(kind == "audio" for kind in kinds):
|
||||||
|
return "\u0413\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435"
|
||||||
|
if any(kind == "sticker" for kind in kinds):
|
||||||
|
return "\u0421\u0442\u0438\u043a\u0435\u0440"
|
||||||
|
if any(kind == "contact" for kind in kinds):
|
||||||
|
return "\u041a\u043e\u043d\u0442\u0430\u043a\u0442"
|
||||||
|
return "\u0424\u0430\u0439\u043b"
|
||||||
|
|
||||||
|
|
||||||
|
def attachment_api_kind(kind: str) -> str:
|
||||||
|
return {
|
||||||
|
"photo": "Image",
|
||||||
|
"video": "Video",
|
||||||
|
"audio": "VoiceNote",
|
||||||
|
"sticker": "Sticker",
|
||||||
|
"contact": "Contact",
|
||||||
|
"file": "File",
|
||||||
|
}.get(kind, "File")
|
||||||
|
|
||||||
|
|
||||||
|
def contact_to_vcard(data: dict[str, Any]) -> str:
|
||||||
|
first_name = first_text(data.get("first_name"), data.get("firstName"))
|
||||||
|
last_name = first_text(data.get("last_name"), data.get("lastName"))
|
||||||
|
full_name = first_text(data.get("name"), data.get("full_name"), " ".join(
|
||||||
|
part for part in [first_name or "", last_name or ""] if part
|
||||||
|
))
|
||||||
|
phone = first_text(data.get("phone"), data.get("phone_number"), data.get("phoneNumber"))
|
||||||
|
lines = ["BEGIN:VCARD", "VERSION:3.0"]
|
||||||
|
if full_name:
|
||||||
|
lines.append(f"FN:{full_name}")
|
||||||
|
if first_name or last_name:
|
||||||
|
lines.append(f"N:{last_name or ''};{first_name or ''};;;")
|
||||||
|
if phone:
|
||||||
|
lines.append(f"TEL;TYPE=CELL:{phone}")
|
||||||
|
lines.append("END:VCARD")
|
||||||
|
return "\r\n".join(lines) + "\r\n"
|
||||||
|
|
||||||
|
|
||||||
async def normalize_attachment(client: Client, message: Any, att: Any, sort_order: int) -> dict[str, Any]:
|
async def normalize_attachment(client: Client, message: Any, att: Any, sort_order: int) -> dict[str, Any]:
|
||||||
data = dump_model(att)
|
data = dump_model(att)
|
||||||
kind = normalize_attachment_kind(att)
|
kind = normalize_attachment_kind(att)
|
||||||
|
remote_url = first_text(
|
||||||
|
data.get("base_url"),
|
||||||
|
data.get("url"),
|
||||||
|
data.get("lottie_url"),
|
||||||
|
data.get("thumbnail"),
|
||||||
|
data.get("preview_url"),
|
||||||
|
find_first_key(data, {"base_url", "url", "lottie_url", "thumbnail", "preview_url"}),
|
||||||
|
)
|
||||||
external_id = (
|
external_id = (
|
||||||
data.get("photo_id")
|
data.get("photo_id")
|
||||||
or data.get("video_id")
|
or data.get("video_id")
|
||||||
or data.get("file_id")
|
or data.get("file_id")
|
||||||
|
or data.get("sticker_id")
|
||||||
|
or data.get("emoji_id")
|
||||||
|
or data.get("contact_id")
|
||||||
or data.get("id")
|
or data.get("id")
|
||||||
or data.get("token")
|
or data.get("token")
|
||||||
or f"{clean_id(getattr(message, 'id', None))}:{sort_order}"
|
or f"{clean_id(getattr(message, 'id', None))}:{sort_order}"
|
||||||
)
|
)
|
||||||
|
fallback_extension = extension_from_url(remote_url)
|
||||||
file_name = (
|
file_name = (
|
||||||
data.get("name")
|
data.get("name")
|
||||||
or data.get("filename")
|
or data.get("filename")
|
||||||
or data.get("file_name")
|
or data.get("file_name")
|
||||||
or f"{kind}-{external_id}"
|
or f"{kind}-{external_id}{fallback_extension}"
|
||||||
)
|
)
|
||||||
remote_url = data.get("base_url") or data.get("url") or data.get("thumbnail")
|
if kind == "contact" and not pathlib.Path(str(file_name)).suffix:
|
||||||
|
file_name = f"{file_name}.vcf"
|
||||||
content_type = None
|
content_type = None
|
||||||
file_size = data.get("size") or data.get("file_size") or data.get("fileSize")
|
file_size = data.get("size") or data.get("file_size") or data.get("fileSize")
|
||||||
|
|
||||||
@@ -329,12 +552,16 @@ async def normalize_attachment(client: Client, message: Any, att: Any, sort_orde
|
|||||||
content_type = "video/mp4"
|
content_type = "video/mp4"
|
||||||
elif kind == "audio":
|
elif kind == "audio":
|
||||||
content_type = "audio/ogg"
|
content_type = "audio/ogg"
|
||||||
|
elif kind == "sticker":
|
||||||
|
content_type = mimetypes.guess_type(str(file_name))[0] or mimetypes.guess_type(remote_url or "")[0] or "image/webp"
|
||||||
|
elif kind == "contact":
|
||||||
|
content_type = "text/vcard; charset=utf-8"
|
||||||
elif file_name:
|
elif file_name:
|
||||||
content_type = mimetypes.guess_type(str(file_name))[0]
|
content_type = mimetypes.guess_type(str(file_name))[0]
|
||||||
|
|
||||||
text_content = None
|
text_content = None
|
||||||
if kind in {"sticker", "contact"}:
|
if kind == "contact":
|
||||||
text_content = str(data)
|
text_content = contact_to_vcard(data)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"externalId": clean_id(external_id),
|
"externalId": clean_id(external_id),
|
||||||
@@ -342,33 +569,37 @@ async def normalize_attachment(client: Client, message: Any, att: Any, sort_orde
|
|||||||
"contentType": content_type,
|
"contentType": content_type,
|
||||||
"fileSizeBytes": file_size,
|
"fileSizeBytes": file_size,
|
||||||
"remoteUrl": remote_url,
|
"remoteUrl": remote_url,
|
||||||
"kind": kind,
|
"kind": attachment_api_kind(kind),
|
||||||
"sortOrder": sort_order,
|
"sortOrder": sort_order,
|
||||||
"textContent": text_content,
|
"textContent": text_content,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def normalize_message(client: Client, chat: Any, message: Any) -> dict[str, Any]:
|
async def normalize_message(
|
||||||
|
client: Client,
|
||||||
|
chat: Any,
|
||||||
|
message: Any,
|
||||||
|
user_map: dict[str, Any] | None = None,
|
||||||
|
me_id: int | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
attaches = list(getattr(message, "attaches", None) or [])
|
attaches = list(getattr(message, "attaches", None) or [])
|
||||||
attachments = [
|
attachments = [
|
||||||
await normalize_attachment(client, message, att, index)
|
await normalize_attachment(client, message, att, index)
|
||||||
for index, att in enumerate(attaches)
|
for index, att in enumerate(attaches)
|
||||||
]
|
]
|
||||||
sender = getattr(message, "sender", None)
|
sender = getattr(message, "sender", None)
|
||||||
me_id = None
|
sender_id = coerce_int(sender)
|
||||||
me = getattr(client, "me", None)
|
if me_id is None:
|
||||||
contact = getattr(me, "contact", None)
|
me_id = get_me_user_id(client)
|
||||||
if contact is not None:
|
is_outgoing = sender_id is not None and me_id is not None and sender_id == me_id
|
||||||
me_id = getattr(contact, "id", None)
|
|
||||||
if me_id is None and me is not None:
|
|
||||||
me_id = getattr(me, "id", None)
|
|
||||||
is_outgoing = sender is not None and me_id is not None and str(sender) == str(me_id)
|
|
||||||
text = getattr(message, "text", None) or attachment_preview(attaches)
|
text = getattr(message, "text", None) or attachment_preview(attaches)
|
||||||
status = value_name(getattr(message, "status", None)).lower() or None
|
status = value_name(getattr(message, "status", None)).lower() or None
|
||||||
|
user = (user_map or {}).get(str(sender_id)) if sender_id is not None else None
|
||||||
|
sender_name = "You" if is_outgoing else user_display_name(user)
|
||||||
return {
|
return {
|
||||||
"externalId": clean_id(getattr(message, "id", None)),
|
"externalId": clean_id(getattr(message, "id", None)),
|
||||||
"senderExternalId": clean_id(sender) or None,
|
"senderExternalId": clean_id(sender) or None,
|
||||||
"senderName": None,
|
"senderName": sender_name,
|
||||||
"isOutgoing": is_outgoing,
|
"isOutgoing": is_outgoing,
|
||||||
"text": text,
|
"text": text,
|
||||||
"sentAt": millis_to_iso(getattr(message, "time", None)),
|
"sentAt": millis_to_iso(getattr(message, "time", None)),
|
||||||
@@ -377,14 +608,27 @@ async def normalize_message(client: Client, chat: Any, message: Any) -> dict[str
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def normalize_chat_update(client: Client, chat: Any, include_history: bool = False) -> dict[str, Any]:
|
async def normalize_chat_update(
|
||||||
|
client: Client,
|
||||||
|
chat: Any,
|
||||||
|
include_history: bool = False,
|
||||||
|
user_map: dict[str, Any] | None = None,
|
||||||
|
me_id: int | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
last_message = getattr(chat, "last_message", None)
|
last_message = getattr(chat, "last_message", None)
|
||||||
|
raw_messages: list[Any] = []
|
||||||
messages: list[dict[str, Any]] = []
|
messages: list[dict[str, Any]] = []
|
||||||
if include_history:
|
if include_history:
|
||||||
history = await client.fetch_history(chat_id=int(chat.id), backward=HISTORY_LIMIT)
|
history = await client.fetch_history(chat_id=int(chat.id), backward=HISTORY_LIMIT)
|
||||||
messages = [await normalize_message(client, chat, m) for m in (history or [])]
|
raw_messages = list(history or [])
|
||||||
elif last_message is not None:
|
elif last_message is not None:
|
||||||
messages = [await normalize_message(client, chat, last_message)]
|
raw_messages = [last_message]
|
||||||
|
|
||||||
|
if me_id is None:
|
||||||
|
me_id = get_me_user_id(client)
|
||||||
|
if user_map is None:
|
||||||
|
user_map = await build_user_map(client, [chat], raw_messages)
|
||||||
|
messages = [await normalize_message(client, chat, m, user_map, me_id) for m in raw_messages]
|
||||||
|
|
||||||
last_preview = None
|
last_preview = None
|
||||||
last_at = getattr(chat, "last_event_time", None)
|
last_at = getattr(chat, "last_event_time", None)
|
||||||
@@ -395,11 +639,10 @@ async def normalize_chat_update(client: Client, chat: Any, include_history: bool
|
|||||||
last_at = int(datetime.fromisoformat(last["sentAt"]).timestamp() * 1000)
|
last_at = int(datetime.fromisoformat(last["sentAt"]).timestamp() * 1000)
|
||||||
last_is_outgoing = last.get("isOutgoing")
|
last_is_outgoing = last.get("isOutgoing")
|
||||||
|
|
||||||
title = getattr(chat, "title", None) or f"MAX {chat.id}"
|
|
||||||
return {
|
return {
|
||||||
"externalId": clean_id(getattr(chat, "id", None)),
|
"externalId": clean_id(getattr(chat, "id", None)),
|
||||||
"title": title,
|
"title": resolve_chat_title(chat, user_map, me_id),
|
||||||
"avatarUrl": getattr(chat, "base_icon_url", None) or getattr(chat, "base_raw_icon_url", None),
|
"avatarUrl": resolve_chat_avatar(chat, user_map, me_id),
|
||||||
"updatedAt": millis_to_iso(last_at),
|
"updatedAt": millis_to_iso(last_at),
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"lastMessagePreview": last_preview,
|
"lastMessagePreview": last_preview,
|
||||||
@@ -532,7 +775,12 @@ async def snapshot(_request: web.Request) -> web.Response:
|
|||||||
async def updates(_request: web.Request) -> web.Response:
|
async def updates(_request: web.Request) -> web.Response:
|
||||||
client = await runtime.get_client()
|
client = await runtime.get_client()
|
||||||
chats = await fetch_chat_pages(client, CHAT_FETCH_LIMIT)
|
chats = await fetch_chat_pages(client, CHAT_FETCH_LIMIT)
|
||||||
result = [await normalize_chat_update(client, chat, include_history=False) for chat in chats]
|
me_id = get_me_user_id(client)
|
||||||
|
user_map = await build_user_map(client, chats)
|
||||||
|
result = [
|
||||||
|
await normalize_chat_update(client, chat, include_history=False, user_map=user_map, me_id=me_id)
|
||||||
|
for chat in chats
|
||||||
|
]
|
||||||
return json_response(result)
|
return json_response(result)
|
||||||
|
|
||||||
|
|
||||||
@@ -598,7 +846,7 @@ async def media_fetch(request: web.Request) -> web.StreamResponse:
|
|||||||
body = await response.read()
|
body = await response.read()
|
||||||
return web.Response(
|
return web.Response(
|
||||||
body=body,
|
body=body,
|
||||||
content_type=response.headers.get("Content-Type", "application/octet-stream"),
|
headers={"Content-Type": response.headers.get("Content-Type", "application/octet-stream")},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public sealed class QMaxOptions
|
|||||||
public string PairingCode { get; set; } = "";
|
public string PairingCode { get; set; } = "";
|
||||||
public string MaxPhoneNumber { get; set; } = "";
|
public string MaxPhoneNumber { get; set; } = "";
|
||||||
public string MaxMode { get; set; } = "Worker";
|
public string MaxMode { get; set; } = "Worker";
|
||||||
public string MaxWorkerBaseUrl { get; set; } = "http://qmax-max-worker:3001";
|
public string MaxWorkerBaseUrl { get; set; } = "http://qmax-pymax-worker:3002";
|
||||||
public string MaxUserDataPath { get; set; } = "data/max-profile";
|
public string MaxUserDataPath { get; set; } = "data/max-profile";
|
||||||
public bool MaxHeadless { get; set; } = true;
|
public bool MaxHeadless { get; set; } = true;
|
||||||
public int MaxPollIntervalSeconds { get; set; } = 6;
|
public int MaxPollIntervalSeconds { get; set; } = 6;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"PairingCode": "",
|
"PairingCode": "",
|
||||||
"MaxPhoneNumber": "",
|
"MaxPhoneNumber": "",
|
||||||
"MaxMode": "Worker",
|
"MaxMode": "Worker",
|
||||||
"MaxWorkerBaseUrl": "http://qmax-max-worker:3001",
|
"MaxWorkerBaseUrl": "http://qmax-pymax-worker:3002",
|
||||||
"MaxUserDataPath": "data/max-profile",
|
"MaxUserDataPath": "data/max-profile",
|
||||||
"MaxHeadless": true,
|
"MaxHeadless": true,
|
||||||
"MaxPollIntervalSeconds": 6,
|
"MaxPollIntervalSeconds": 6,
|
||||||
|
|||||||
Reference in New Issue
Block a user