[
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Check TMDB IP\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 10,22 * * *'  # 每天 10:00 和 22:00 执行\n\npermissions:\n  contents: write  # 确保有写入权限\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    \n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v2\n      \n    - name: Set up Python\n      uses: actions/setup-python@v2\n      with:\n        python-version: '3.10'\n        \n    - name: Install dependencies\n      run: |\n        sudo apt update\n        sudo apt install --only-upgrade python3-pip\n        if [ -f requirements.txt ]; then sudo pip install -r requirements.txt; fi\n        \n    - name: Run Python script\n      run: |\n        sudo python check_tmdb_github_dnschecked.py\n        \n    - name: 提交更改\n      run: |\n         git config --global user.name 'GitHub Action'\n         git config --global user.email 'action@github.com'\n         git add README.md Tmdb_host_ipv4 Tmdb_host_ipv6\n         git commit -m \"Update TMDB IP [action]\" || echo \"No changes to commit\"\n      continue-on-error: true\n\n    - name: Push changes\n      env:\n       GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      run: git push origin main\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 cnwikee\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# CheckTMDB\n\n每日自动更新TMDB，themoviedb、thetvdb 国内可正常连接IP，解决DNS污染，供tinyMediaManager(TMM削刮器)、Kodi的刮削器、群晖VideoStation的海报墙、Plex Server的元数据代理、Emby Server元数据下载器、Infuse、Nplayer等正常削刮影片信息。\n\n## 一、前景\n\n自从我早两年使用了黑群NAS以后，下了好多的电影电视剧，发现电视端无法生成正常的海报墙。查找资料得知应该是 themoviedb.org、tmdb.org 无法正常访问，因为DNS受到了污染无法正确解析到TMDB的IP，故依葫芦画瓢写了一个python脚本，每日定时通过[dnschecker](https://dnschecker.org/)查询出最佳IP，并自动同步到路由器外挂hosts，可正常削刮。\n\n**本项目无需安装任何程序**\n\n通过修改本地、路由器 hosts 文件，即可正常削刮影片信息。\n\n## 文件地址\n\n- TMDB IPv4 hosts：`https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4` ，[链接](https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4)\n- TMDB IPv6 hosts：`https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6` ，[链接](https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6)\n\n## 二、使用方法\n\n### 2.1 手动方式\n\n#### 2.1.1 IPv4地址复制下面的内容\n\n```bash\n# Tmdb Hosts Start\n18.160.10.119               tmdb.org\n3.170.19.81                 api.tmdb.org\n3.170.42.125                files.tmdb.org\n3.171.38.81                 themoviedb.org\n3.170.19.106                api.themoviedb.org\n3.171.38.81                 www.themoviedb.org\n3.170.3.12                  auth.themoviedb.org\n185.93.1.249                image.tmdb.org\n169.150.236.107             images.tmdb.org\n98.82.155.134               imdb.com\n18.67.70.32                 www.imdb.com\n98.82.155.134               secure.imdb.com\n18.67.70.32                 s.media-imdb.com\n98.82.158.179               us.dd.imdb.com\n18.67.70.32                 www.imdb.to\n98.82.155.134               origin-www.imdb.com\n23.207.202.24               ia.media-imdb.com\n3.171.75.86                 thetvdb.com\n3.170.35.80                 api.thetvdb.com\n151.101.1.16                ia.media-imdb.com\n151.101.1.16                f.media-amazon.com\n18.67.76.79                 imdb-video.media-imdb.com\n148.113.196.166             webservice.fanart.tv\n172.67.74.146               images.fanart.tv\n158.69.209.125              assets.fanart.tv\n172.67.74.146               fanart.tv\n104.20.14.80                api.trakt.tv\n104.20.14.80                trakt.tv\n# Update time: 2026-04-24T19:13:55+08:00\n# IPv4 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4\n# IPv6 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6\n# Star me: https://github.com/cnwikee/CheckTMDB\n# Tmdb Hosts End\n\n```\n\n该内容会自动定时更新， 数据更新时间：2026-04-24T19:13:55+08:00\n\n#### 2.1.2 IPv6地址复制下面的内容\n\n```bash\n# Tmdb Hosts Start\n2600:9000:250a:8c00:10:db24:6940:93a1              tmdb.org\n2600:9000:286d:2e00:10:fb02:4000:93a1              api.tmdb.org\n2600:9000:2870:ca00:5:da10:7440:93a1               files.tmdb.org\n2600:9000:28a0:e00:e:5373:440:93a1                 themoviedb.org\n2600:9000:286d:3000:c:174a:c400:93a1               api.themoviedb.org\n2600:9000:28a0:9200:e:5373:440:93a1                www.themoviedb.org\n2600:9000:286a:b000:16:e4a1:eb00:93a1              auth.themoviedb.org\n2400:52e0:1a00::1029:1                             image.tmdb.org\n2400:52e0:1a00::1233:1                             images.tmdb.org\n2a04:4e42:400::272                                 ia.media-imdb.com\n2a04:4e42:400::272                                 ia.media-imdb.com\n2a04:4e42:400::272                                 f.media-amazon.com\n2606:4700:20::681a:d7e                             images.fanart.tv\n2606:4700:20::681a:d7e                             fanart.tv\n2606:4700:10::6814:e50                             api.trakt.tv\n2606:4700:10::6814:e50                             trakt.tv\n# Update time: 2026-04-24T19:13:55+08:00\n# IPv4 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4\n# IPv6 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6\n# Star me: https://github.com/cnwikee/CheckTMDB\n# Tmdb Hosts End\n\n```\n\n该内容会自动定时更新， 数据更新时间：2026-04-24T19:13:55+08:00\n\n> [!NOTE]\n> 由于项目搭建在Github Aciton，延时数据获取于Github Action 虚拟主机网络环境，请自行测试可用性，建议使用本地网络环境自动设置。\n\n#### 2.1.3 修改 hosts 文件\n\nhosts 文件在每个系统的位置不一，详情如下：\n\n- Windows 系统：`C:\\Windows\\System32\\drivers\\etc\\hosts`\n- Linux 系统：`/etc/hosts`\n- Mac（苹果电脑）系统：`/etc/hosts`\n- Android（安卓）系统：`/system/etc/hosts`\n- iPhone（iOS）系统：`/etc/hosts`\n\n修改方法，把第一步的内容复制到文本末尾：\n\n1. Windows 使用记事本。\n2. Linux、Mac 使用 Root 权限：`sudo vi /etc/hosts`。\n3. iPhone、iPad 须越狱、Android 必须要 root。\n\n#### 2.1.4 激活生效\n\n大部分情况下是直接生效，如未生效可尝试下面的办法，刷新 DNS：\n\n1. Windows：在 CMD 窗口输入：`ipconfig /flushdns`\n\n2. Linux 命令：`sudo nscd restart`，如报错则须安装：`sudo apt install nscd` 或 `sudo /etc/init.d/nscd restart`\n\n3. Mac 命令：`sudo killall -HUP mDNSResponder`\n\n**Tips：** 上述方法无效可以尝试重启机器。\n\n### 2.2 自动方式\n\n#### 2.2.1 安装 SwitchHosts\n\nGitHub 发行版：https://github.com/oldj/SwitchHosts/releases/latest\n\n#### 2.2.2 添加 hosts\n\n点击左上角“+”，并进行以下配置：\n\n- Hosts 类型：`远程`\n- Hosts 标题：任意\n- URL\n    - IPv4：`https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4`\n    - IPv6：`https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6`\n- 自动刷新：`1 小时`\n\n#### 2.2.3 启用 hosts\n\n在左侧边栏启用 hosts，首次使用时软件会自动获取内容。如果无法连接到 GitHub，可以尝试用同样的方法添加 [GitHub520](https://github.com/521xueweihan/GitHub520) hosts。\n\n## 三、参数说明\n\n1. 直接执行`check_tmdb_github.py`脚本，同时查询IPv4及IPv6地址，目录生成`Tmdb_host_ipv4`文件，及`Tmdb_host_ipv6`文件；\n2. 带`-G` 参数执行：`check_tmdb_github.py -G`，会在`Tmdb_host_ipv4`文件，及`Tmdb_host_ipv6`文件中追加 Github IPv4 地址；\n\n## 其他\n\n- [x] 自学薄弱编程基础，大部分代码基于AI辅助生成，此项目过程中，主要人为解决的是：通过 [dnschecker](https://dnschecker.org/) 提交时，通过计算出正确的udp参数，获取正确的csrftoken，携带正确的referer提交！\n- [x] README.md 及 部分代码 参考[GitHub520](https://github.com/521xueweihan/GitHub520)\n- [x] * 本项目仅在本机测试通过，如有问题欢迎提 [issues](https://github.com/cnwikee/CheckTMDB/issues/new)\n"
  },
  {
    "path": "README_template.md",
    "content": "# CheckTMDB\n\n每日自动更新TMDB，themoviedb、thetvdb 国内可正常连接IP，解决DNS污染，供tinyMediaManager(TMM削刮器)、Kodi的刮削器、群晖VideoStation的海报墙、Plex Server的元数据代理、Emby Server元数据下载器、Infuse、Nplayer等正常削刮影片信息。\n\n## 一、前景\n\n自从我早两年使用了黑群NAS以后，下了好多的电影电视剧，发现电视端无法生成正常的海报墙。查找资料得知应该是 themoviedb.org、tmdb.org 无法正常访问，因为DNS受到了污染无法正确解析到TMDB的IP，故依葫芦画瓢写了一个python脚本，每日定时通过[dnschecker](https://dnschecker.org/)查询出最佳IP，并自动同步到路由器外挂hosts，可正常削刮。\n\n**本项目无需安装任何程序**\n\n通过修改本地、路由器 hosts 文件，即可正常削刮影片信息。\n\n## 文件地址\n\n- TMDB IPv4 hosts：`https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4` ，[链接](https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4)\n- TMDB IPv6 hosts：`https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6` ，[链接](https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6)\n\n## 二、使用方法\n\n### 2.1 手动方式\n\n#### 2.1.1 IPv4地址复制下面的内容\n\n```bash\n{ipv4_hosts_str}\n```\n\n该内容会自动定时更新， 数据更新时间：{update_time}\n\n#### 2.1.2 IPv6地址复制下面的内容\n\n```bash\n{ipv6_hosts_str}\n```\n\n该内容会自动定时更新， 数据更新时间：{update_time}\n\n> [!NOTE]\n> 由于项目搭建在Github Aciton，延时数据获取于Github Action 虚拟主机网络环境，请自行测试可用性，建议使用本地网络环境自动设置。\n\n#### 2.1.3 修改 hosts 文件\n\nhosts 文件在每个系统的位置不一，详情如下：\n\n- Windows 系统：`C:\\Windows\\System32\\drivers\\etc\\hosts`\n- Linux 系统：`/etc/hosts`\n- Mac（苹果电脑）系统：`/etc/hosts`\n- Android（安卓）系统：`/system/etc/hosts`\n- iPhone（iOS）系统：`/etc/hosts`\n\n修改方法，把第一步的内容复制到文本末尾：\n\n1. Windows 使用记事本。\n2. Linux、Mac 使用 Root 权限：`sudo vi /etc/hosts`。\n3. iPhone、iPad 须越狱、Android 必须要 root。\n\n#### 2.1.4 激活生效\n\n大部分情况下是直接生效，如未生效可尝试下面的办法，刷新 DNS：\n\n1. Windows：在 CMD 窗口输入：`ipconfig /flushdns`\n\n2. Linux 命令：`sudo nscd restart`，如报错则须安装：`sudo apt install nscd` 或 `sudo /etc/init.d/nscd restart`\n\n3. Mac 命令：`sudo killall -HUP mDNSResponder`\n\n**Tips：** 上述方法无效可以尝试重启机器。\n\n### 2.2 自动方式\n\n#### 2.2.1 安装 SwitchHosts\n\nGitHub 发行版：https://github.com/oldj/SwitchHosts/releases/latest\n\n#### 2.2.2 添加 hosts\n\n点击左上角“+”，并进行以下配置：\n\n- Hosts 类型：`远程`\n- Hosts 标题：任意\n- URL\n    - IPv4：`https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4`\n    - IPv6：`https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6`\n- 自动刷新：`1 小时`\n\n#### 2.2.3 启用 hosts\n\n在左侧边栏启用 hosts，首次使用时软件会自动获取内容。如果无法连接到 GitHub，可以尝试用同样的方法添加 [GitHub520](https://github.com/521xueweihan/GitHub520) hosts。\n\n## 三、参数说明\n\n1. 直接执行`check_tmdb_github.py`脚本，同时查询IPv4及IPv6地址，目录生成`Tmdb_host_ipv4`文件，及`Tmdb_host_ipv6`文件；\n2. 带`-G` 参数执行：`check_tmdb_github.py -G`，会在`Tmdb_host_ipv4`文件，及`Tmdb_host_ipv6`文件中追加 Github IPv4 地址；\n\n## 其他\n\n- [x] 自学薄弱编程基础，大部分代码基于AI辅助生成，此项目过程中，主要人为解决的是：通过 [dnschecker](https://dnschecker.org/) 提交时，通过计算出正确的udp参数，获取正确的csrftoken，携带正确的referer提交！\n- [x] README.md 及 部分代码 参考[GitHub520](https://github.com/521xueweihan/GitHub520)\n- [x] * 本项目仅在本机测试通过，如有问题欢迎提 [issues](https://github.com/cnwikee/CheckTMDB/issues/new)\n"
  },
  {
    "path": "Tmdb_host_ipv4",
    "content": "# Tmdb Hosts Start\n18.160.10.119               tmdb.org\n3.170.19.81                 api.tmdb.org\n3.170.42.125                files.tmdb.org\n3.171.38.81                 themoviedb.org\n3.170.19.106                api.themoviedb.org\n3.171.38.81                 www.themoviedb.org\n3.170.3.12                  auth.themoviedb.org\n185.93.1.249                image.tmdb.org\n169.150.236.107             images.tmdb.org\n98.82.155.134               imdb.com\n18.67.70.32                 www.imdb.com\n98.82.155.134               secure.imdb.com\n18.67.70.32                 s.media-imdb.com\n98.82.158.179               us.dd.imdb.com\n18.67.70.32                 www.imdb.to\n98.82.155.134               origin-www.imdb.com\n23.207.202.24               ia.media-imdb.com\n3.171.75.86                 thetvdb.com\n3.170.35.80                 api.thetvdb.com\n151.101.1.16                ia.media-imdb.com\n151.101.1.16                f.media-amazon.com\n18.67.76.79                 imdb-video.media-imdb.com\n148.113.196.166             webservice.fanart.tv\n172.67.74.146               images.fanart.tv\n158.69.209.125              assets.fanart.tv\n172.67.74.146               fanart.tv\n104.20.14.80                api.trakt.tv\n104.20.14.80                trakt.tv\n# Update time: 2026-04-24T19:13:55+08:00\n# IPv4 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4\n# IPv6 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6\n# Star me: https://github.com/cnwikee/CheckTMDB\n# Tmdb Hosts End\n"
  },
  {
    "path": "Tmdb_host_ipv6",
    "content": "# Tmdb Hosts Start\n2600:9000:250a:8c00:10:db24:6940:93a1              tmdb.org\n2600:9000:286d:2e00:10:fb02:4000:93a1              api.tmdb.org\n2600:9000:2870:ca00:5:da10:7440:93a1               files.tmdb.org\n2600:9000:28a0:e00:e:5373:440:93a1                 themoviedb.org\n2600:9000:286d:3000:c:174a:c400:93a1               api.themoviedb.org\n2600:9000:28a0:9200:e:5373:440:93a1                www.themoviedb.org\n2600:9000:286a:b000:16:e4a1:eb00:93a1              auth.themoviedb.org\n2400:52e0:1a00::1029:1                             image.tmdb.org\n2400:52e0:1a00::1233:1                             images.tmdb.org\n2a04:4e42:400::272                                 ia.media-imdb.com\n2a04:4e42:400::272                                 ia.media-imdb.com\n2a04:4e42:400::272                                 f.media-amazon.com\n2606:4700:20::681a:d7e                             images.fanart.tv\n2606:4700:20::681a:d7e                             fanart.tv\n2606:4700:10::6814:e50                             api.trakt.tv\n2606:4700:10::6814:e50                             trakt.tv\n# Update time: 2026-04-24T19:13:55+08:00\n# IPv4 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4\n# IPv6 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6\n# Star me: https://github.com/cnwikee/CheckTMDB\n# Tmdb Hosts End\n"
  },
  {
    "path": "check_tmdb_github.py",
    "content": "import requests\nfrom time import sleep\nimport random\nimport time\nimport os\nimport sys\nfrom datetime import datetime, timezone, timedelta\nfrom retry import retry\nimport socket\n\ncountry_code = 'jp' #节点\n\nDOMAINS = [\n    'tmdb.org',\n    'api.tmdb.org',\n    'files.tmdb.org',\n    'themoviedb.org',\n    'api.themoviedb.org',\n    'www.themoviedb.org',\n    'auth.themoviedb.org',\n    'image.tmdb.org',\n    'images.tmdb.org',\n    'imdb.com',\n    'www.imdb.com',\n    'secure.imdb.com',\n    's.media-imdb.com',\n    'us.dd.imdb.com',\n    'www.imdb.to',\n    'origin-www.imdb.com',\n    'ia.media-imdb.com',\n    'thetvdb.com',\n    'api.thetvdb.com',\n    'ia.media-imdb.com',\n    'f.media-amazon.com',\n    'imdb-video.media-imdb.com'\n]\n\nTmdb_Host_TEMPLATE = \"\"\"# Tmdb Hosts Start\n{content}\n# Update time: {update_time}\n# IPv4 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4\n# IPv6 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6\n# Star me: https://github.com/cnwikee/CheckTMDB\n# Tmdb Hosts End\\n\"\"\"\n\ndef write_file(ipv4_hosts_content: str, ipv6_hosts_content: str, update_time: str) -> bool:\n    output_doc_file_path = os.path.join(os.path.dirname(__file__), \"README.md\")\n    template_path = os.path.join(os.path.dirname(__file__), \"README_template.md\")\n    \n    if os.path.exists(output_doc_file_path):\n        with open(output_doc_file_path, \"r\", encoding='utf-8') as old_readme_md:\n            old_readme_md_content = old_readme_md.read()            \n            if old_readme_md_content:\n                old_ipv4_block = old_readme_md_content.split(\"```bash\")[1].split(\"```\")[0].strip()\n                old_ipv4_hosts = old_ipv4_block.split(\"# Update time:\")[0].strip()\n\n                old_ipv6_block = old_readme_md_content.split(\"```bash\")[2].split(\"```\")[0].strip()\n                old_ipv6_hosts = old_ipv6_block.split(\"# Update time:\")[0].strip()\n                \n                if ipv4_hosts_content != \"\":\n                    new_ipv4_hosts = ipv4_hosts_content.split(\"# Update time:\")[0].strip()\n                    if old_ipv4_hosts == new_ipv4_hosts:\n                        print(\"ipv4 host not change\")\n                        w_ipv4_block = old_ipv4_block\n                    else:\n                        w_ipv4_block = ipv4_hosts_content\n                        write_host_file(ipv4_hosts_content, 'ipv4')\n                else:\n                    print(\"ipv4_hosts_content is null\")\n                    w_ipv4_block = old_ipv4_block\n\n                if ipv6_hosts_content != \"\":\n                    new_ipv6_hosts = ipv6_hosts_content.split(\"# Update time:\")[0].strip()\n                    if old_ipv6_hosts == new_ipv6_hosts:\n                        print(\"ipv6 host not change\")\n                        w_ipv6_block = old_ipv6_block\n                    else:\n                        w_ipv6_block = ipv6_hosts_content\n                        write_host_file(ipv6_hosts_content, 'ipv6')\n                else:\n                    print(\"ipv6_hosts_content is null\")\n                    w_ipv6_block = old_ipv6_block\n                \n                with open(template_path, \"r\", encoding='utf-8') as temp_fb:\n                    template_str = temp_fb.read()\n                    hosts_content = template_str.format(ipv4_hosts_str=w_ipv4_block, ipv6_hosts_str=w_ipv6_block, update_time=update_time)\n\n                    with open(output_doc_file_path, \"w\", encoding='utf-8') as output_fb:\n                        output_fb.write(hosts_content)\n                return True\n        return False\n               \n                \n\ndef write_host_file(hosts_content: str, filename: str) -> None:\n    output_file_path = os.path.join(os.path.dirname(__file__), \"Tmdb_host_\" + filename)\n    if len(sys.argv) >= 2 and sys.argv[1].upper() == '-G':\n        print(\"\\n~追加Github ip~\")\n        hosts_content = hosts_content + \"\\n\" + (get_github_hosts() or \"\")\n    with open(output_file_path, \"w\", encoding='utf-8') as output_fb:\n        output_fb.write(hosts_content)\n        print(\"\\n~最新TMDB\" + filename + \"地址已更新~\")\n\ndef get_github_hosts() -> None:\n    github_hosts_urls = [\n        \"https://hosts.gitcdn.top/hosts.txt\",\n        \"https://raw.githubusercontent.com/521xueweihan/GitHub520/refs/heads/main/hosts\",\n        \"https://gitlab.com/ineo6/hosts/-/raw/master/next-hosts\",\n        \"https://raw.githubusercontent.com/ittuann/GitHub-IP-hosts/refs/heads/main/hosts_single\"\n    ]\n    all_failed = True\n    for url in github_hosts_urls:\n        try:\n            response = requests.get(url)\n            if response.status_code == 200:\n                github_hosts = response.text\n                all_failed = False\n                break\n            else:\n                print(f\"\\n从 {url} 获取GitHub hosts失败: HTTP {response.status_code}\")\n        except Exception as e:\n            print(f\"\\n从 {url} 获取GitHub hosts时发生错误: {str(e)}\")\n    if all_failed:\n        print(\"\\n获取GitHub hosts失败: 所有Url项目失败！\")\n        return\n    else:\n        return github_hosts\n\ndef is_ci_environment():\n    ci_environment_vars = {\n        'GITHUB_ACTIONS': 'true',\n        'TRAVIS': 'true',\n        'CIRCLECI': 'true'\n    }\n    for env_var, expected_value in ci_environment_vars.items():\n        env_value = os.getenv(env_var)\n        if env_value is not None and str(env_value).lower() == expected_value.lower():\n            return True\n    return False\n    \n\n@retry(tries=3)\ndef get_csrf_token(udp):\n    \"\"\"获取CSRF Token\"\"\"\n    try:\n        url = f'https://dnschecker.org/ajax_files/gen_csrf.php?udp={udp}'\n        headers = {\n            'referer': 'https://dnschecker.org/country/{country_code}/','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0'\n        }\n        \n        response = requests.get(url, headers=headers)\n        if response.status_code == 200:\n            csrf = response.json().get('csrf')\n            print(f\"获取到的CSRF Token: {csrf}\")\n            return csrf\n        else:\n            print(f\"获取CSRF Token失败，HTTP状态码: {response.status_code}\")\n            return None\n    except Exception as e:\n        print(f\"获取CSRF Token时发生错误: {str(e)}\")\n        return None\n\n@retry(tries=3)\ndef get_domain_ips(domain, csrf_token, udp, argument):\n    url = f'https://dnschecker.org/ajax_files/api/220/{argument}/{domain}?dns_key=country&dns_value={country_code}&v=0.36&cd_flag=1&upd={udp}'\n    headers = {'csrftoken': csrf_token, 'referer':f'https://dnschecker.org/country/{country_code}/','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0'}\n    \n    try:\n        response = requests.get(url, headers=headers)\n        if response.status_code == 200:\n            data = response.json()\n            if 'result' in data and 'ips' in data['result']:\n                ips_str = data['result']['ips']\n                if '<br />' in ips_str:\n                    return [ip.strip() for ip in ips_str.split('<br />') if ip.strip()]\n                else:\n                    return [ips_str.strip()] if ips_str.strip() else []\n            else:\n                print(f\"获取 {domain} 的IP列表失败：返回数据格式不正确\")\n                return []\n        else:\n            print(f\"获取 {domain} 的IP列表失败，HTTP状态码: {response.status_code}\")\n            return []\n    except Exception as e:\n        print(f\"获取 {domain} 的IP列表时发生错误: {str(e)}\")\n        return []\n\ndef ping_ip(ip, port=80):\n    print(f\"使用TCP连接测试IP地址的延迟（毫秒）\")\n    try:\n        print(f\"\\n开始 ping {ip}...\")\n        start_time = time.time()\n        with socket.create_connection((ip, port), timeout=2) as sock:\n            latency = (time.time() - start_time) * 1000  # 转换为毫秒\n            print(f\"IP: {ip} 的平均延迟: {latency}ms\")\n            return latency\n    except Exception as e:\n        print(f\"Ping {ip} 时发生错误: {str(e)}\")\n        return float('inf')\n    \ndef find_fastest_ip(ips):\n    \"\"\"找出延迟最低的IP地址\"\"\"\n    if not ips:\n        return None\n    \n    fastest_ip = None\n    min_latency = float('inf')\n    ip_latencies = []  # 存储所有IP及其延迟\n    \n    for ip in ips:\n        ip = ip.strip()\n        if not ip:\n            continue\n            \n        print(f\"正在测试 IP: {ip}\")\n        latency = ping_ip(ip)\n        ip_latencies.append((ip, latency))\n        print(f\"IP: {ip} 延迟: {latency}ms\")\n        \n        if latency < min_latency:\n            min_latency = latency\n            fastest_ip = ip\n            \n        sleep(0.5) \n    \n    print(\"\\n所有IP延迟情况:\")\n    for ip, latency in ip_latencies:\n        print(f\"IP: {ip} - 延迟: {latency}ms\")\n    \n    if fastest_ip:\n        print(f\"\\n最快的IP是: {fastest_ip}，延迟: {min_latency}ms\")\n    \n    return fastest_ip\n\ndef main():\n    print(\"开始检测TMDB相关域名的最快IP...\")\n    udp = random.random() * 1000 + (int(time.time() * 1000) % 1000)\n    # 获取CSRF Token\n    csrf_token = get_csrf_token(udp)\n    if not csrf_token:\n        print(\"无法获取CSRF Token，程序退出\")\n        sys.exit(1)\n\n    ipv4_ips, ipv6_ips, ipv4_results, ipv6_results = [], [], [], []\n\n    for domain in DOMAINS:\n        print(f\"\\n正在处理域名: {domain}\")       \n        ipv4_ips = get_domain_ips(domain, csrf_token, udp, \"A\")\n        ipv6_ips = get_domain_ips(domain, csrf_token, udp, \"AAAA\")\n\n        if not ipv4_ips and not ipv6_ips:\n            print(f\"无法获取 {domain} 的IP列表，跳过该域名\")\n            continue\n        \n        # 处理 IPv4 地址\n        if ipv4_ips:\n            fastest_ipv4 = find_fastest_ip(ipv4_ips)\n            if fastest_ipv4:\n                ipv4_results.append([fastest_ipv4, domain])\n                print(f\"域名 {domain} 的最快IPv4是: {fastest_ipv4}\")\n            else:\n                ipv4_results.append([ipv4_ips[0], domain])\n        \n        # 处理 IPv6 地址\n        if ipv6_ips:\n            fastest_ipv6 = find_fastest_ip(ipv6_ips)\n            if fastest_ipv6:\n                ipv6_results.append([fastest_ipv6, domain])\n                print(f\"域名 {domain} 的最快IPv6是: {fastest_ipv6}\")\n            else:\n                # 兜底：可能存在无法正确获取 fastest_ipv6 的情况，则将第一个IP赋值\n                ipv6_results.append([ipv6_ips[0], domain])\n        \n        sleep(1)  # 避免请求过于频繁\n    \n    # 保存结果到文件\n    if not ipv4_results and not ipv6_results:\n        print(f\"程序出错：未获取任何domain及对应IP，请检查接口~\")\n        sys.exit(1)\n\n    # 生成更新时间\n    update_time = datetime.now(timezone(timedelta(hours=8))).replace(microsecond=0).isoformat()\n    \n    ipv4_hosts_content = Tmdb_Host_TEMPLATE.format(content=\"\\n\".join(f\"{ip:<27} {domain}\" for ip, domain in ipv4_results), update_time=update_time) if ipv4_results else \"\"\n    ipv6_hosts_content = Tmdb_Host_TEMPLATE.format(content=\"\\n\".join(f\"{ip:<50} {domain}\" for ip, domain in ipv6_results), update_time=update_time) if ipv6_results else \"\"\n\n    write_file(ipv4_hosts_content, ipv6_hosts_content, update_time)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "check_tmdb_github_dnschecked.py",
    "content": "import requests\nfrom time import sleep\nimport random\nimport time\nimport os\nimport sys\nimport re  # 导入正则模块用于IP验证\nfrom datetime import datetime, timezone, timedelta\nfrom retry import retry\nimport socket\n\nDOMAINS = [\n    'tmdb.org',\n    'api.tmdb.org',\n    'files.tmdb.org',\n    'themoviedb.org',\n    'api.themoviedb.org',\n    'www.themoviedb.org',\n    'auth.themoviedb.org',\n    'image.tmdb.org',\n    'images.tmdb.org',\n    'imdb.com',\n    'www.imdb.com',\n    'secure.imdb.com',\n    's.media-imdb.com',\n    'us.dd.imdb.com',\n    'www.imdb.to',\n    'origin-www.imdb.com',\n    'ia.media-imdb.com',\n    'thetvdb.com',\n    'api.thetvdb.com',\n    'ia.media-imdb.com',\n    'f.media-amazon.com',\n    'imdb-video.media-imdb.com',\n    'webservice.fanart.tv',\n    'images.fanart.tv',\n    'assets.fanart.tv',\n    'fanart.tv',\n    'api.trakt.tv',\n    'api-staging.trakt.tv',\n    'trakt.tv'\n]\n\nTmdb_Host_TEMPLATE = \"\"\"# Tmdb Hosts Start\n{content}\n# Update time: {update_time}\n# IPv4 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv4\n# IPv6 Update url: https://raw.githubusercontent.com/cnwikee/CheckTMDB/refs/heads/main/Tmdb_host_ipv6\n# Star me: https://github.com/cnwikee/CheckTMDB\n# Tmdb Hosts End\\n\"\"\"\n\ndef write_file(ipv4_hosts_content: str, ipv6_hosts_content: str, update_time: str) -> bool:\n    output_doc_file_path = os.path.join(os.path.dirname(__file__), \"README.md\")\n    template_path = os.path.join(os.path.dirname(__file__), \"README_template.md\")\n    \n    if os.path.exists(output_doc_file_path):\n        with open(output_doc_file_path, \"r\", encoding='utf-8') as old_readme_md:\n            old_readme_md_content = old_readme_md.read()            \n            if old_readme_md_content:\n                old_ipv4_block = old_readme_md_content.split(\"```bash\")[1].split(\"```\")[0].strip()\n                old_ipv4_hosts = old_ipv4_block.split(\"# Update time:\")[0].strip()\n\n                old_ipv6_block = old_readme_md_content.split(\"```bash\")[2].split(\"```\")[0].strip()\n                old_ipv6_hosts = old_ipv6_block.split(\"# Update time:\")[0].strip()\n                \n                if ipv4_hosts_content != \"\":\n                    new_ipv4_hosts = ipv4_hosts_content.split(\"# Update time:\")[0].strip()\n                    if old_ipv4_hosts == new_ipv4_hosts:\n                        print(\"ipv4 host not change\")\n                        w_ipv4_block = old_ipv4_block\n                    else:\n                        w_ipv4_block = ipv4_hosts_content\n                        write_host_file(ipv4_hosts_content, 'ipv4')\n                else:\n                    print(\"ipv4_hosts_content is null\")\n                    w_ipv4_block = old_ipv4_block\n\n                if ipv6_hosts_content != \"\":\n                    new_ipv6_hosts = ipv6_hosts_content.split(\"# Update time:\")[0].strip()\n                    if old_ipv6_hosts == new_ipv6_hosts:\n                        print(\"ipv6 host not change\")\n                        w_ipv6_block = old_ipv6_block\n                    else:\n                        w_ipv6_block = ipv6_hosts_content\n                        write_host_file(ipv6_hosts_content, 'ipv6')\n                else:\n                    print(\"ipv6_hosts_content is null\")\n                    w_ipv6_block = old_ipv6_block\n                \n                with open(template_path, \"r\", encoding='utf-8') as temp_fb:\n                    template_str = temp_fb.read()\n                    hosts_content = template_str.format(ipv4_hosts_str=w_ipv4_block, ipv6_hosts_str=w_ipv6_block, update_time=update_time)\n\n                    with open(output_doc_file_path, \"w\", encoding='utf-8') as output_fb:\n                        output_fb.write(hosts_content)\n                return True\n        return False\n               \ndef validate_ip(ip):\n    \"\"\"\n    验证IP是否为合法的IPv4或IPv6地址\n    :param ip: 待验证的IP字符串\n    :return: True（合法）/False（非法）\n    \"\"\"\n    # IPv4正则（严格验证：每个段0-255，无前置零（除0.0.0.0等合法场景））\n    ipv4_pattern = r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'\n    # IPv6正则（兼容压缩格式、本地链路地址、IPv4映射地址等所有合法格式）\n    ipv6_pattern = r'^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$'\n    \n    # 忽略大小写验证IPv6，优先验证IPv4\n    if re.match(ipv4_pattern, ip):\n        return True\n    elif re.match(ipv6_pattern, ip, re.IGNORECASE):\n        return True\n    else:\n        return False                \n\ndef write_host_file(hosts_content: str, filename: str) -> None:\n    output_file_path = os.path.join(os.path.dirname(__file__), \"Tmdb_host_\" + filename)\n    if len(sys.argv) >= 2 and sys.argv[1].upper() == '-G':\n        print(\"\\n~追加Github ip~\")\n        hosts_content = hosts_content + \"\\n\" + (get_github_hosts() or \"\")\n    with open(output_file_path, \"w\", encoding='utf-8') as output_fb:\n        output_fb.write(hosts_content)\n        print(\"\\n~最新TMDB\" + filename + \"地址已更新~\")\n\ndef get_github_hosts() -> None:\n    github_hosts_urls = [\n        \"https://hosts.gitcdn.top/hosts.txt\",\n        \"https://raw.githubusercontent.com/521xueweihan/GitHub520/refs/heads/main/hosts\",\n        \"https://gitlab.com/ineo6/hosts/-/raw/master/next-hosts\",\n        \"https://raw.githubusercontent.com/ittuann/GitHub-IP-hosts/refs/heads/main/hosts_single\"\n    ]\n    all_failed = True\n    for url in github_hosts_urls:\n        try:\n            response = requests.get(url)\n            if response.status_code == 200:\n                github_hosts = response.text\n                all_failed = False\n                break\n            else:\n                print(f\"\\n从 {url} 获取GitHub hosts失败: HTTP {response.status_code}\")\n        except Exception as e:\n            print(f\"\\n从 {url} 获取GitHub hosts时发生错误: {str(e)}\")\n    if all_failed:\n        print(\"\\n获取GitHub hosts失败: 所有Url项目失败！\")\n        return\n    else:\n        return github_hosts\n\ndef is_ci_environment():\n    ci_environment_vars = {\n        'GITHUB_ACTIONS': 'true',\n        'TRAVIS': 'true',\n        'CIRCLECI': 'true'\n    }\n    for env_var, expected_value in ci_environment_vars.items():\n        env_value = os.getenv(env_var)\n        if env_value is not None and str(env_value).lower() == expected_value.lower():\n            return True\n    return False\n    \n@retry(tries=3)\ndef get_domain_ips(domain, record_type):\n    \"\"\"\n    从Google DNS获取域名的A/AAAA记录IP列表\n    :param domain: 目标域名（如 tmdb.org）\n    :param record_type: 记录类型（A/AAAA，或数字1/28）\n    :return: 去重后的IP列表\n    \"\"\"\n    all_ips = []  # 存储所有DNS服务器返回的IP\n    \n    print(f\"正在从Google DNS获取 {domain} 的{record_type}记录...\")\n    url = f'https://dns.google/resolve'\n    headers = {\n        \"accept\": \"*/*\",\n        \"accept-encoding\": \"gzip, deflate, br, zstd\",\n        \"accept-language\": \"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6\",\n        \"content-type\": \"application/json; charset=UTF-8\",  # 关键：指定JSON格式负载\n        \"referer\": f\"https://dns.google/query?name={domain}&rr_type={record_type}&ecs=\",\n        \"user-agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0\"\n    }\n\n    params = {\n        'name': domain,\n        'type': record_type\n    }\n\n    # 初始化IP列表（默认空列表，确保后续使用安全）\n    ips_str = []\n\n    try:\n        # 改用GET请求（Google DNS resolve接口标准用法）\n        response = requests.get(url, headers=headers, params=params, timeout=10)\n        response.raise_for_status()  # 主动抛出HTTP错误（如4xx/5xx）\n\n        # 解析JSON响应\n        data = response.json()\n        if not isinstance(data, dict):\n            print(\"返回数据不是字典格式，无法解析\")\n            return all_ips\n\n        # 核心：提取Answer数组中的data字段（IP地址）\n        answer_list = data.get(\"Answer\", [])\n        if not answer_list:\n            print(f\"未找到 {domain} 的{record_type}记录（Answer字段为空）\")\n            return all_ips\n\n        # 遍历Answer数组，提取每个条目的data值（IP）\n        for answer in answer_list:\n            ip = answer.get(\"data\")\n            if not ip:  # 过滤空值\n                continue\n            \n            # 验证IP格式合法性\n            if validate_ip(ip):\n                all_ips.append(ip)\n                print(f\"提取到合法IP：{ip}\")\n            else:\n                print(f\"跳过非法IP格式：{ip}\")\n\n    except requests.exceptions.RequestException as e:\n        # 捕获所有网络/请求异常\n        print(f\"请求Google DNS失败：{e}\")\n        if hasattr(e, 'response') and e.response:\n            print(f\"响应内容：{e.response.text[:500]}\")  # 打印前500字符避免过长\n    except ValueError:\n        # JSON解析失败\n        print(f\"响应内容不是有效的JSON格式：{response.text[:500]}\")\n    time.sleep(1)\n    # 去重并返回（保持列表格式）\n    unique_ips = list(set(all_ips))\n    print(f\"最终提取到 {domain} 的{record_type}记录IP（去重后）：{unique_ips}\")\n    return unique_ips\n\ndef ping_ip(ip, port=80):\n    print(f\"使用TCP连接测试IP地址的延迟（毫秒）\")\n    try:\n        print(f\"\\n开始 ping {ip}...\")\n        start_time = time.time()\n        with socket.create_connection((ip, port), timeout=2) as sock:\n            latency = (time.time() - start_time) * 1000  # 转换为毫秒\n            print(f\"IP: {ip} 的平均延迟: {latency}ms\")\n            return latency\n    except Exception as e:\n        print(f\"Ping {ip} 时发生错误: {str(e)}\")\n        return float('inf')\n    \ndef find_fastest_ip(ips):\n    \"\"\"找出延迟最低的IP地址\"\"\"\n    if not ips:\n        return None\n    \n    fastest_ip = None\n    min_latency = float('inf')\n    ip_latencies = []  # 存储所有IP及其延迟\n    \n    for ip in ips:\n        ip = ip.strip()\n        if not ip:\n            continue\n            \n        print(f\"正在测试 IP: {ip}\")\n        latency = ping_ip(ip)\n        ip_latencies.append((ip, latency))\n        print(f\"IP: {ip} 延迟: {latency}ms\")\n        \n        if latency < min_latency:\n            min_latency = latency\n            fastest_ip = ip\n            \n        sleep(0.5) \n    \n    print(\"\\n所有IP延迟情况:\")\n    for ip, latency in ip_latencies:\n        print(f\"IP: {ip} - 延迟: {latency}ms\")\n    \n    if fastest_ip:\n        print(f\"\\n最快的IP是: {fastest_ip}，延迟: {min_latency}ms\")\n    \n    return fastest_ip\n\ndef main():\n    print(\"开始检测TMDB相关域名的最快IP...\")\n\n    ipv4_ips, ipv6_ips, ipv4_results, ipv6_results = [], [], [], []\n\n    for domain in DOMAINS:\n        print(f\"\\n正在处理域名: {domain}\")       \n        ipv4_ips = get_domain_ips(domain, \"A\")\n        ipv6_ips = get_domain_ips(domain, \"AAAA\")\n\n        if not ipv4_ips and not ipv6_ips:\n            print(f\"无法获取 {domain} 的IP列表，跳过该域名\")\n            continue\n        \n        # 处理 IPv4 地址\n        if ipv4_ips:\n            fastest_ipv4 = find_fastest_ip(ipv4_ips)\n            if fastest_ipv4:\n                ipv4_results.append([fastest_ipv4, domain])\n                print(f\"域名 {domain} 的最快IPv4是: {fastest_ipv4}\")\n            else:\n                ipv4_results.append([ipv4_ips[0], domain])\n        \n        # 处理 IPv6 地址\n        if ipv6_ips:\n            fastest_ipv6 = find_fastest_ip(ipv6_ips)\n            if fastest_ipv6:\n                ipv6_results.append([fastest_ipv6, domain])\n                print(f\"域名 {domain} 的最快IPv6是: {fastest_ipv6}\")\n            else:\n                # 兜底：可能存在无法正确获取 fastest_ipv6 的情况，则将第一个IP赋值\n                ipv6_results.append([ipv6_ips[0], domain])\n        \n        sleep(1)  # 避免请求过于频繁\n    \n    # 保存结果到文件\n    if not ipv4_results and not ipv6_results:\n        print(f\"程序出错：未获取任何domain及对应IP，请检查接口~\")\n        sys.exit(1)\n\n    # 生成更新时间\n    update_time = datetime.now(timezone(timedelta(hours=8))).replace(microsecond=0).isoformat()\n    \n    ipv4_hosts_content = Tmdb_Host_TEMPLATE.format(content=\"\\n\".join(f\"{ip:<27} {domain}\" for ip, domain in ipv4_results), update_time=update_time) if ipv4_results else \"\"\n    ipv6_hosts_content = Tmdb_Host_TEMPLATE.format(content=\"\\n\".join(f\"{ip:<50} {domain}\" for ip, domain in ipv6_results), update_time=update_time) if ipv6_results else \"\"\n\n    write_file(ipv4_hosts_content, ipv6_hosts_content, update_time)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "requirements.txt",
    "content": "requests>=2.31.0\npythonping==1.1.4\nretry==0.9.2\nping3==4.0.8\n"
  }
]