Nova Kwok's Awesome Blog

我是怎么在两天之内糊出 350+ 个 PR 的(NPM Mirror 修复小记)

事情的起因是这样的,有一天我在 Telegram 里面看到了一条消息:

由于之前有过被大量 Dependabot 骚扰的经历,加上有 GitHub Code Search 的 Preview 权限,于是便想到:为什么我不能做一个类似 dependabot 的东西来批量帮别人来改 NPM Mirror 地址呢?

拿到所有的仓库和文件信息

第一反应便是去 https://cs.github.com 上拿到所有包含老地址的仓库,虽然 GitHub Code search 没有提供任何 API,但是通过浏览器的包来看,还是有个 API 地址可用的,所以很快就有了第一个小脚本用来拿到所有仓库和关键词所在文件的信息(这里为了简单考虑只查了 ['Makefile','Dockerfile','.md','package.json','.npmrc' 中包含旧地址的信息,Copilot 一开,啪的一下,很快啊:

import requests
import csv

filename_list = ['Makefile','Dockerfile','.md','package.json','.npmrc']

url = 'https://cs.github.com/api/search?q=path%3A{filename}+registry.npm.taobao.org++&p={page}'
header = {"cookie":"_COOKIE_HERE",
"user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"}

results = []
for filename in filename_list:
    res = requests.get(url.format(filename=filename, page=1),headers=header)
    total_pages = res.json()['total_pages']
    for page in range(1,total_pages+1):
        res = requests.get(url.format(filename=filename, page=page),headers=header)
        for result in res.json()['results']:
            each_repo = {}
            each_repo['filename'] = result['path']
            each_repo['repo_name'] = result['repo_name']
            each_repo['ref_name'] = result['ref_name']
            results.append(each_repo)

# Write results to csv file
with open('results.csv', 'w') as csvfile:
    fieldnames = ['filename', 'repo_name', 'ref_name']
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
    writer.writeheader()
    for result in results:
        writer.writerow(result)

这样,我们就可以快速拿到所有的信息了,保存在一个 csv 里面,文件内容类似如下:

filename,repo_name,ref_name
Makefile,ElemeFE/element,refs/heads/dev
Makefile,node-modules/copy-to,refs/heads/master
Makefile,cnodejs/nodeclub,refs/heads/master
Makefile,leungwensen/svg-icon,refs/heads/master
makefile,V-Tom/blog,refs/heads/koa2
Makefile,aliyun/api-gateway-nodejs-sdk,refs/heads/master
Makefile,cojs/urllib,refs/heads/master
Makefile,barretlee/blog,refs/heads/master

但是这个里面有非 master 分支的数据(由于这个脚本希望它越简单粗暴越好,所以决定只处理 master 分支),很快就有了如下语句:

grep "refs/heads/master" results.csv >> master.csv

在拿到了 master.csv 之后我发现,有些仓库内可能会存在同一个 repo 中多个文件都有出现旧地址的情况,类似如下:

lang/node-firmata/Makefile,immortalwrt/packages,refs/heads/master
lang/node-sqlite3/Makefile,immortalwrt/packages,refs/heads/master

在这种情况下如果直接按行遍历的话会出现一个仓库多个 PR 的情况,这种情况肯定不能出现,于是将数据洗成一个简单的 JSON 格式,结果看上去类似这样:

[{
    "repo_name_with_owner": "node-modules/default-user-agent",
    "files": [
        "package.json"
    ]
},
{
    "repo_name_with_owner": "KittenBot/Kittenblock",
    "files": [
        "scratch-blocks/package.json",
        "scratch-vm/package.json"
    ]
}]

批量提 PR

好了,我们现在有了所有需要的仓库的信息

可以开始一个个遍历了,由于这个脚本希望越简单粗暴越好,所以对于每个仓库,我们都:

  1. Fork 原仓库并等 5s(GitHub 的 Fork 似乎需要时间,如果直接 Clone 会报错)
  2. Clone 仓库
  3. 用脚本修改需要的文件内容
  4. git add . && git commit -m "update https://registry.npm.taobao.org to https://registry.npmmirror.com" && git push
  5. 调用 GitHub API 产生一个 PR

然后在遇到过很多次的 Rate limit 之后:

{"message":"API rate limit exceeded for user ID 99484857.","documentation_url":"https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting"}

300+ 个 PR 就出现了~

然后我的帐号也被 Flag 了:

还好是新注册的一个小号…

2022-02-14:@npmmirror 帐号已经被放了出来,GitHub 的回复表示:Sorry for the troubles here. Sometimes the automated systems we use will incorrectly flag an account when it shouldn’t be, and that’s what occurred here. I’ve reset your profile, so you should be able to access and use everything as normal again.

接下来就是等着这些 PR 被慢慢合并掉就好了~

有趣的事情

在做这个事情的过程中,我们可以看到有很多有趣的事情,这里分为几类:

CLA

嗯,是的,很多 PR 被 CLA 就直接挡了下来

比如:https://github.com/alibaba/hooks/pull/1459#issuecomment-1037043145

EmailBot Reply

这里面就很有意思了,比如我们可以看到 https://github.com/alsotang/node-lessons/pull/173#issuecomment-1037010525 中的「这是来自QQ邮箱的假期自动回复邮件。您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。」,「你的来信我已收到!」,「您好,已收到你的来信,很高兴,我会进快回复,谢谢」

或者 https://github.com/egret-labs/egret-core/pull/419#issuecomment-1037011169 中的:「什么事吖」

Bot Fight

名场面应该是 DIYGOD 的 RSSHUB,可以看到真人和机器人之间针对这个 PR 开开关关,最后 Merge 的案例:

Close and Push Commit

也有直接就 Close 了 PR 并且手动提交了 Commit 的情况,不过这类情况似乎不多见,这里的案例是:https://github.com/erda-project/erda-ui/pull/2895 (这里原因是因为 PR 中少改了一个文件)

作者 Close 了这个 PR 并重新 Push 了一个新的 Commit

Merge with question

可能是因为淘宝 NPM Mirror 修改源这个信息出现的位置过于诡异(GitHub Issue 和 Zhihu Link,分别是:https://github.com/cnpm/cnpm/issues/361 , https://zhuanlan.zhihu.com/p/430580607https://zhuanlan.zhihu.com/p/465424728 ),所以会出现即使作者看到了 PR 也没法确定整个事情的真实性,这里的代表案例是:https://github.com/apache/skywalking/pull/8538

Merged

这种最多,就是直接 Merge 了,没有任何 Comment.

后记

第一次干这种 污染全 GitHub 的 事情,现在想想还是蛮刺激的,记得刚刚开始搞的时候由于怕被人骂,在 NPMMirror 的 PR Body 和 URL 中留下的是 n0vad3v 经过 sha256sum 之后的值 8d6e8cefe5a7e3202364ec2c48db03fcc544218cf70100bf65d2ed2df5cc83da,后来发现多虑了。

同时在 @xuanwo 的提醒「我感觉海星啊,感觉可以做一个面向全体开源项目批量重构的服务」下,感觉其实这里面还有很大的潜在需求等待被满足,或许可以做一个新的公共服务?

#Chinese