FastRPC-binary-protocol-specification
Other links
# mapy_frpc_client.py
# Simple working FRPC client for Mapy.cz API
import requests
from typing import Dict, Optional
# from config_pro import MAPY_COOKIES, DEFAULT_FOLDER_ID
from config import MAPY_COOKIES, DEFAULT_FOLDER_ID
class MapyFRPCClient:
def __init__(
self,
cookies: Dict[str, str],
base_url: str = "https://pro.mapy.com/mapybox-ng/",
):
self.base_url = base_url
self.cookies = cookies
self.headers = {
"accept": "application/x-frpc",
"content-type": "application/x-frpc",
"origin": "https://mapy.com",
"referer": "https://mapy.com/",
"user-agent": "Mozilla/5.0",
"x-correlation-id": "python-client-123",
}
def send_frpc_payload(self, payload: bytes) -> Optional[Dict[str, str]]:
"""Send FRPC payload and parse response."""
try:
response = requests.post(
self.base_url, headers=self.headers, cookies=self.cookies, data=payload
)
if response.ok:
return self._parse_response(response.content)
else:
return {
"error": response.status_code,
"raw": response.content.hex(),
}
except Exception as e:
return {"exception": str(e)}
def _parse_response(self, content: bytes) -> Dict[str, str]:
"""Parse FRPC response to extract status, message, and IDs."""
result = {}
try:
# Extract status if present
if b"status" in content:
status_idx = content.index(b"status") + len("status")
if status_idx < len(content):
result["status"] = content[status_idx]
# Extract statusMessage if present
if b"statusMessage" in content:
start = content.index(b"statusMessage") + len("statusMessage")
# Skip control bytes
while start < len(content) and content[start] < 32:
start += 1
# Find end of message
end = start
while (
end < len(content)
and content[end] >= 32
and content[end] != ord("%")
):
end += 1
if end > start:
msg = content[start:end].decode("utf-8", errors="ignore")
result["message"] = msg.strip()
# Look for folder/point ID in response
if b"id" in content:
# Find potential ID locations
id_idx = content.find(b"id")
while id_idx != -1:
# Try to extract ID after this position
search_start = id_idx + 2
# Look for 24-character hex strings (typical IDs)
end_search = min(search_start + 50, len(content))
for i in range(search_start, end_search):
candidate = content[i : i + 24]
if len(candidate) == 24 and candidate.isalnum():
result["id"] = candidate.decode("ascii")
break
id_idx = content.find(b"id", id_idx + 1)
except Exception as e:
result["parse_error"] = str(e)
return result if result else {"raw": content.hex()}
def create_point(
self,
title: str,
usertitle: str,
lat: float,
lon: float,
subtitle: str = "",
parent_id: str = "",
comment: str = "",
) -> Optional[Dict[str, str]]:
"""Create a point using the exact working binary payload format."""
import struct
def float64_bytes(f):
return struct.pack("<d", f)
# This replicates the exact working curl payload format
# Based on: $'Ê\u0011\u0002\u0001h\u000blike.createP\u0007\u0004type \u0005point\u0005title \u000fPozice na mapÄ\u009b\u000bdescription \u0000\u0004markP\u0002\u0003lat\u0018*±åk¶\u0087H@\u0003lon\u0018 z\u001dx\u0005Å0@\u0009usertitle \u0009SomePoint\u0007visited\u0010\u0009parent_id \uA4fdsfsdfdsf....'
payload = (
b"\xca\x11\x02\x01h\x0blike.createP\x07" # Header + method + 7 params
+ b"\x04type " # Field name "type" (4 chars) + space
+ b"\x05point" # Value "point" (5 chars)
+ b"\x05title " # Field name "title" (5 chars) + space
+ bytes([len(title)])
+ title.encode("utf-8") # Title
+ b"\x0bdescription " # Field name "description" (11 chars) + space
+ (
bytes([len(comment)]) + comment.encode("utf-8")
if comment
else b"\x00"
) # Comment or empty
+ b"\x04markP\x02" # Field name "mark" + struct with 2 members
+ b"\x03lat" # Field name "lat" (3 chars)
+ b"\x18"
+ float64_bytes(lat) # Double type + lat value
+ b"\x03lon" # Field name "lon" (3 chars)
+ b"\x18"
+ float64_bytes(lon) # Double type + lon value
+ b"\tusertitle " # Field name "usertitle" (9 chars = \t) + space
+ bytes([len(usertitle)])
+ usertitle.encode("utf-8") # User title
+ b"\x07visited" # Field name "visited" (7 chars)
+ b"\x10" # Some visited value
+ b"\tparent_id " # Field name "parent_id" (9 chars = \t) + space
+ bytes([len(parent_id)])
+ parent_id.encode("utf-8") # Parent ID
)
return self.send_frpc_payload(payload)
# Example usage:
if __name__ == "__main__":
client = MapyFRPCClient(cookies=MAPY_COOKIES)
# Use existing folder ID
existing_folder_id = DEFAULT_FOLDER_ID
print("Testing point creation in existing folder...")
point_result = client.create_point(
title="Python Test Point",
usertitle="TestPoint",
lat=49.1951,
lon=16.6068,
parent_id=existing_folder_id,
comment="This is a test comment",
)
print("Point result:", point_result)
# Test with different coordinates
print("\nCreating another point...")
point_result2 = client.create_point(
title="Second Python Point",
usertitle="SecondPoint",
lat=50.0755,
lon=14.4378, # Prague coordinates
parent_id=existing_folder_id,
comment="Prague location",
)
print("Point result2:", point_result2)config.py
# config.py
MAPY_COOKIES = {
"ds": "123fdsfnjk32...",
}
# Default folder for operations - your existing folder
DEFAULT_FOLDER_ID = "123fdsfnjk32..."
# Parent folder ID from your curl command (seems to be a different folder)
PARENT_FOLDER_ID = "123fdsfnjk32..."