[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [521xueweihan]\ncustom: ['https://hellogithub.com/', 'https://mp.weixin.qq.com/s/xNpX3eCaWL_kNe8oZLxTWA']\n"
  },
  {
    "path": ".github/workflows/GitHub520.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\n\nname: GitHub520\n\non:\n  push:\n  schedule:\n    - cron: '0 */2 * * *'\n\njobs:\n  build:\n\n    runs-on: ubuntu-24.04\n    strategy:\n      matrix:\n        python-version: [3.9]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v2\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        if [ -f actions_requirements.txt ]; then pip install -r actions_requirements.txt; fi\n    - name: run script\n      run: |\n        # fetch new ip content and update readme file\n        python update_ips.py\n    - name: commit\n      id: commit\n      run: |\n        git config --global user.email sunxuebangong@gmail.com\n        git config --global user.name action_bot\n        git add .\n        git commit -m \"update readme content\"\n      continue-on-error: true\n    - name: Check on failures\n      if: steps.commit.outputs.status == 'failure'\n      run: exit 1\n    - name: Push changes\n      uses: ad-m/github-push-action@master\n      with:\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n.idea/\n.DS_Store\n"
  },
  {
    "path": "README.md",
    "content": "# GitHub520\n\n<p align=\"center\">\n<a href=\"https://hellogithub.com/repository/d05ff820bf36470581c02cda5cbd17ea\" target=\"_blank\"><img src=\"https://api.hellogithub.com/v1/widgets/recommend.svg?rid=d05ff820bf36470581c02cda5cbd17ea&claim_uid=8MKvZoxaWt\" alt=\"Featured｜HelloGitHub\" style=\"width: 250px; height: 54px;\" width=\"250\" height=\"54\" /></a><br>\n😘 让你“爱”上 GitHub，解决访问时图裂、加载慢的问题。\n</p>\n\n> 💰服务器将于 2025-12-31 到期，续费需要 831 元/年 [点击扫码赞助](https://raw.hellogithub.com/code.png)，感谢🙏\n\n## 一、介绍\n对 GitHub 说\"爱\"太难了：访问慢、图片加载不出来。\n\n**本项目无需安装任何程序，仅需 5 分钟。**\n\n通过修改本地 hosts 文件，试图解决：\n- GitHub 访问速度慢的问题\n- GitHub 项目中的图片显示不出的问题\n\n让你\"爱\"上 GitHub。\n\n\n\n*注：* 本项目还处于测试阶段，仅在本机测试通过，如有问题欢迎提 [issues](https://github.com/521xueweihan/GitHub520/issues/new)\n\n\n## 二、使用方法\n\n下面的地址无需访问 GitHub 即可获取到最新的 hosts 内容：\n\n- 文件：`https://raw.hellogithub.com/hosts`\n- JSON：`https://raw.hellogithub.com/hosts.json`\n\n### 2.1 手动方式\n\n#### 2.1.1 复制下面的内容\n\n```bash\n# GitHub520 Host Start\n140.82.113.25                 alive.github.com\n20.205.243.168                api.github.com\n140.82.114.22                 api.individual.githubcopilot.com\n185.199.110.133               avatars.githubusercontent.com\n185.199.110.133               avatars0.githubusercontent.com\n185.199.110.133               avatars1.githubusercontent.com\n185.199.110.133               avatars2.githubusercontent.com\n185.199.110.133               avatars3.githubusercontent.com\n185.199.110.133               avatars4.githubusercontent.com\n185.199.110.133               avatars5.githubusercontent.com\n185.199.110.133               camo.githubusercontent.com\n140.82.112.21                 central.github.com\n185.199.110.133               cloud.githubusercontent.com\n20.205.243.165                codeload.github.com\n140.82.114.21                 collector.github.com\n185.199.110.133               desktop.githubusercontent.com\n185.199.110.133               favicons.githubusercontent.com\n140.82.112.3                  gist.github.com\n52.217.75.116                 github-cloud.s3.amazonaws.com\n52.217.119.65                 github-com.s3.amazonaws.com\n16.15.193.93                  github-production-release-asset-2e65be.s3.amazonaws.com\n54.231.232.73                 github-production-repository-file-5c1aeb.s3.amazonaws.com\n52.217.226.249                github-production-user-asset-6210df.s3.amazonaws.com\n192.0.66.2                    github.blog\n20.205.243.166                github.com\n140.82.114.17                 github.community\n185.199.108.154               github.githubassets.com\n151.101.193.194               github.global.ssl.fastly.net\n185.199.111.153               github.io\n185.199.110.133               github.map.fastly.net\n185.199.111.153               githubstatus.com\n140.82.113.26                 live.github.com\n185.199.110.133               media.githubusercontent.com\n185.199.110.133               objects.githubusercontent.com\n13.107.42.16                  pipelines.actions.githubusercontent.com\n185.199.110.133               raw.githubusercontent.com\n185.199.110.133               user-images.githubusercontent.com\n13.107.213.73                 vscode.dev\n140.82.112.22                 education.github.com\n185.199.110.133               private-user-images.githubusercontent.com\n\n\n# Update time: 2026-03-20T14:25:11+08:00\n# Update url: https://raw.hellogithub.com/hosts\n# Star me: https://github.com/521xueweihan/GitHub520\n# GitHub520 Host End\n\n```\n\n该内容会自动定时更新， 数据更新时间：2026-03-20T14:25:11+08:00\n\n#### 2.1.2 修改 hosts 文件\n\nhosts 文件在每个系统的位置不一，详情如下：\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.3 激活生效\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 自动方式（SwitchHosts）\n\n**Tip**：推荐 [SwitchHosts](https://github.com/oldj/SwitchHosts) 工具管理 hosts\n\n以 SwitchHosts 为例，看一下怎么使用的，配置参考下面：\n\n- Hosts 类型: `Remote`\n\n- Hosts 标题: 随意\n\n- URL: `https://raw.hellogithub.com/hosts`\n\n- 自动刷新: 最好选 `1 小时`\n\n如图：\n\n![](./img/switch-hosts.png)\n\n这样每次 hosts 有更新都能及时进行更新，免去手动更新。\n\n### 2.3 一行命令\n\n#### Windows\n\n使用命令需要安装[git bash](https://gitforwindows.org/)\n复制以下命令保存到本地命名为**fetch_github_hosts**\n\n```shell\n_hosts=$(mktemp /tmp/hostsXXX)\nhosts=/c/Windows/System32/drivers/etc/hosts\nremote=https://raw.hellogithub.com/hosts\nreg='/# GitHub520 Host Start/,/# Github520 Host End/d'\n\nsed \"$reg\" $hosts > \"$_hosts\"\ncurl \"$remote\" >> \"$_hosts\"\ncat \"$_hosts\" > \"$hosts\"\n\nrm \"$_hosts\"\n```\n\n在**CMD**中执行以下命令，执行前需要替换**git-bash.exe**和**fetch_github_hosts**为你本地的路径，注意前者为windows路径格式后者为shell路径格式\n\n`\"C:\\Program Files\\Git\\git-bash.exe\" -c \"/c/Users/XXX/fetch_github_hosts\"`\n\n可以将上述命令添加到windows的task schedular（任务计划程序）中以定时执行\n\n#### GNU（Ubuntu/CentOS/Fedora）\n\n`sudo sh -c 'sed -i \"/# GitHub520 Host Start/Q\" /etc/hosts && curl https://raw.hellogithub.com/hosts >> /etc/hosts'`\n\n#### BSD/macOS\n\n`sudo sed -i \"\" \"/# GitHub520 Host Start/,/# Github520 Host End/d\" /etc/hosts && curl https://raw.hellogithub.com/hosts | sudo tee -a /etc/hosts`\n\n将上面的命令添加到 cron，可定时执行。使用前确保 GitHub520 内容在该文件最后部分。\n\n**在 Docker 中运行，若遇到 `Device or resource busy` 错误，可使用以下命令执行**\n\n`cp /etc/hosts ~/hosts.new && sed -i \"/# GitHub520 Host Start/Q\" ~/hosts.new && curl https://raw.hellogithub.com/hosts >> ~/hosts.new && cp -f ~/hosts.new /etc/hosts`\n\n### 2.4 AdGuard 用户（自动方式）\n\n在 **过滤器>DNS 封锁清单>添加阻止列表>添加一个自定义列表**，配置如下：\n\n- 名称：随意\n\n- URL：`https://raw.hellogithub.com/hosts`（和上面 SwitchHosts 使用的一样）\n\n如图：\n\n![](./img/AdGuard-rules.png)\n\n更新间隔在 **设置 > 常规设置 > 过滤器更新间隔（设置一小时一次即可）**，记得勾选上 **使用过滤器和 Hosts 文件以拦截指定域名**\n\n![](./img/AdGuard-rules2.png)\n\n**Tip**：不要添加在 **DNS 允许清单** 内，只能添加在 **DNS 封锁清单** 才管用。 另外，AdGuard for Mac、AdGuard for Windows、AdGuard for Android、AdGuard for IOS 等等 **AdGuard 家族软件** 添加方法均类似。\n\n\n## 三、效果对比\n之前的样子：\n\n![](./img/old.png)\n\n修改完 hosts 的样子：\n\n![](./img/new.png)\n\n\n## TODO\n- [x] 定时自动更新 hosts 内容\n- [x] hosts 内容无变动不会更新\n- [x] 寻到最优 IP 解析结果\n\n## 声明\n<a rel=\"license\" href=\"https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh\"><img alt=\"知识共享许可协议\" style=\"border-width: 0\" src=\"https://licensebuttons.net/l/by-nc-nd/4.0/88x31.png\"></a><br>本作品采用 <a rel=\"license\" href=\"https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh\">署名-非商业性使用-禁止演绎 4.0 国际</a> 进行许可。\n"
  },
  {
    "path": "README_template.md",
    "content": "# GitHub520\n\n<p align=\"center\">\n<a href=\"https://hellogithub.com/repository/d05ff820bf36470581c02cda5cbd17ea\" target=\"_blank\"><img src=\"https://api.hellogithub.com/v1/widgets/recommend.svg?rid=d05ff820bf36470581c02cda5cbd17ea&claim_uid=8MKvZoxaWt\" alt=\"Featured｜HelloGitHub\" style=\"width: 250px; height: 54px;\" width=\"250\" height=\"54\" /></a><br>\n😘 让你“爱”上 GitHub，解决访问时图裂、加载慢的问题。\n</p>\n\n> 💰服务器将于 2025-12-31 到期，续费需要 831 元/年 [点击扫码赞助](https://raw.hellogithub.com/code.png)，感谢🙏\n\n## 一、介绍\n对 GitHub 说\"爱\"太难了：访问慢、图片加载不出来。\n\n**本项目无需安装任何程序，仅需 5 分钟。**\n\n通过修改本地 hosts 文件，试图解决：\n- GitHub 访问速度慢的问题\n- GitHub 项目中的图片显示不出的问题\n\n让你\"爱\"上 GitHub。\n\n\n\n*注：* 本项目还处于测试阶段，仅在本机测试通过，如有问题欢迎提 [issues](https://github.com/521xueweihan/GitHub520/issues/new)\n\n\n## 二、使用方法\n\n下面的地址无需访问 GitHub 即可获取到最新的 hosts 内容：\n\n- 文件：`https://raw.hellogithub.com/hosts`\n- JSON：`https://raw.hellogithub.com/hosts.json`\n\n### 2.1 手动方式\n\n#### 2.1.1 复制下面的内容\n\n```bash\n{hosts_str}\n```\n\n该内容会自动定时更新， 数据更新时间：{update_time}\n\n#### 2.1.2 修改 hosts 文件\n\nhosts 文件在每个系统的位置不一，详情如下：\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.3 激活生效\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 自动方式（SwitchHosts）\n\n**Tip**：推荐 [SwitchHosts](https://github.com/oldj/SwitchHosts) 工具管理 hosts\n\n以 SwitchHosts 为例，看一下怎么使用的，配置参考下面：\n\n- Hosts 类型: `Remote`\n\n- Hosts 标题: 随意\n\n- URL: `https://raw.hellogithub.com/hosts`\n\n- 自动刷新: 最好选 `1 小时`\n\n如图：\n\n![](./img/switch-hosts.png)\n\n这样每次 hosts 有更新都能及时进行更新，免去手动更新。\n\n### 2.3 一行命令\n\n#### Windows\n\n使用命令需要安装[git bash](https://gitforwindows.org/)\n复制以下命令保存到本地命名为**fetch_github_hosts**\n\n```shell\n_hosts=$(mktemp /tmp/hostsXXX)\nhosts=/c/Windows/System32/drivers/etc/hosts\nremote=https://raw.hellogithub.com/hosts\nreg='/# GitHub520 Host Start/,/# Github520 Host End/d'\n\nsed \"$reg\" $hosts > \"$_hosts\"\ncurl \"$remote\" >> \"$_hosts\"\ncat \"$_hosts\" > \"$hosts\"\n\nrm \"$_hosts\"\n```\n\n在**CMD**中执行以下命令，执行前需要替换**git-bash.exe**和**fetch_github_hosts**为你本地的路径，注意前者为windows路径格式后者为shell路径格式\n\n`\"C:\\Program Files\\Git\\git-bash.exe\" -c \"/c/Users/XXX/fetch_github_hosts\"`\n\n可以将上述命令添加到windows的task schedular（任务计划程序）中以定时执行\n\n#### GNU（Ubuntu/CentOS/Fedora）\n\n`sudo sh -c 'sed -i \"/# GitHub520 Host Start/Q\" /etc/hosts && curl https://raw.hellogithub.com/hosts >> /etc/hosts'`\n\n#### BSD/macOS\n\n`sudo sed -i \"\" \"/# GitHub520 Host Start/,/# Github520 Host End/d\" /etc/hosts && curl https://raw.hellogithub.com/hosts | sudo tee -a /etc/hosts`\n\n将上面的命令添加到 cron，可定时执行。使用前确保 GitHub520 内容在该文件最后部分。\n\n**在 Docker 中运行，若遇到 `Device or resource busy` 错误，可使用以下命令执行**\n\n`cp /etc/hosts ~/hosts.new && sed -i \"/# GitHub520 Host Start/Q\" ~/hosts.new && curl https://raw.hellogithub.com/hosts >> ~/hosts.new && cp -f ~/hosts.new /etc/hosts`\n\n### 2.4 AdGuard 用户（自动方式）\n\n在 **过滤器>DNS 封锁清单>添加阻止列表>添加一个自定义列表**，配置如下：\n\n- 名称：随意\n\n- URL：`https://raw.hellogithub.com/hosts`（和上面 SwitchHosts 使用的一样）\n\n如图：\n\n![](./img/AdGuard-rules.png)\n\n更新间隔在 **设置 > 常规设置 > 过滤器更新间隔（设置一小时一次即可）**，记得勾选上 **使用过滤器和 Hosts 文件以拦截指定域名**\n\n![](./img/AdGuard-rules2.png)\n\n**Tip**：不要添加在 **DNS 允许清单** 内，只能添加在 **DNS 封锁清单** 才管用。 另外，AdGuard for Mac、AdGuard for Windows、AdGuard for Android、AdGuard for IOS 等等 **AdGuard 家族软件** 添加方法均类似。\n\n\n## 三、效果对比\n之前的样子：\n\n![](./img/old.png)\n\n修改完 hosts 的样子：\n\n![](./img/new.png)\n\n\n## TODO\n- [x] 定时自动更新 hosts 内容\n- [x] hosts 内容无变动不会更新\n- [x] 寻到最优 IP 解析结果\n\n## 声明\n<a rel=\"license\" href=\"https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh\"><img alt=\"知识共享许可协议\" style=\"border-width: 0\" src=\"https://licensebuttons.net/l/by-nc-nd/4.0/88x31.png\"></a><br>本作品采用 <a rel=\"license\" href=\"https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh\">署名-非商业性使用-禁止演绎 4.0 国际</a> 进行许可。\n"
  },
  {
    "path": "actions_requirements.txt",
    "content": "requests-html==0.10.0\nretry==0.9.2\nlxml_html_clean"
  },
  {
    "path": "common.py",
    "content": "#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n#\n#   Author  :   XueWeiHan\n#   E-mail  :   595666367@qq.com\n#   Date    :   2025-01-16 15:27\n#   Desc    :   公共函数\nimport os\nimport json\nfrom typing import Any, Optional\nfrom datetime import datetime, timezone, timedelta\n\nfrom retry import retry\n\nGITHUB_URLS = [\n    'alive.github.com', 'api.github.com', 'api.individual.githubcopilot.com',\n    'avatars.githubusercontent.com', 'avatars0.githubusercontent.com',\n    'avatars1.githubusercontent.com', 'avatars2.githubusercontent.com',\n    'avatars3.githubusercontent.com', 'avatars4.githubusercontent.com',\n    'avatars5.githubusercontent.com', 'camo.githubusercontent.com',\n    'central.github.com', 'cloud.githubusercontent.com', 'codeload.github.com',\n    'collector.github.com', 'desktop.githubusercontent.com',\n    'favicons.githubusercontent.com', 'gist.github.com',\n    'github-cloud.s3.amazonaws.com', 'github-com.s3.amazonaws.com',\n    'github-production-release-asset-2e65be.s3.amazonaws.com',\n    'github-production-repository-file-5c1aeb.s3.amazonaws.com',\n    'github-production-user-asset-6210df.s3.amazonaws.com', 'github.blog',\n    'github.com', 'github.community', 'github.githubassets.com',\n    'github.global.ssl.fastly.net', 'github.io', 'github.map.fastly.net',\n    'githubstatus.com', 'live.github.com', 'media.githubusercontent.com',\n    'objects.githubusercontent.com', 'pipelines.actions.githubusercontent.com',\n    'raw.githubusercontent.com', 'user-images.githubusercontent.com',\n    'vscode.dev', 'education.github.com', 'private-user-images.githubusercontent.com'\n]\n\nHOSTS_TEMPLATE = \"\"\"# GitHub520 Host Start\n{content}\n\n# Update time: {update_time}\n# Update url: https://raw.hellogithub.com/hosts\n# Star me: https://github.com/521xueweihan/GitHub520\n# GitHub520 Host End\\n\"\"\"\n\n\n@retry(tries=3)\ndef get_json(session: Any) -> Optional[list]:\n    url = 'https://raw.hellogithub.com/hosts.json'\n    try:\n        rs = session.get(url)\n        data = json.loads(rs.text)\n        return data\n    except Exception as ex:\n        print(f\"get: {url}, error: {ex}\")\n        raise Exception\n\n\ndef write_file(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__),\n                                 \"README_template.md\")\n    write_host_file(hosts_content)\n    if os.path.exists(output_doc_file_path):\n        with open(output_doc_file_path, \"r\") as old_readme_fb:\n            old_content = old_readme_fb.read()\n            if old_content:\n                old_hosts = old_content.split(\"```bash\")[1].split(\"```\")[0].strip()\n                old_hosts = old_hosts.split(\"# Update time:\")[0].strip()\n                hosts_content_hosts = hosts_content.split(\"# Update time:\")[\n                    0].strip()\n                if old_hosts == hosts_content_hosts:\n                    print(\"host not change\")\n                    return False\n\n    with open(template_path, \"r\") as temp_fb:\n        template_str = temp_fb.read()\n        hosts_content = template_str.format(hosts_str=hosts_content,\n                                            update_time=update_time)\n        with open(output_doc_file_path, \"w\") as output_fb:\n            output_fb.write(hosts_content)\n    return True\n\n\ndef write_host_file(hosts_content: str) -> None:\n    output_file_path = os.path.join(os.path.dirname(__file__), 'hosts')\n    with open(output_file_path, \"w\") as output_fb:\n        output_fb.write(hosts_content)\n\n\ndef write_json_file(hosts_list: list) -> None:\n    output_file_path = os.path.join(os.path.dirname(__file__), 'hosts.json')\n    with open(output_file_path, \"w\") as output_fb:\n        json.dump(hosts_list, output_fb)\n\n\ndef write_hosts_content(content: str, content_list: list) -> str:\n    if not content:\n        return \"\"\n    update_time = datetime.now(timezone.utc).astimezone(\n        timezone(timedelta(hours=8))).replace(microsecond=0).isoformat()\n    hosts_content = HOSTS_TEMPLATE.format(content=content,\n                                          update_time=update_time)\n    has_change = write_file(hosts_content, update_time)\n    if has_change:\n        write_json_file(content_list)\n    return hosts_content\n"
  },
  {
    "path": "fetch_ips.py",
    "content": "#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n#   \n#   Author  :   XueWeiHan\n#   E-mail  :   595666367@qq.com\n#   Date    :   2020-05-19 15:27\n#   Desc    :   获取最新的 GitHub 相关域名对应 IP\nimport re\nfrom typing import Any, Dict, List, Optional\nfrom datetime import datetime\nimport sys\nimport asyncio\nimport aiodns\n\nfrom pythonping import ping\nfrom requests_html import HTMLSession\nfrom retry import retry\n\nfrom common import GITHUB_URLS, write_hosts_content\n\n\nPING_TIMEOUT_SEC: int = 1\nDISCARD_LIST: List[str] = [\"1.0.1.1\", \"1.2.1.1\", \"127.0.0.1\"]\n\n\nPING_LIST: Dict[str, int] = dict()\n\n\ndef ping_cached(ip: str) -> int:\n    global PING_LIST\n    if ip in PING_LIST:\n        return PING_LIST[ip]\n    ping_times = [ping(ip, timeout=PING_TIMEOUT_SEC).rtt_avg_ms for _ in range(3)]\n    ping_times.sort()\n    print(f'Ping {ip}: {ping_times} ms')\n    PING_LIST[ip] = ping_times[1] # 取中位数\n    return PING_LIST[ip]\n\n\ndef select_ip_from_list(ip_list: List[str]) -> Optional[str]:\n    if len(ip_list) == 0:\n        return None\n    ping_results = [(ip, ping_cached(ip)) for ip in ip_list]\n    ping_results.sort(key=lambda x: x[1])\n    best_ip = ping_results[0][0]\n    print(f\"{ping_results}, selected {best_ip}\")\n    return best_ip\n\n\n@retry(tries=3)\ndef get_ip_list_from_ipaddress_com(session: Any, github_url: str) -> Optional[List[str]]:\n    url = f'https://sites.ipaddress.com/{github_url}'\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)'\n                      ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/1'\n                      '06.0.0.0 Safari/537.36'}\n    try:\n        rs = session.get(url, headers=headers, timeout=5)\n        pattern = r\"\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b\"\n        ip_list = re.findall(pattern, rs.html.text)\n        return ip_list\n    except Exception as ex:\n        print(f\"get: {url}, error: {ex}\")\n        raise Exception\n\n\nDNS_SERVER_LIST = [\n    \"1.1.1.1\",  # Cloudflare\n    \"8.8.8.8\",  # Google\n    \"101.101.101.101\",  # Quad101\n    \"101.102.103.104\",  # Quad101\n]\n\n\ndef windows_compatibility_check():\n    if sys.platform == \"win32\":\n        # 检查 pycares 是否正常加载\n        try:\n            import pycares\n        except ImportError:\n            raise RuntimeError(\"请先执行 'pip install pycares'\")\n\n\nasync def get_ip_list_from_dns(\n    domain,\n    record_type=\"A\",\n    dns_server_list=[\"1.2.4.8\", \"114.114.114.114\"],\n):\n    # Windows 兼容性检查\n    windows_compatibility_check()\n\n    # 配置 DNS 服务器\n    resolver = aiodns.DNSResolver()\n    resolver.nameservers = dns_server_list\n\n    try:\n        # 执行异步查询\n        result = await resolver.query(domain, record_type)\n        return [answer.host for answer in result]\n    except aiodns.error.DNSError as e:\n        print(f\"{domain}: DNS 查询失败: {e}\")\n        return []\n\n\nasync def get_ip(session: Any, github_url: str) -> Optional[str]:\n    ip_list_web = []\n    try:\n        ip_list_web = get_ip_list_from_ipaddress_com(session, github_url)\n    except Exception as ex:\n        pass\n    ip_list_dns = []\n    try:\n        ip_list_dns = await get_ip_list_from_dns(github_url, dns_server_list=DNS_SERVER_LIST)\n    except Exception as ex:\n        pass\n    ip_list_set = set(ip_list_web + ip_list_dns)\n    for discard_ip in DISCARD_LIST:\n        ip_list_set.discard(discard_ip)\n    ip_list = list(ip_list_set)\n    ip_list.sort()\n    if len(ip_list) == 0:\n        return None\n    print(f\"{github_url}: {ip_list}\")\n    best_ip = select_ip_from_list(ip_list)\n    return best_ip\n\n\nasync def main() -> None:\n    current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')\n    print(f'{current_time} - Start script.')\n    session = HTMLSession()\n    content = \"\"\n    content_list = []\n    for index, github_url in enumerate(GITHUB_URLS):\n        print(f'Start Processing url: {index + 1}/{len(GITHUB_URLS)}, {github_url}')\n        try:\n            ip = await get_ip(session, github_url)\n            if ip is None:\n                print(f\"{github_url}: IP Not Found\")\n                ip = \"# IP Address Not Found\"\n            content += ip.ljust(30) + github_url\n            global PING_LIST\n            if PING_LIST.get(ip) is not None and PING_LIST.get(ip) == PING_TIMEOUT_SEC * 1000:\n                content += \"  # Timeout\"\n            content += \"\\n\"\n            content_list.append((ip, github_url,))\n        except Exception:\n            continue\n\n    write_hosts_content(content, content_list)\n    # print(hosts_content)\n    current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')\n    print(f'{current_time} - End script.')\n\n\nif __name__ == \"__main__\":\n    if sys.platform == \"win32\":\n        # Windows 事件循环策略配置\n        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())\n    asyncio.run(main())"
  },
  {
    "path": "hosts",
    "content": "# GitHub520 Host Start\n140.82.113.25                 alive.github.com\n20.205.243.168                api.github.com\n140.82.114.22                 api.individual.githubcopilot.com\n185.199.110.133               avatars.githubusercontent.com\n185.199.110.133               avatars0.githubusercontent.com\n185.199.110.133               avatars1.githubusercontent.com\n185.199.110.133               avatars2.githubusercontent.com\n185.199.110.133               avatars3.githubusercontent.com\n185.199.110.133               avatars4.githubusercontent.com\n185.199.110.133               avatars5.githubusercontent.com\n185.199.110.133               camo.githubusercontent.com\n140.82.112.21                 central.github.com\n185.199.110.133               cloud.githubusercontent.com\n20.205.243.165                codeload.github.com\n140.82.114.21                 collector.github.com\n185.199.110.133               desktop.githubusercontent.com\n185.199.110.133               favicons.githubusercontent.com\n140.82.112.3                  gist.github.com\n52.217.75.116                 github-cloud.s3.amazonaws.com\n52.217.119.65                 github-com.s3.amazonaws.com\n16.15.193.93                  github-production-release-asset-2e65be.s3.amazonaws.com\n54.231.232.73                 github-production-repository-file-5c1aeb.s3.amazonaws.com\n52.217.226.249                github-production-user-asset-6210df.s3.amazonaws.com\n192.0.66.2                    github.blog\n20.205.243.166                github.com\n140.82.114.17                 github.community\n185.199.108.154               github.githubassets.com\n151.101.193.194               github.global.ssl.fastly.net\n185.199.111.153               github.io\n185.199.110.133               github.map.fastly.net\n185.199.111.153               githubstatus.com\n140.82.113.26                 live.github.com\n185.199.110.133               media.githubusercontent.com\n185.199.110.133               objects.githubusercontent.com\n13.107.42.16                  pipelines.actions.githubusercontent.com\n185.199.110.133               raw.githubusercontent.com\n185.199.110.133               user-images.githubusercontent.com\n13.107.213.73                 vscode.dev\n140.82.112.22                 education.github.com\n185.199.110.133               private-user-images.githubusercontent.com\n\n\n# Update time: 2026-03-20T14:25:11+08:00\n# Update url: https://raw.hellogithub.com/hosts\n# Star me: https://github.com/521xueweihan/GitHub520\n# GitHub520 Host End\n"
  },
  {
    "path": "hosts.json",
    "content": "[[\"140.82.113.25\", \"alive.github.com\"], [\"20.205.243.168\", \"api.github.com\"], [\"140.82.114.22\", \"api.individual.githubcopilot.com\"], [\"185.199.110.133\", \"avatars.githubusercontent.com\"], [\"185.199.110.133\", \"avatars0.githubusercontent.com\"], [\"185.199.110.133\", \"avatars1.githubusercontent.com\"], [\"185.199.110.133\", \"avatars2.githubusercontent.com\"], [\"185.199.110.133\", \"avatars3.githubusercontent.com\"], [\"185.199.110.133\", \"avatars4.githubusercontent.com\"], [\"185.199.110.133\", \"avatars5.githubusercontent.com\"], [\"185.199.110.133\", \"camo.githubusercontent.com\"], [\"140.82.112.21\", \"central.github.com\"], [\"185.199.110.133\", \"cloud.githubusercontent.com\"], [\"20.205.243.165\", \"codeload.github.com\"], [\"140.82.114.21\", \"collector.github.com\"], [\"185.199.110.133\", \"desktop.githubusercontent.com\"], [\"185.199.110.133\", \"favicons.githubusercontent.com\"], [\"140.82.112.3\", \"gist.github.com\"], [\"52.217.75.116\", \"github-cloud.s3.amazonaws.com\"], [\"52.217.119.65\", \"github-com.s3.amazonaws.com\"], [\"16.15.193.93\", \"github-production-release-asset-2e65be.s3.amazonaws.com\"], [\"54.231.232.73\", \"github-production-repository-file-5c1aeb.s3.amazonaws.com\"], [\"52.217.226.249\", \"github-production-user-asset-6210df.s3.amazonaws.com\"], [\"192.0.66.2\", \"github.blog\"], [\"20.205.243.166\", \"github.com\"], [\"140.82.114.17\", \"github.community\"], [\"185.199.108.154\", \"github.githubassets.com\"], [\"151.101.193.194\", \"github.global.ssl.fastly.net\"], [\"185.199.111.153\", \"github.io\"], [\"185.199.110.133\", \"github.map.fastly.net\"], [\"185.199.111.153\", \"githubstatus.com\"], [\"140.82.113.26\", \"live.github.com\"], [\"185.199.110.133\", \"media.githubusercontent.com\"], [\"185.199.110.133\", \"objects.githubusercontent.com\"], [\"13.107.42.16\", \"pipelines.actions.githubusercontent.com\"], [\"185.199.110.133\", \"raw.githubusercontent.com\"], [\"185.199.110.133\", \"user-images.githubusercontent.com\"], [\"13.107.213.73\", \"vscode.dev\"], [\"140.82.112.22\", \"education.github.com\"], [\"185.199.110.133\", \"private-user-images.githubusercontent.com\"]]"
  },
  {
    "path": "requirements.txt",
    "content": "requests-html==0.10.0\npythonping==1.1.4\nretry==0.9.2\nlxml_html_clean\naiodns==3.0.0"
  },
  {
    "path": "update_ips.py",
    "content": "#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n#\n#   Author  :   XueWeiHan\n#   E-mail  :   595666367@qq.com\n#   Date    :   2025-01-16 15:27\n#   Desc    :   GitHub Action 运行的脚本\nimport json\nfrom typing import Any, Optional\n\nfrom retry import retry\nfrom requests_html import HTMLSession\n\nfrom common import write_hosts_content\n\n\n@retry(tries=3)\ndef get_json(session: Any) -> Optional[list]:\n    url = 'https://raw.hellogithub.com/hosts.json'\n    try:\n        rs = session.get(url)\n        data = json.loads(rs.text)\n        return data\n    except Exception as ex:\n        print(f\"get: {url}, error: {ex}\")\n        raise Exception\n\n\ndef main() -> None:\n    print('Start script.')\n    session = HTMLSession()\n    content = \"\"\n    content_list = get_json(session)\n    for item in content_list:\n        content += item[0].ljust(30) + item[1] + \"\\n\"\n    hosts_content = write_hosts_content(content, content_list)\n    print(hosts_content)\n    print('End script.')\n\n\nif __name__ == '__main__':\n    main()\n"
  }
]