docker/anki
2023-07-30 09:37:30 +08:00
..
anki-env upload docker 2023-07-30 09:37:30 +08:00
anki.ipynb upload docker 2023-07-30 09:37:30 +08:00
Dockerfile upload docker 2023-07-30 09:37:30 +08:00
readme.ipynb upload docker 2023-07-30 09:37:30 +08:00

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "46c5ef50",
   "metadata": {},
   "source": [
    "# anki sync文档\n",
    "## 数据包\n",
    "### http\n",
    "```python\n",
    "same = {\n",
    "    'SERVER_PROTOCOL': 'HTTP/1.0',  'REQUEST_METHOD': 'POST', 'PATH_INFO': '/msync/begin', 'QUERY_STRING': '', \n",
    "}\n",
    "```\n",
    "### android 包\n",
    "```python\n",
    "env = { 'CONTENT_LENGTH': '449','CONTENT_TYPE': 'multipart/form-data; boundary=Anki-sync-boundary', 'HTTP_ACCEPT_ENCODING': 'gzip', 'HTTP_USER_AGENT': 'AnkiDroid-2.15.6'}\n",
    "```\n",
    "### pc包\n",
    "```python\n",
    "b'{\"v\":\"anki,2.1.64 (581f82c5),win:10\"}'\n",
    "```\n",
    "```python\n",
    "env = { 'CONTENT_LENGTH': '46', 'CONTENT_TYPE': 'application/octet-stream',  'HTTP_ANKI_SYNC': '{\"v\":11,\"k\":\"a8b327a1e7ccbf704c4a60a23a8c4a57\",\"c\":\"2.1.64,581f82c5,windows\",\"s\":\"cJcynh\"}' }\n",
    "```\n",
    "\n",
    "## PC数据\n",
    "### 数据协议\n",
    "- 输入\n",
    "    - path is ::/msync/begin body is::b'(\\xb5/\\xfd\\x00X)\\x01\\x00{\"v\":\"anki,2.1.64 (581f82c5),win:10\"}'\n",
    "    - path is ::/sync/meta body is::b'(\\xb5/\\xfd\\x00Xi\\x01\\x00{\"v\":11,\"cv\":\"anki,2.1.64 (581f82c5),win:10\"}'\n",
    "    - path is ::/sync/hostKey body is::b'(\\xb5/\\xfd\\x00X\\xc9\\x00\\x00{\"u\":\"anki\",\"p\":\"ouczbs\"}'\n",
    "- 输出\n",
    "    - 不加密,不压缩\n",
    "### 流式解析\n",
    "- 在数据较大时,使用结果不太对\n",
    "```python\n",
    "import pyzstd\n",
    "data = b'{\"v\":\"anki,2.1.64 (581f82c5),win:10\"}'\n",
    "encoder = pyzstd.ZstdCompressor()\n",
    "encoder.compress(data)\n",
    "body = encoder.flush()\n",
    "print(body)\n",
    "decoder = pyzstd.ZstdDecompressor()\n",
    "data = decoder.decompress(body)\n",
    "print(data)\n",
    "```\n",
    "### 快速解析\n",
    "```python\n",
    "import pyzstd\n",
    "data = b'(\\xb5/\\xfd\\x00X)\\x01\\x00{\"v\":\"anki,2.1.64 (581f82c5),win:10\"}'\n",
    "# with open(\"conf/data.txt\",\"wb\")as f:\n",
    "#     f.write(data)\n",
    "with open(\"data.txt\",\"rb\")as f:\n",
    "    data = f.read()\n",
    "print(len(data))\n",
    "body = pyzstd.compress(data)\n",
    "print(len(body))\n",
    "data = pyzstd.decompress(body)\n",
    "print(len(data))\n",
    "```\n",
    "## Android 数据\n",
    "### 数据协议\n",
    "- 输入\n",
    "```python\n",
    "data = b'--Anki-sync-boundary\\r\\nContent-Disposition: form-data; name=\"k\"\\r\\n\\r\\n0361e82baf7e86bf1b5f4339382fccd4\\r\\n--Anki-sync-boundary\\r\\nContent-Disposition: form-data; name=\"s\"\\r\\n\\r\\na87d9d0a\\r\\n--Anki-sync-boundary\\r\\nContent-Disposition: form-data; name=\"c\"\\r\\n\\r\\n1\\r\\n--Anki-sync-boundary\\r\\nContent-Disposition: form-data; name=\"data\"; filename=\"data\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\x00e\\x91MN\\xc30\\x10\\x85\\xafb\\xcd\\x86\\x8d\\xd5$(M\\x9b\\xa8t\\xc3\\x16!\\x84\\xd8U]\\xb8\\xf1\\xa4\\xb1\\xea\\xd8\\x95\\xed6TUwH\\xdc\\xa0\\x1b\\x16p\\x07\\x84\\xc4\\x81\\x10\\xa2\\xb7\\xc0I\\xf9+\\xdd\\xcd|\\xf3\\xe4yo\\xbc\\x86\\xbcdj\\x8a\\x16\\xb25T\\x9a\\xa3\\xf4\\xd5hL\\x81c>k\\xca\\xd1\\x1a\\x04\\x87,J\\xd2\\xb0\\x9b\\xc4q\\x14\\xa7Q\\x8f6\\xca\\x1f\\xd6Mc\\n\\x8aU\\x08\\x19|\\xbcl\\xdf^\\xb7\\xbb\\x87\\xbb\\xdd\\xe3\\xf3\\xfb\\xd3=PXX\\xe5\\x95\\x11\\x05i\\xd4\\x8d\\xe6l\\xe5\\xdf\\x8ch\\xe87\\x18\\\\\\x1e\\x02\\x85\\xf5!p\\xa2\\xc2_\\x92\\xf4{\\xa9\\x87\\xb9\\x96\\x92\\xcd-z\\x03\\x05\\x93\\x16)L\\x8c\\xae-\\x9a\\xf3\\xa3\\x01G\\x9b{SW\\x12\\x99Eb\\x11\\x89+\\x91\\x0c\\x18)\\r\\x16g\\'\\xa5ss\\x9b\\x05\\x01S3Q\\xe3\\xa4\\xa3\\xd0\\x05\\xb6d\\x06y T\\xa1\\x83^\\xdc\\x8d\\x92~\\x18\\x9f\\x9e\\x0c\\xf7\\x984G!s6\\xc5A\\xc0\\x86\\xa4\\xd0\\x86T\\xda i\\xd4\\x1d\\x9f\\x95\\xaf|\\xd6\\xb0M&\\xb0\\xbe\\x10\\x95p_\\xf6\\xd5B\\xca6\\xe01\\xfc#\\xfe\\xaf\\xfb\\xees\\xad\\n\\x7fD\\nx\\xebP\\xf1K\\xac\\xdb5\\xfb\\xee\\x1a\\x97\\xbe\\xdb\\x8c\\xe9h\\xdc\\x9c\\x8cM\\xdb\\x0f\\xdcl>\\x019A\\x97\\x92\\xda\\x01\\x00\\x00\\r\\n--Anki-sync-boundary--\\r\\n'\n",
    "\n",
    "data = b'--Anki-sync-boundary\\r\\nContent-Disposition: form-data; name=\"k\"\\r\\n\\r\\nxQ0tcIOYlDyeJhZV\\r\\n--Anki-sync-boundary\\r\\nContent-Disposition: form-data; name=\"s\"\\r\\n\\r\\n8d6272d6\\r\\n--Anki-sync-boundary\\r\\nContent-Disposition: form-data; name=\"c\"\\r\\n\\r\\n1\\r\\n--Anki-sync-boundary\\r\\nContent-Disposition: form-data; name=\"data\"; filename=\"data\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xabV*S\\xb224\\xd0QJ\\x06\\xd2J\\x89y\\xd9\\x99)E\\xf9\\x99):Fz\\x86\\xa6zf:\\x89y`\\xae\\x95\\xa1\\x91\\x95\\xaf\\x91\\x81\\x81\\xb9\\x97a\\xb0\\xb3R-\\x00\\x16\\x80\\xd5\\x825\\x00\\x00\\x00\\r\\n--Anki-sync-boundary--\\r\\n'\n",
    "```\n",
    "- 输出\n",
    "    - 不加密zstd压缩\n",
    "### 正则匹配\n",
    "```python\n",
    "import re\n",
    "pattern = b'--Anki-sync-boundary\\r\\nContent-Disposition: form-data; name=\"(.*?)\"\\\n",
    "(?:; filename=\".*?\"\\r\\nContent-Type: application/octet-stream)*\\r\\n\\r\\n(.*?|[\\s\\S]*?)\\r\\n(?:--Anki-sync-boundary--\\r\\n$)*'\n",
    "res = re.findall(pattern,data)\n",
    "print(res)\n",
    "```\n",
    "\n",
    "## python\n",
    "### api 版本号\n",
    "`sync.py >> SYNC_VER = 11`\n",
    "### 数据包\n",
    "```python\n",
    "#class Request\n",
    "def parseAndroid(self, body, env):\n",
    "    env[\"APP_CLIENT_TYPE\"] = ClientType.Android\n",
    "    request_items_dict = {}\n",
    "    pattern = b'--Anki-sync-boundary\\r\\nContent-Disposition: form-data; name=\"(.*?)\"\\\n",
    "(?:; filename=\".*?\"\\r\\nContent-Type: application/octet-stream)*\\r\\n\\r\\n(.*?|[\\s\\S]*?)\\r\\n(?:--Anki-sync-boundary--\\r\\n$)*'\n",
    "    res = re.findall(pattern , body)\n",
    "    for k in res:\n",
    "        if len(k) < 2:\n",
    "            logger.error(f\"error pattern match: {k}\")\n",
    "            continue\n",
    "        v = k[1] if k[0] == b\"data\" else k[1].decode()\n",
    "        request_items_dict[k[0].decode()] = v\n",
    "    if \"data\" in request_items_dict and \"c\" in request_items_dict \\\n",
    "        and int(request_items_dict[\"c\"]):\n",
    "        data = request_items_dict[\"data\"] \n",
    "        with gzip.GzipFile(mode=\"rb\", fileobj=io.BytesIO(data)) as gz:\n",
    "            data = gz.read()\n",
    "        request_items_dict[\"data\"]  = data\n",
    "    if \"data\" not in request_items_dict:\n",
    "        #pdb.set_trace()\n",
    "        pass\n",
    "    return request_items_dict\n",
    "def parsePC(self, body, env):\n",
    "    env[\"APP_CLIENT_TYPE\"] = ClientType.PC\n",
    "    request_items_dict = {}\n",
    "    body = pyzstd.decompress(body)\n",
    "    request_items_dict[\"data\"] = body\n",
    "    http_anki_sync = env.get(\"HTTP_ANKI_SYNC\", \"\")\n",
    "    if http_anki_sync != \"\":\n",
    "        anki_sync = json.loads(http_anki_sync)\n",
    "        for k in anki_sync.keys():\n",
    "            request_items_dict[k] = anki_sync[k]\n",
    "    return request_items_dict\n",
    "    \n",
    "def parse(self):\n",
    "    if 'application/octet-stream' in env.get(\"CONTENT_TYPE\",\"\"):\n",
    "        request_items_dict = self.parsePC(body, env)\n",
    "    else:\n",
    "        request_items_dict = self.parseAndroid(body, env)\n",
    "def wrap_body(self, body, env):\n",
    "    if \"APP_CLIENT_TYPE\" not in env:\n",
    "        return body\n",
    "    if env[\"APP_CLIENT_TYPE\"] == ClientType.PC:\n",
    "        return pyzstd.compress(body)\n",
    "    # if env[\"APP_CLIENT_TYPE\"] == ClientType.Android:\n",
    "    #     return body\n",
    "    return body\n",
    "```\n",
    "\n",
    "### 注解\n",
    "```python\n",
    "import types\n",
    "from functools import wraps\n",
    "class chunked(object):\n",
    "    \"\"\"decorator\"\"\"\n",
    "    def __init__(self, func):\n",
    "        wraps(func)(self)\n",
    "        print(\"__init__ chunked\",func)\n",
    "\n",
    "    def __call__(self, *args, **kwargs):\n",
    "        clss = args[0]\n",
    "        environ = args[1]\n",
    "        start_response = args[2]\n",
    "        args = (\n",
    "            clss,\n",
    "            environ,\n",
    "        )\n",
    "        w = self.__wrapped__(*args, **kwargs)\n",
    "        print(\"__call__ chunked\",clss,environ,start_response,kwargs,w)\n",
    "        return \"4\"\n",
    "\n",
    "    def __get__(self, instance, cls):\n",
    "        print(\"__get__ chunked\", instance , cls)\n",
    "        if instance is None:\n",
    "            return self\n",
    "        else:\n",
    "            return types.MethodType(self, instance)\n",
    "class SyncApp:\n",
    "    def __init__(self, config):\n",
    "        print(\"__init__SyncApp\",config)\n",
    "\n",
    "    @chunked\n",
    "    def __call__(self, req):\n",
    "        print(\"__call__SyncApp\",req)\n",
    "        return \"3\"\n",
    "sync = SyncApp(\"config\")\n",
    "sync(1,2)\n",
    " ```\n",
    "### webob\n",
    "- app\n",
    "- environ\n",
    " - 环境变量\n",
    " - 请求数据\n",
    "  - body env.get(\"wsgi.input\")\n",
    "  - header env.get(\"HTTP_ANKI_SYNC\", \"\")\n",
    "- request\n",
    "- response\n",
    "\n",
    "## rust\n",
    "```rust\n",
    "pub fn decode_zstd_body_stream_for_client<S, E>(data: S) -> impl Stream<Item = HttpResult<Bytes>>\n",
    "where\n",
    "    S: Stream<Item = Result<Bytes, E>> + Unpin,\n",
    "    E: Display,\n",
    "{\n",
    "    let response_total = resp\n",
    "        .headers()\n",
    "        .get(&ORIGINAL_SIZE)\n",
    "        .and_then(|v| v.to_str().ok())\n",
    "        .and_then(|v| v.parse::<u32>().ok())\n",
    "        .or_bad_request(\"missing original size\")?;\n",
    "    stream.map(move |res| match res {\n",
    "        Ok(bytes) => {\n",
    "            let mut inner = inner.lock().unwrap();\n",
    "            inner.last_activity = Instant::now();\n",
    "            if sending {\n",
    "                inner.bytes_sent += bytes.len() as u32;\n",
    "            } else {\n",
    "                inner.bytes_received += bytes.len() as u32;\n",
    "            }\n",
    "            Ok(bytes)\n",
    "        }\n",
    "        err => err.or_http_err(StatusCode::SEE_OTHER, \"stream failure\"),\n",
    "    })\n",
    "    \n",
    "}\n",
    "```\n",
    "- and_then map match\n",
    "- |v| ?\n",
    "- where impl trait"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e63eb4d",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}