[
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '15 8,20 * * *'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: pnpm/action-setup@v4\n        with:\n          version: 9\n      - name: Checkout\n        uses: actions/checkout@v3\n      - name: Build\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          DOWNLOAD_TOKEN: ${{ secrets.DOWNLOAD_TOKEN }}\n          CZDB_TOKEN: ${{ secrets.CZDB_TOKEN }}\n        run: |\n          pnpm i\n          pnpm run build\n"
  },
  {
    "path": ".gitignore",
    "content": "dist/\nnode_modules/\ntemp/"
  },
  {
    "path": "README.md",
    "content": "纯真 IP 数据库自动同步仓库\n\n# 使用说明\n\n下载最新版本\n```\nwget https://github.com/metowolf/qqwry.dat/releases/latest/download/qqwry.dat\n```\n\n获取最新版本号 YYYYMMDD 格式\n```\ncurl https://raw.githubusercontent.com/metowolf/qqwry.dat/main/version.json | jq -r .latest\n```\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"qqwry.dat\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"repository\": \"git@github.com:metowolf/qqwry.dat.git\",\n  \"author\": \"metowolf <i@i-meto.com>\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"build\": \"node src/build.js\"\n  },\n  \"devDependencies\": {\n    \"@ipdb/czdb\": \"^0.0.2\",\n    \"execa\": \"^9.5.2\",\n    \"iconv-lite\": \"^0.6.3\",\n    \"lib-qqwry\": \"^1.3.4\"\n  }\n}\n"
  },
  {
    "path": "protocol.md",
    "content": "# File Structure\n\nAll integers are stored in little-endian format.\n\n```\n┌─────────────────────────────────┐\n│           File Header           │ 8 bytes\n├─────────────────────────────────┤\n│          Record Zone            │ Variable length\n├─────────────────────────────────┤\n│           Index Zone            │ 7 bytes × n entries\n└─────────────────────────────────┘\n```\n\n## File Header Detail\n\n```\n┌───────────────┬───────────────┐\n│ First Index   │  Last Index   │\n│   Offset      │    Offset     │\n│   4 bytes     │    4 bytes    │\n└───────────────┴───────────────┘\n```\n\n## Record Zone Entry Detail\n\nAll strings are GBK encoded and null-terminated.\n\n```\n┌──────────┬───────────┬──────────┐\n│  End IP  │  Country  │   Area   │\n│ 4 bytes  │ Variable  │ Variable │\n└──────────┴───────────┴──────────┘\n\nA. Direct String:\n┌─────────────┬───┐\n│   String    │ 0 │ \n└─────────────┴───┘\n\nB. Redirect Mode 1 (0x01):\n┌────┬───────────┐\n│ 01 │  Offset   │ → Points to [Country][Area]\n└────┴───────────┘\n     3 bytes\n\nC. Redirect Mode 2 (0x02):\n┌────┬───────────┐\n│ 02 │  Offset   │\n└────┴───────────┘\n     3 bytes\n```\n\n## Index Zone Entry Detail\n\n```\n┌──────────────┬─────────────┐\n│   Start IP   │   Offset    │\n│   4 bytes    │   3 bytes   │\n└──────────────┴─────────────┘\n                 Points to Record Zone\n```\n\n## Link\n\n- https://web.archive.org/web/20140423114336/http://lumaqq.linuxsir.org/article/qqwry_format_detail.html"
  },
  {
    "path": "src/build.js",
    "content": "import fs from 'fs'\nimport { execa } from 'execa'\nimport libqqwry from 'lib-qqwry'\nimport Decoder from '@ipdb/czdb'\nimport QQWryPacker from './packer.js'\n\nconst DOWNLOAD_TOKEN = process.env.DOWNLOAD_TOKEN\nconst CZDB_TOKEN = process.env.CZDB_TOKEN\n\nconst download = async () => {\n  const url = `https://www.cz88.net/api/communityIpAuthorization/communityIpDbFile?fn=czdb&key=${DOWNLOAD_TOKEN}`\n  await fs.promises.mkdir('./temp', { recursive: true })\n  await execa('wget', ['--timeout=30', '--tries=3', '-O', './temp/download.zip', url])\n  // 解压\n  await execa('unzip', ['./temp/download.zip', '-d', './temp'])\n}\n\nconst extract = async () => {\n  const qqwryPacker = new QQWryPacker()\n  const decoder = new Decoder('./temp/cz88_public_v4.czdb', CZDB_TOKEN)\n  decoder.dump(info => {\n    const { startIp, endIp, regionInfo } = info\n    // 过滤 IPv6\n    if (startIp.includes(':')) {\n      return\n    }\n    // 分离 geo, isp\n    const [geo, isp] = regionInfo.split('\\t', 2)\n    // 生成记录\n    qqwryPacker.insert(startIp, endIp, geo, isp)\n  })\n\n  // 生成二进制文件\n  const buffer = qqwryPacker.build()\n  await fs.promises.mkdir('./dist', { recursive: true })\n  fs.writeFileSync('./dist/qqwry.dat', buffer)\n}\n\nconst parseQQwryInfo = async () => {\n  const qqwry = libqqwry(true, './dist/qqwry.dat')\n\n  const info = {\n    count: 0,\n    unique: 0,\n  }\n  \n  const unique = new Set()\n\n  let ip = '0.0.0.0'\n  while (true) {\n    let data = qqwry.searchIPScope(ip, ip)[0]\n    // stat\n    info.count += 1\n    const hashkey = `${data.Country}${data.Area}`\n    if (!unique.has(hashkey)) {\n      info.unique += 1\n      unique.add(hashkey)\n    }\n    if (data.endIP === '255.255.255.255') break\n    ip = libqqwry.intToIP(data.endInt + 1)\n  }\n\n  return info\n}\n\nconst readInfo = () => {\n  const data = fs.readFileSync('./version.json', 'utf-8')\n  return JSON.parse(data)\n}\n\nconst parseQQWryVersion = () => {\n  const qqwry = libqqwry(true, './dist/qqwry.dat')\n  const info = qqwry.searchIP('255.255.255.255')\n  return info.Area.match(/(\\d+)/gi).join('')\n}\n\nconst release = async () => {\n  const info = await readInfo()\n  const currentVersion = parseQQWryVersion()\n  if (info.latest === currentVersion || info.versions[currentVersion]) {\n    console.log('No new version, skip')\n    return\n  }\n\n  const currentInfo = await parseQQwryInfo()\n\n  if (!info.versions[currentVersion]) {\n    info.versions[currentVersion] = currentInfo\n    if (info.latest < currentVersion) {\n      info.latest = currentVersion\n    }\n    fs.writeFileSync('./version.json', JSON.stringify(info, null, 2))\n\n    console.log({\n      info,\n      currentVersion,\n      currentInfo\n    })\n\n    await execa('gh', ['release', 'create', currentVersion, '-t', currentVersion, '-n', `QQWry version: ${currentVersion}`, './dist/qqwry.dat'])\n    await execa('git', ['config', 'user.name', 'github-actions'])\n    await execa('git', ['config', 'user.email', 'i@i-meto.com'])\n    await execa('git', ['add', './version.json'])\n    await execa('git', ['commit', '-m', `chore: update version info to ${currentVersion}`])\n    await execa('git', ['push'])\n  }\n\n}\n\nconst main = async () => {\n  // 0. 下载 czdb 并解压\n  await download()\n  console.log('Downloaded')\n\n  // 1. 反解压 czdb 并生成 qqwry.dat\n  await extract()\n  console.log('Extracted')\n\n  // 2. 生成版本信息\n  await release()\n  console.log('Released')\n}\n\nmain()"
  },
  {
    "path": "src/packer.js",
    "content": "import iconv from 'iconv-lite'\n\nclass QQWryPacker {\n  constructor() {\n    this.indexList = []      // IP索引区\n    this.recordList = []     // 记录区\n    this.ipTree = new Map()  // IP树\n    this.stringCache = new Map() // 字符串缓存\n    this.maxRecordOffset = 8\n  }\n\n  // 插入一条IP记录\n  insert(startIP, endIP, country, area) {\n    const startIPInt = this._ipToInt(startIP)\n    const endIPInt = this._ipToInt(endIP)\n    const geoOffset = this.maxRecordOffset\n    this._createRecord(endIPInt, country, area || 'CZ88.NET')\n    this.ipTree.set(startIPInt, { endIPInt, geoOffset, country, area: area || 'CZ88.NET' })\n  }\n\n  // 生成最终的二进制文件\n  build() {\n    // 1. 构造数据区\n    const recordBuffer = Buffer.concat(this.recordList)\n\n    // 2. 构造索引区\n    const sortedIPs = Array.from(this.ipTree.keys()).sort((a, b) => a - b)\n    const indexList = []\n\n    for (let i = 0; i < sortedIPs.length; i++) {\n      const startIPInt = sortedIPs[i]\n      const { geoOffset } = this.ipTree.get(startIPInt)\n      const indexRecord = Buffer.alloc(7)\n      indexRecord.writeUInt32LE(startIPInt, 0)\n      if (geoOffset > 0xFFFFFF) {\n        throw new Error('Offset overflow')\n      }\n      indexRecord.writeUInt8((geoOffset >> 0) & 0xFF, 4)\n      indexRecord.writeUInt8((geoOffset >> 8) & 0xFF, 5)\n      indexRecord.writeUInt8((geoOffset >> 16) & 0xFF, 6)\n      indexList.push(indexRecord)\n    }\n\n    // 3. 构造文件头\n    const headerBuffer = Buffer.alloc(8)\n    headerBuffer.writeUInt32LE(8 + recordBuffer.length, 0)\n    headerBuffer.writeUInt32LE(8 + recordBuffer.length + sortedIPs.length * 7 - 7, 4)\n\n    console.log([\n      '文件头长度:', headerBuffer.length,\n      '记录区长度:', recordBuffer.length,\n      '索引区长度:', Buffer.concat(indexList).length,\n      '记录数:', sortedIPs.length,\n    ])\n\n    // 4. 合并所有部分\n    return Buffer.concat([\n      headerBuffer,  // 文件头\n      recordBuffer,  // 记录区\n      ...indexList // 索引区\n    ])\n  }\n\n  _ipToInt(ip) {\n    const parts = ip.split('.')\n    // 使用无符号右移确保结果为正数\n    return ((parseInt(parts[0]) << 24) |\n      (parseInt(parts[1]) << 16) |\n      (parseInt(parts[2]) << 8) |\n      parseInt(parts[3])) >>> 0\n  }\n\n  _createRecord(endIPInt, country, area) {\n    // 写入 endIP\n    const recordBuf = Buffer.alloc(4)\n    recordBuf.writeUInt32LE(endIPInt, 0)\n    this.recordList.push(recordBuf)\n    this.maxRecordOffset += 4\n\n    // country + area 都有的记录\n    if (this.stringCache.has(`${country}\\t${area}`)) {\n      const redirectBuf = Buffer.alloc(4)\n      redirectBuf.writeUInt8(0x01, 0)\n      const offset = this.stringCache.get(`${country}\\t${area}`)\n      redirectBuf.writeUInt8((offset >> 0) & 0xFF, 1)\n      redirectBuf.writeUInt8((offset >> 8) & 0xFF, 2)\n      redirectBuf.writeUInt8((offset >> 16) & 0xFF, 3)\n      this.recordList.push(redirectBuf)\n      this.maxRecordOffset += 4\n      return\n    }\n\n    // country, area 分开都有的记录\n    if (this.stringCache.has(country) && this.stringCache.has(area)) {\n      const countryOffset = this.stringCache.get(country)\n      const areaOffset = this.stringCache.get(area)\n\n      // 生成缓存键:基于两个偏移的组合\n      const ptrCacheKey = `ptr:${countryOffset}:${areaOffset}`\n\n      // 检查是否已有相同的指针组合\n      if (this.stringCache.has(ptrCacheKey)) {\n        // 复用:用 0x01 重定向到已有的 8 字节块\n        const existingOffset = this.stringCache.get(ptrCacheKey)\n        const redirectBuf = Buffer.alloc(4)\n        redirectBuf.writeUInt8(0x01, 0)\n        redirectBuf.writeUInt8((existingOffset >> 0) & 0xFF, 1)\n        redirectBuf.writeUInt8((existingOffset >> 8) & 0xFF, 2)\n        redirectBuf.writeUInt8((existingOffset >> 16) & 0xFF, 3)\n        this.recordList.push(redirectBuf)\n        this.maxRecordOffset += 4\n        return\n      }\n\n      // 首次出现:直接写入 8 字节(省略 0x01 层)\n      const currentOffset = this.maxRecordOffset\n\n      const countryBuf = Buffer.alloc(4)\n      countryBuf.writeUInt8(0x02, 0)\n      countryBuf.writeUInt8((countryOffset >> 0) & 0xFF, 1)\n      countryBuf.writeUInt8((countryOffset >> 8) & 0xFF, 2)\n      countryBuf.writeUInt8((countryOffset >> 16) & 0xFF, 3)\n\n      const areaBuf = Buffer.alloc(4)\n      areaBuf.writeUInt8(0x02, 0)\n      areaBuf.writeUInt8((areaOffset >> 0) & 0xFF, 1)\n      areaBuf.writeUInt8((areaOffset >> 8) & 0xFF, 2)\n      areaBuf.writeUInt8((areaOffset >> 16) & 0xFF, 3)\n\n      this.recordList.push(countryBuf)\n      this.recordList.push(areaBuf)\n      this.maxRecordOffset += 8\n\n      // 缓存这个 8 字节块的位置\n      this.stringCache.set(ptrCacheKey, currentOffset)\n      // 保持原有的组合缓存(用于策略 1)\n      this.stringCache.set(`${country}\\t${area}`, currentOffset)\n      return\n    }\n\n    // country 有 area 没有的记录\n    if (this.stringCache.has(country)) {\n      const currentOffset = this.maxRecordOffset\n      const countryOffset = this.stringCache.get(country)\n      const countryBuf = Buffer.alloc(4)\n      countryBuf.writeUInt8(0x02, 0)\n      countryBuf.writeUInt8((countryOffset >> 0) & 0xFF, 1)\n      countryBuf.writeUInt8((countryOffset >> 8) & 0xFF, 2)\n      countryBuf.writeUInt8((countryOffset >> 16) & 0xFF, 3)\n\n      const areaBuf = Buffer.concat([\n        iconv.encode(area || '', 'gbk'),\n        Buffer.from([0x00])\n      ])\n\n      this.recordList.push(countryBuf)\n      this.recordList.push(areaBuf)\n      this.maxRecordOffset += countryBuf.length + areaBuf.length\n\n      // 缓存\n      const areaOffset = currentOffset + countryBuf.length\n      this.stringCache.set(area, areaOffset)\n      this.stringCache.set(`${country}\\t${area}`, currentOffset)\n      return\n    }\n\n    // 其他情况\n    const currentOffset = this.maxRecordOffset\n    const countryBuf = Buffer.concat([\n      iconv.encode(country || '', 'gbk'),\n      Buffer.from([0x00])\n    ])\n    const areaBuf = Buffer.concat([\n      iconv.encode(area || '', 'gbk'),\n      Buffer.from([0x00])\n    ])\n\n    this.recordList.push(countryBuf)\n    this.recordList.push(areaBuf)\n    this.maxRecordOffset += countryBuf.length + areaBuf.length\n\n    // 缓存\n    const areaOffset = currentOffset + countryBuf.length\n    this.stringCache.set(`${country}`, currentOffset)\n    this.stringCache.set(area, areaOffset)\n    this.stringCache.set(`${country}\\t${area}`, currentOffset)\n  }\n}\n\nexport default QQWryPacker"
  },
  {
    "path": "version.json",
    "content": "{\n  \"latest\": \"20260415\",\n  \"versions\": {\n    \"20221005\": {\n      \"count\": 530599,\n      \"unique\": 156555\n    },\n    \"20221012\": {\n      \"count\": 530606,\n      \"unique\": 156559\n    },\n    \"20221019\": {\n      \"count\": 530598,\n      \"unique\": 156557\n    },\n    \"20221026\": {\n      \"count\": 530613,\n      \"unique\": 156558\n    },\n    \"20221102\": {\n      \"count\": 530632,\n      \"unique\": 156559\n    },\n    \"20221109\": {\n      \"count\": 530283,\n      \"unique\": 156534\n    },\n    \"20221116\": {\n      \"count\": 530310,\n      \"unique\": 156530\n    },\n    \"20221123\": {\n      \"count\": 530387,\n      \"unique\": 156538\n    },\n    \"20221207\": {\n      \"count\": 530416,\n      \"unique\": 156532\n    },\n    \"20221214\": {\n      \"count\": 530424,\n      \"unique\": 156532\n    },\n    \"20221221\": {\n      \"count\": 530422,\n      \"unique\": 156532\n    },\n    \"20221228\": {\n      \"count\": 530429,\n      \"unique\": 156599\n    },\n    \"20230104\": {\n      \"count\": 530432,\n      \"unique\": 156599\n    },\n    \"20230111\": {\n      \"count\": 530438,\n      \"unique\": 156601\n    },\n    \"20230118\": {\n      \"count\": 530452,\n      \"unique\": 156603\n    },\n    \"20230125\": {\n      \"count\": 530460,\n      \"unique\": 156606\n    },\n    \"20230201\": {\n      \"count\": 530475,\n      \"unique\": 156608\n    },\n    \"20230208\": {\n      \"count\": 530501,\n      \"unique\": 156610\n    },\n    \"20230215\": {\n      \"count\": 530548,\n      \"unique\": 156615\n    },\n    \"20230222\": {\n      \"count\": 530586,\n      \"unique\": 156623\n    },\n    \"20230301\": {\n      \"count\": 530599,\n      \"unique\": 156664\n    },\n    \"20230308\": {\n      \"count\": 530600,\n      \"unique\": 156725\n    },\n    \"20230315\": {\n      \"count\": 530585,\n      \"unique\": 157012\n    },\n    \"20230322\": {\n      \"count\": 530523,\n      \"unique\": 160559\n    },\n    \"20230405\": {\n      \"count\": 530571,\n      \"unique\": 162291\n    },\n    \"20230419\": {\n      \"count\": 530638,\n      \"unique\": 161940\n    },\n    \"20230426\": {\n      \"count\": 530697,\n      \"unique\": 161953\n    },\n    \"20230510\": {\n      \"count\": 530805,\n      \"unique\": 161980\n    },\n    \"20230517\": {\n      \"count\": 530831,\n      \"unique\": 161989\n    },\n    \"20230524\": {\n      \"count\": 530870,\n      \"unique\": 162003\n    },\n    \"20230607\": {\n      \"count\": 530889,\n      \"unique\": 162009\n    },\n    \"20230614\": {\n      \"count\": 531340,\n      \"unique\": 162133\n    },\n    \"20230621\": {\n      \"count\": 531380,\n      \"unique\": 162138\n    },\n    \"20230628\": {\n      \"count\": 531400,\n      \"unique\": 162144\n    },\n    \"20230705\": {\n      \"count\": 531635,\n      \"unique\": 162206\n    },\n    \"20230726\": {\n      \"count\": 545203,\n      \"unique\": 162299\n    },\n    \"20230802\": {\n      \"count\": 545277,\n      \"unique\": 162316\n    },\n    \"20230809\": {\n      \"count\": 545361,\n      \"unique\": 162343\n    },\n    \"20230823\": {\n      \"count\": 545617,\n      \"unique\": 162419\n    },\n    \"20230913\": {\n      \"count\": 546118,\n      \"unique\": 162597\n    },\n    \"20230920\": {\n      \"count\": 546212,\n      \"unique\": 162606\n    },\n    \"20230927\": {\n      \"count\": 546633,\n      \"unique\": 162627\n    },\n    \"20231011\": {\n      \"count\": 546712,\n      \"unique\": 162648\n    },\n    \"20231018\": {\n      \"count\": 546739,\n      \"unique\": 162655\n    },\n    \"20231025\": {\n      \"count\": 546956,\n      \"unique\": 162667\n    },\n    \"20231108\": {\n      \"count\": 547183,\n      \"unique\": 162691\n    },\n    \"20231115\": {\n      \"count\": 547242,\n      \"unique\": 162691\n    },\n    \"20231122\": {\n      \"count\": 547299,\n      \"unique\": 162704\n    },\n    \"20231213\": {\n      \"count\": 547514,\n      \"unique\": 162726\n    },\n    \"20231220\": {\n      \"count\": 547557,\n      \"unique\": 162729\n    },\n    \"20231227\": {\n      \"count\": 547635,\n      \"unique\": 162740\n    },\n    \"20240103\": {\n      \"count\": 547639,\n      \"unique\": 162740\n    },\n    \"20240110\": {\n      \"count\": 547681,\n      \"unique\": 162741\n    },\n    \"20240117\": {\n      \"count\": 547698,\n      \"unique\": 162742\n    },\n    \"20240124\": {\n      \"count\": 547722,\n      \"unique\": 162745\n    },\n    \"20240131\": {\n      \"count\": 547744,\n      \"unique\": 162750\n    },\n    \"20240207\": {\n      \"count\": 547753,\n      \"unique\": 162751\n    },\n    \"20240214\": {\n      \"count\": 547760,\n      \"unique\": 162751\n    },\n    \"20240221\": {\n      \"count\": 547783,\n      \"unique\": 162765\n    },\n    \"20240228\": {\n      \"count\": 547947,\n      \"unique\": 162769\n    },\n    \"20240306\": {\n      \"count\": 547750,\n      \"unique\": 162753\n    },\n    \"20240313\": {\n      \"count\": 547762,\n      \"unique\": 162754\n    },\n    \"20240320\": {\n      \"count\": 547857,\n      \"unique\": 162753\n    },\n    \"20240327\": {\n      \"count\": 548329,\n      \"unique\": 162823\n    },\n    \"20240403\": {\n      \"count\": 578950,\n      \"unique\": 163491\n    },\n    \"20240410\": {\n      \"count\": 591425,\n      \"unique\": 164525\n    },\n    \"20240417\": {\n      \"count\": 599415,\n      \"unique\": 165770\n    },\n    \"20240424\": {\n      \"count\": 600034,\n      \"unique\": 165952\n    },\n    \"20240508\": {\n      \"count\": 628031,\n      \"unique\": 166831\n    },\n    \"20240515\": {\n      \"count\": 1221918,\n      \"unique\": 167456\n    },\n    \"20240522\": {\n      \"count\": 1465151,\n      \"unique\": 167531\n    },\n    \"20240529\": {\n      \"count\": 1465430,\n      \"unique\": 167550\n    },\n    \"20240605\": {\n      \"count\": 1465577,\n      \"unique\": 167562\n    },\n    \"20240612\": {\n      \"count\": 1465857,\n      \"unique\": 167598\n    },\n    \"20240619\": {\n      \"count\": 1465450,\n      \"unique\": 166536\n    },\n    \"20240626\": {\n      \"count\": 1463756,\n      \"unique\": 166554\n    },\n    \"20240703\": {\n      \"count\": 1463859,\n      \"unique\": 166564\n    },\n    \"20240710\": {\n      \"count\": 1463934,\n      \"unique\": 166579\n    },\n    \"20240911\": {\n      \"count\": 1470296,\n      \"unique\": 167555\n    },\n    \"20240925\": {\n      \"count\": 1491882,\n      \"unique\": 170650\n    },\n    \"20241225\": {\n      \"count\": 1497246,\n      \"unique\": 171027\n    },\n    \"20250101\": {\n      \"count\": 1497606,\n      \"unique\": 171048\n    },\n    \"20250108\": {\n      \"count\": 1499133,\n      \"unique\": 171292\n    },\n    \"20250115\": {\n      \"count\": 1499426,\n      \"unique\": 171320\n    },\n    \"20250122\": {\n      \"count\": 1499518,\n      \"unique\": 171336\n    },\n    \"20250129\": {\n      \"count\": 1500053,\n      \"unique\": 171366\n    },\n    \"20250205\": {\n      \"count\": 1500234,\n      \"unique\": 171374\n    },\n    \"20250212\": {\n      \"count\": 1500678,\n      \"unique\": 171382\n    },\n    \"20250219\": {\n      \"count\": 1502228,\n      \"unique\": 171565\n    },\n    \"20250226\": {\n      \"count\": 1502538,\n      \"unique\": 171589\n    },\n    \"20250305\": {\n      \"count\": 1502784,\n      \"unique\": 171597\n    },\n    \"20250312\": {\n      \"count\": 1503240,\n      \"unique\": 171602\n    },\n    \"20250319\": {\n      \"count\": 1503712,\n      \"unique\": 171606\n    },\n    \"20250326\": {\n      \"count\": 1503908,\n      \"unique\": 171605\n    },\n    \"20250402\": {\n      \"count\": 1504242,\n      \"unique\": 171610\n    },\n    \"20250409\": {\n      \"count\": 1504441,\n      \"unique\": 171615\n    },\n    \"20250416\": {\n      \"count\": 1504873,\n      \"unique\": 171623\n    },\n    \"20250423\": {\n      \"count\": 1505280,\n      \"unique\": 171645\n    },\n    \"20250430\": {\n      \"count\": 1505524,\n      \"unique\": 171641\n    },\n    \"20250507\": {\n      \"count\": 1505651,\n      \"unique\": 171643\n    },\n    \"20250514\": {\n      \"count\": 1505876,\n      \"unique\": 171645\n    },\n    \"20250521\": {\n      \"count\": 1506135,\n      \"unique\": 171649\n    },\n    \"20250528\": {\n      \"count\": 1506279,\n      \"unique\": 171671\n    },\n    \"20250604\": {\n      \"count\": 1506465,\n      \"unique\": 171669\n    },\n    \"20250611\": {\n      \"count\": 1506669,\n      \"unique\": 171676\n    },\n    \"20250618\": {\n      \"count\": 1507011,\n      \"unique\": 171683\n    },\n    \"20250625\": {\n      \"count\": 1507345,\n      \"unique\": 171691\n    },\n    \"20250702\": {\n      \"count\": 1508023,\n      \"unique\": 171761\n    },\n    \"20250709\": {\n      \"count\": 1508640,\n      \"unique\": 171846\n    },\n    \"20250716\": {\n      \"count\": 1509472,\n      \"unique\": 171917\n    },\n    \"20250723\": {\n      \"count\": 1510061,\n      \"unique\": 171957\n    },\n    \"20250730\": {\n      \"count\": 1510563,\n      \"unique\": 172002\n    },\n    \"20250806\": {\n      \"count\": 1511161,\n      \"unique\": 172012\n    },\n    \"20250813\": {\n      \"count\": 1512917,\n      \"unique\": 172032\n    },\n    \"20250820\": {\n      \"count\": 1513246,\n      \"unique\": 172048\n    },\n    \"20250827\": {\n      \"count\": 1513404,\n      \"unique\": 172057\n    },\n    \"20250903\": {\n      \"count\": 1513733,\n      \"unique\": 172065\n    },\n    \"20250910\": {\n      \"count\": 1513841,\n      \"unique\": 172066\n    },\n    \"20250917\": {\n      \"count\": 1513968,\n      \"unique\": 172076\n    },\n    \"20250924\": {\n      \"count\": 1514285,\n      \"unique\": 172085\n    },\n    \"20251001\": {\n      \"count\": 1514499,\n      \"unique\": 172073\n    },\n    \"20251008\": {\n      \"count\": 1514536,\n      \"unique\": 172079\n    },\n    \"20251015\": {\n      \"count\": 1514763,\n      \"unique\": 172094\n    },\n    \"20251022\": {\n      \"count\": 1514904,\n      \"unique\": 172096\n    },\n    \"20251029\": {\n      \"count\": 1515943,\n      \"unique\": 172092\n    },\n    \"20251105\": {\n      \"count\": 1516117,\n      \"unique\": 172097\n    },\n    \"20251112\": {\n      \"count\": 1516398,\n      \"unique\": 172108\n    },\n    \"20251119\": {\n      \"count\": 1516499,\n      \"unique\": 172126\n    },\n    \"20251126\": {\n      \"count\": 1516582,\n      \"unique\": 172112\n    },\n    \"20251203\": {\n      \"count\": 1516869,\n      \"unique\": 172114\n    },\n    \"20251210\": {\n      \"count\": 1516927,\n      \"unique\": 172129\n    },\n    \"20251217\": {\n      \"count\": 1517124,\n      \"unique\": 172130\n    },\n    \"20251224\": {\n      \"count\": 1517155,\n      \"unique\": 172098\n    },\n    \"20251231\": {\n      \"count\": 1516654,\n      \"unique\": 172067\n    },\n    \"20260107\": {\n      \"count\": 1516779,\n      \"unique\": 172069\n    },\n    \"20260114\": {\n      \"count\": 1516888,\n      \"unique\": 172079\n    },\n    \"20260121\": {\n      \"count\": 1516987,\n      \"unique\": 172093\n    },\n    \"20260128\": {\n      \"count\": 1517149,\n      \"unique\": 172075\n    },\n    \"20260204\": {\n      \"count\": 1517652,\n      \"unique\": 172078\n    },\n    \"20260211\": {\n      \"count\": 1517758,\n      \"unique\": 172085\n    },\n    \"20260218\": {\n      \"count\": 1518892,\n      \"unique\": 172108\n    },\n    \"20260225\": {\n      \"count\": 1518990,\n      \"unique\": 172121\n    },\n    \"20260304\": {\n      \"count\": 1519155,\n      \"unique\": 172139\n    },\n    \"20260311\": {\n      \"count\": 1519910,\n      \"unique\": 172153\n    },\n    \"20260318\": {\n      \"count\": 1520032,\n      \"unique\": 172165\n    },\n    \"20260325\": {\n      \"count\": 1521275,\n      \"unique\": 172372\n    },\n    \"20260401\": {\n      \"count\": 1521384,\n      \"unique\": 172383\n    },\n    \"20260408\": {\n      \"count\": 1521597,\n      \"unique\": 172401\n    },\n    \"20260415\": {\n      \"count\": 1522039,\n      \"unique\": 172421\n    }\n  }\n}"
  }
]