├── .github ├── ISSUE_TEMPLATE │ ├── others.md │ └── queryhosts.md └── workflows │ ├── updateHostAsset.yml │ ├── replyIssueCreate.yml │ └── createIssue.yml ├── README.md ├── socket_query.py ├── LICENSE.txt └── github.py /.github/ISSUE_TEMPLATE/others.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Others 3 | about: 不属于host查询的其它问题 4 | title: "[Issue]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 建议标题以`[Issue]`开头,否则机器人会自动回复hosts 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/queryhosts.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: QueryHosts 3 | about: 查询host 4 | title: "[Host]YYYY-MM-dd" 5 | labels: host-query 6 | assignees: '' 7 | 8 | --- 9 | 10 | + 建议在查询之前看看是否已经存在日期靠近的host,直接使用即可 11 | + 建议标题为`[Host]YYYY-MM-dd`,比如`[Host]2021-01-01` 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GithubHost 2 | ## 前言 3 | 因为有几个域名被DNS污染了,GitHub老是看不见图,遂有了这个工程。虽然没大用是了。。。 4 | 5 | ## 广而告之 6 | 现在主流浏览器基本上都支持基于HTTPS的DNS,很大程度上可以替代本项目。 7 | 你可以根据实际情况进行选择。 8 | 9 | ## 功能 10 | 利用海外的机器进行相关网站的DNS查询, 将得到的结果发布到issue页面和release附件。 11 | + 支持周期性自动发布 12 | + 每月1/16号发布issue 13 | + 支持自动回复 14 | + 如果想查询最新的host,可以自己开个issue,自动回复。 15 | + 举例[issue #1](https://github.com/ButterAndButterfly/GithubHost/issues/1) 16 | + 支持通过http链接获取host文件 17 | + 你可以通过以下的地址获取附件中的host文件 18 | + Github源地址: 19 | + Github镜像: 20 | + host文件将由Github Actions机器人每天定时刷新,当有issue提交时也会触发构建 21 | + 支持通过外链触发host发布(测试功能,随时可能撤销) 22 | + 免去登录建issue的烦恼 23 | + 频率最多1次/小时, 请勿滥用 24 | + [点一下这里](https://github-helper.vercel.app/host), 然后issue区、release区均会更新 25 | 26 | ## 查看 27 | + 请移步[issue](https://github.com/ButterAndButterfly/GithubHost/issues/)页面。 28 | + 更改hosts后,注意`ipconfig /flushdns`刷新DNS缓存。 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/updateHostAsset.yml: -------------------------------------------------------------------------------- 1 | name: HostUpdate 2 | 3 | ## 当有提交、有issue创建时触发,或者凌晨1点左右触发 4 | ## 将最新host提交到release的附件 5 | on: 6 | push: 7 | paths: 8 | - "**" 9 | schedule: 10 | - cron: '6 17 * * *' 11 | issues: 12 | types: [opened] 13 | 14 | jobs: 15 | permission: 16 | name: permission check 17 | runs-on: ubuntu-latest 18 | if: ${{ github.actor != 'github-actions[bot]' || github.event_name == 'schedule' }} 19 | steps: 20 | - name: check permission 21 | run: | 22 | echo ${{ github.actor }} 23 | echo ${{ github.event_name }} 24 | echo permission pass 25 | 26 | build: 27 | 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - name: Set up Python 34 | uses: actions/setup-python@v5 35 | with: 36 | python-version: 3.13 37 | 38 | - name: Install dependencies 39 | run: | 40 | python -m pip install --upgrade pip 41 | pip install requests 42 | pip install urllib3 43 | 44 | - name: Run tasks 45 | env: 46 | MY_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | MY_OWNER: ${{ github.repository_owner }} 48 | MY_REPOSITORY: ${{ github.repository }} 49 | run: | 50 | python github.py 51 | -------------------------------------------------------------------------------- /socket_query.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf-8 3 | 4 | import socket 5 | from datetime import datetime, timedelta, timezone 6 | domains = [ 7 | 'api.github.com', 8 | 'avatars.githubusercontent.com', 9 | 'avatars0.githubusercontent.com', 10 | 'camo.githubusercontent.com', 11 | 'cloud.githubusercontent.com', 12 | 'codeload.github.com', 13 | 'favicons.githubusercontent.com', 14 | 'gist.github.com', 15 | 'gist.githubusercontent.com', 16 | 'github.com', 17 | 'github.githubassets.com', 18 | 'marketplace-screenshots.githubusercontent.com', 19 | 'octocaptcha.com', # 用途包括但不限于: 创建organization时的验证码 20 | 'raw.githubusercontent.com', 21 | 'repository-images.githubusercontent.com', 22 | 'uploads.github.com', # 用途包括但不限于: release附件上传 23 | 'user-images.githubusercontent.com', 24 | ] 25 | 26 | def gen_host(): 27 | for domain in domains: 28 | print('Querying ip for domain %s'%domain) 29 | ip = socket.gethostbyname(domain) 30 | print(ip) 31 | yield (ip, domain) 32 | 33 | def get_now_date_str(format_string="%Y-%m-%d %H:%M:%S"):#"%Y-%m-%d %H:%M:%S" 34 | utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc) 35 | bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8))) 36 | str_date = bj_dt.strftime(format_string) 37 | return str_date 38 | 39 | def output_hosts(): 40 | with open('hosts.txt', 'w') as f: 41 | f.write('```\n') 42 | f.write('# GitHub Start \n') 43 | f.write('# Last update at %s (Beijing Time)\n'%(get_now_date_str())) 44 | for ip, domain in gen_host(): 45 | f.write('%s %s\n'%(ip, domain)) 46 | f.write('# GitHub End \n') 47 | f.write('```\n') 48 | if __name__ == '__main__': 49 | output_hosts() 50 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The Star And Thank Author License (SATA) 2 | 3 | Copyright © 2020 NiceLee 4 | 5 | Project Url: https://github.com/nICEnnnnnnnLee/GithubHost 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | And wait, the most important, you shall star/+1/like the project(s) in project url 18 | section above first, and then thank the author(s) in Copyright section. 19 | 20 | Here are some suggested ways: 21 | 22 | - Email the authors a thank-you letter, and make friends with him/her/them. 23 | - Report bugs or issues. 24 | - Tell friends what a wonderful project this is. 25 | - And, sure, you can just express thanks in your mind without telling the world. 26 | 27 | Contributors of this project by forking have the option to add his/her name and 28 | forked project url at copyright and project url sections, but shall not delete 29 | or modify anything else in these two sections. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 37 | THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/replyIssueCreate.yml: -------------------------------------------------------------------------------- 1 | 2 | name: ReplyIssueCreate 3 | 4 | on: 5 | issues: 6 | types: [opened] 7 | 8 | jobs: 9 | permission: 10 | name: permission check 11 | runs-on: ubuntu-latest 12 | if: ${{ github.actor != 'github-actions[bot]' && !startsWith(github.event.issue.title, '[Issue]') }} 13 | steps: 14 | - name: check permission 15 | run: | 16 | echo ${{ github.actor }} 17 | echo permission pass 18 | 19 | 20 | build: 21 | runs-on: ubuntu-latest 22 | needs: permission 23 | steps: 24 | - uses: actions/checkout@v5 25 | 26 | - name: Set up Python 3.13 27 | uses: actions/setup-python@v6 28 | with: 29 | python-version: 3.13 30 | 31 | - name: Generate github hosts 32 | run: | 33 | python socket_query.py 34 | 35 | - uses: actions/github-script@v8 36 | with: 37 | github-token: ${{ secrets.GITHUB_TOKEN }} 38 | script: | 39 | let reg = /\[Host\]\d{4}-\d{2}-\d{2}/ 40 | if (!reg.test("${{ github.event.issue.title }}")) { 41 | const d = new Date(); 42 | const localTime = d.getTime(); 43 | const localOffset = d.getTimezoneOffset() * 60000; //getTimezoneOffset()返回是以分钟为单位,需要转化成ms 44 | const utc = localTime + localOffset; 45 | const offset = 8; //东8区 46 | const beijing = utc + (3600000 * offset); 47 | const nd = new Date(beijing); 48 | let year = "" + nd.getFullYear(); 49 | let month = nd.getMonth() + 1; 50 | month = month >= 10 ? "" + month : "0" + month; 51 | let day = nd.getDate(); 52 | day = day >= 10 ? "" + day : "0" + day; 53 | const title = `[Host]${year}-${month}-${day}`; 54 | console.log("title is: " + title); 55 | github.rest.issues.update({ 56 | owner: context.repo.owner, 57 | repo: context.repo.repo, 58 | issue_number: context.issue.number, 59 | title: title, 60 | }); 61 | } 62 | let fs = require("fs"); 63 | fs.readFile("hosts.txt", 'utf-8', (err, data) => { 64 | github.rest.issues.createComment({ 65 | owner: context.repo.owner, 66 | repo: context.repo.repo, 67 | issue_number: context.issue.number, 68 | body: data 69 | }).then((res) => { 70 | github.rest.issues.lock({ 71 | owner: context.repo.owner, 72 | repo: context.repo.repo, 73 | issue_number: context.issue.number, 74 | lock_reason: "off-topic", 75 | }); 76 | }); 77 | }); 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /.github/workflows/createIssue.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a package using Maven and then publish it to GitHub packages when a release is created 2 | # For more information see: https://o00o.us.to/actions/setup-java#apache-maven-with-a-settings-path 3 | 4 | name: CreateIssue 5 | 6 | on: 7 | schedule: 8 | - cron: '1 0 1,16 * *' 9 | label: 10 | types: [edited] 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v5 18 | 19 | - name: Set up Python 3.13 20 | uses: actions/setup-python@v6 21 | with: 22 | python-version: 3.13 23 | 24 | - name: Generate github hosts 25 | run: | 26 | python socket_query.py 27 | cat hosts.txt 28 | 29 | 30 | - name: Close issues of hosts label, return 'invalid' if latest action runs within an hour 31 | uses: actions/github-script@v8 32 | id: check 33 | with: 34 | github-token: ${{ secrets.GITHUB_TOKEN }} 35 | result-encoding: string 36 | script: | 37 | let response = await github.rest.issues.listForRepo({ 38 | owner: context.repo.owner, 39 | repo: context.repo.repo, 40 | labels: ['host'], 41 | state: 'open' 42 | }); 43 | let data = response['data']; 44 | 45 | try { 46 | data.forEach(function (issue) { 47 | let id = issue['number']; 48 | let updated_at = new Date(issue['updated_at']); 49 | let time_now = new Date(); 50 | let deta = time_now.getTime() - updated_at.getTime(); 51 | console.log('deta: ' + deta); 52 | if (deta < 1000 * 60 * 60) { 53 | throw new Error("latest action runs within an hour"); 54 | } 55 | github.rest.issues.update({ 56 | owner: context.repo.owner, 57 | repo: context.repo.repo, 58 | issue_number: id, 59 | state: 'closed' 60 | }); 61 | }); 62 | return 'valid'; 63 | } catch (e) { 64 | return 'invalid'; 65 | } 66 | - name: Close issues of out-dated host query 67 | uses: actions/github-script@v8 68 | if: ${{ steps.check.outputs.result == 'valid'}} 69 | with: 70 | github-token: ${{ secrets.GITHUB_TOKEN }} 71 | script: | 72 | let response = await github.rest.issues.listForRepo({ 73 | owner: context.repo.owner, 74 | repo: context.repo.repo, 75 | labels: ['host-query'], 76 | state: 'open', 77 | sort: 'updated', 78 | direction: 'asc' 79 | }); 80 | let data = response['data']; 81 | 82 | data.forEach(function (issue) { 83 | let id = issue['number']; 84 | let is2Deal = true; 85 | let updated_at = new Date(issue['updated_at']); 86 | let time_now = new Date(); 87 | let deta = time_now.getTime() - updated_at.getTime(); 88 | console.log('deta: ' + deta); 89 | if (deta < 1000 * 60 * 60 * 24 * 5) { 90 | is2Deal = false; 91 | } 92 | if (is2Deal) { 93 | github.rest.issues.update({ 94 | owner: context.repo.owner, 95 | repo: context.repo.repo, 96 | issue_number: id, 97 | state: 'closed' 98 | }); 99 | } 100 | }); 101 | - name: Create and lock issue to show hosts 102 | uses: actions/github-script@v8 103 | if: ${{ steps.check.outputs.result == 'valid'}} 104 | with: 105 | github-token: ${{ secrets.GITHUB_TOKEN }} 106 | script: | 107 | const date = new Date(); 108 | const year = date.getFullYear().toString(); 109 | const month = (date.getMonth() + 1).toString(); 110 | const day = date.getDate().toString(); 111 | const title = year + '-' + month + '-' + day + ' github hosts'; 112 | var fs = require("fs"); 113 | fs.readFile("hosts.txt", 'utf-8', (err, data) => { 114 | github.rest.issues.create({ 115 | owner: context.repo.owner, 116 | repo: context.repo.repo, 117 | labels: ['host'], 118 | title: title, 119 | body: data 120 | }).then((res) =>{ 121 | github.rest.issues.lock({ 122 | owner: context.repo.owner, 123 | repo: context.repo.repo, 124 | issue_number: res.data.number, 125 | lock_reason: "off-topic", 126 | }); 127 | }); 128 | }); 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /github.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf-8 3 | import os 4 | import requests,json 5 | from datetime import datetime, timedelta, timezone 6 | import socket_query 7 | 8 | class GithubHelper: 9 | 10 | def __init__(self, owner, repo, auth, **args): 11 | self.auth = auth 12 | self.owner = owner 13 | self.repo = repo 14 | 15 | def getLatestRelease(self, **args): 16 | url = 'https://api.github.com/repos/%s/%s/releases/latest'%(self.owner, self.repo) 17 | headers = {'User-Agent': 'None', 'Accept': 'application/vnd.github.v3.+json', "Authorization": "token "+ self.auth} 18 | res = requests.get(url, headers=headers).json() 19 | #print(res) 20 | return res 21 | 22 | 23 | def updateReleaseBody(self, release_id, body, **args): 24 | url = 'https://api.github.com/repos/%s/%s/releases/%d'%(self.owner, self.repo, release_id) 25 | headers = {'User-Agent': 'None', 'Accept': 'application/vnd.github.v3.+json', "Authorization": "token "+ self.auth} 26 | param = { 27 | "body": body 28 | } 29 | res = requests.request("PATCH", url, data=json.dumps(param), headers=headers).json() 30 | #print(res) 31 | return res 32 | 33 | def updateReleaseAsset(self, asset_id, name, **args): 34 | url = 'https://api.github.com/repos/%s/%s/releases/assets/%d'%(self.owner, self.repo, asset_id) 35 | headers = {'User-Agent': 'None', 'Accept': 'application/vnd.github.v3.+json', "Authorization": "token "+ self.auth} 36 | param = '{"name":"%s"}'%name 37 | res = requests.request("PATCH", url, data=param, headers=headers).json() 38 | #print(res) 39 | return res 40 | 41 | def deleteReleaseAsset(self, asset_id, **args): 42 | url = 'https://api.github.com/repos/%s/%s/releases/assets/%d'%(self.owner, self.repo, asset_id) 43 | headers = {'User-Agent': 'None', 'Accept': 'application/vnd.github.v3.+json', "Authorization": "token "+ self.auth} 44 | res = requests.request("DELETE", url, headers=headers) 45 | #print(res.text) 46 | return res 47 | 48 | def uploadReleaseAsset(self, release_id:int, name, data, **args): 49 | url = 'https://uploads.github.com/repos/%s/%s/releases/%d/assets?name=%s'%(self.owner, self.repo, release_id, name) 50 | headers = {'content-type': 'application/octet-stream', 'Accept': 'application/vnd.github.v3.+json', "Authorization": "token "+ self.auth} 51 | res = requests.request("POST", url, data=data, headers=headers).json() 52 | #print(res) 53 | return res 54 | 55 | def replaceLatestReleaseAssets(self, name, data, **args): 56 | if not "releaseInfo" in locals(): 57 | releaseInfo = self.getLatestRelease() 58 | release_id = releaseInfo['id'] 59 | assets = releaseInfo['assets'] 60 | # 删除原来的附件 61 | for asset in assets: 62 | if name == asset["name"]: 63 | self.deleteReleaseAsset(asset['id']) 64 | break; 65 | # 上传新的附件 66 | return self.uploadReleaseAsset(release_id, name, data) 67 | 68 | def shaGithubFile(path): 69 | url = 'https://api.github.com/repos/%s/%s/contents/%s'%(self.owner, self.repo, path) 70 | headers = {'Accept': 'application/vnd.github.v3.star+json', "Authorization": "token "+ self.auth} 71 | res = requests.get(url, headers=headers).json() 72 | return res["sha"] if "sha" in res else None 73 | 74 | def downloadGithubFile(path): 75 | url = 'https://raw.githubusercontent.com/%s/%s/master/%s'%(self.owner, self.repo, path) 76 | headers = {'User-Agent': 'None', 'Accept': 'application/vnd.github.v3.star+json', "Authorization": "token "+ self.auth} 77 | res = requests.get(url, headers=headers) 78 | return res.content 79 | 80 | def uploadGithubFile(data:bytes, path): 81 | sha = shaGithubFile(path, token) 82 | print(sha) 83 | url = 'https://api.github.com/repos/%s/%s/contents/%s'%(self.owner, self.repo, path) 84 | headers = {'User-Agent': 'None', 'Accept': 'application/vnd.github.v3.star+json', "Authorization": "token "+ token} 85 | def gen(): 86 | yield (r'{"message":"Updated at %d.",'%int(time.time()*1000)).encode("utf-8") 87 | if sha: 88 | yield (r'"sha":"%s",'%sha).encode("utf-8") 89 | yield r'"content":"'.encode("utf-8") 90 | yield base64.b64encode(data) 91 | yield r'"}'.encode("utf-8") 92 | res = requests.put(url, data = gen(), headers=headers) 93 | print(res.text) 94 | return res 95 | 96 | def get_now_date_str(format_string="%Y-%m-%d %H:%M:%S"):#"%Y-%m-%d %H:%M:%S" 97 | utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc) 98 | bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8))) 99 | str_date = bj_dt.strftime(format_string) 100 | return str_date 101 | 102 | def load_from_env(): 103 | repo_full_name = os.environ.get('MY_REPOSITORY') 104 | owner = os.environ.get('MY_OWNER') 105 | repo = repo_full_name[len(owner) + 1:] 106 | token = os.environ.get('MY_GITHUB_TOKEN') 107 | github_config = { 108 | "repo": repo, 109 | "owner": owner, 110 | "auth": token, 111 | } 112 | return github_config 113 | 114 | 115 | if __name__ == "__main__": 116 | github_config = load_from_env() 117 | ''' 118 | github_config = { 119 | "repo": "repo", 120 | "owner": "owner", 121 | "auth": "auth", 122 | } 123 | ''' 124 | date_now = get_now_date_str() 125 | lines = [] 126 | lines.append('# GitHub Start') 127 | lines.append('# from https://github.com/ButterAndButterfly/GithubHost') 128 | lines.append('# Last update at %s (Beijing Time)'%date_now) 129 | for ip, domain in socket_query.gen_host(): 130 | lines.append("%s %s"%(ip, domain)) 131 | lines.append('# GitHub End') 132 | data = '\n'.join(lines) 133 | 134 | helper = GithubHelper(**github_config) 135 | release_info = helper.getLatestRelease() 136 | result = helper.replaceLatestReleaseAssets(name = "host.txt", data = data, release_info=release_info) 137 | print(result) 138 | if "id" in result: 139 | body = \ 140 | """ 141 | + 你可以通过以下的地址获取附件中的host文件 142 | + Github源地址: 143 | + Github镜像: 144 | + host文件将由机器人每天定时刷新,最后更新于(北京时间): 145 | """ 146 | body += date_now 147 | body = body.strip() 148 | helper.updateReleaseBody(release_id = release_info["id"], body=body) 149 | 150 | --------------------------------------------------------------------------------