diff --git a/.github/workflows/check-failed-biz.yml b/.github/workflows/check-failed-biz.yml index 9dd4e3486b2..f7c9e146b82 100644 --- a/.github/workflows/check-failed-biz.yml +++ b/.github/workflows/check-failed-biz.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: ^1.15 + go-version: ^1.16 - name: Get dependencies run: | diff --git a/.github/workflows/check-transfer.yml b/.github/workflows/check-transfer.yml new file mode 100644 index 00000000000..fc14f207c44 --- /dev/null +++ b/.github/workflows/check-transfer.yml @@ -0,0 +1,29 @@ +name: check transfer + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" # Runs at 00:00 UTC every day. + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ^1.16 + + - name: Get dependencies + run: | + go get -v -t -d ./... + + - name: Run + id: check + run: | + go run ./check-transfer + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deal-with-issues.yml b/.github/workflows/deal-with-issues.yml index 8f4370213eb..1991e63771b 100644 --- a/.github/workflows/deal-with-issues.yml +++ b/.github/workflows/deal-with-issues.yml @@ -13,7 +13,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: ^1.15 + go-version: ^1.16 - name: Get dependencies run: | diff --git a/.github/workflows/merge-bot.yml b/.github/workflows/merge-bot.yml index 55e0b67e070..bfbdd5d3912 100644 --- a/.github/workflows/merge-bot.yml +++ b/.github/workflows/merge-bot.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: ^1.15 + go-version: ^1.16 - name: Get dependencies run: | diff --git a/check-failed-biz/main.go b/check-failed-biz/main.go index 502f701b50d..ea21dac83e2 100644 --- a/check-failed-biz/main.go +++ b/check-failed-biz/main.go @@ -75,20 +75,14 @@ LABEL: os.Exit(0) } -type bizInfo struct { - Name string `csv:"name"` - BizID string `csv:"bizid"` - Description string `csv:"description"` -} - -func getList() []*bizInfo { +func getList() []*common.BizInfo { body := common.Fetch("https://github.com/hellodword/wechat-feeds/raw/main/list.csv") if !common.WithUTF8Bom(body) { panic("list.csv not utf8 bom") } - var bis []*bizInfo + var bis []*common.BizInfo err := gocsv.Unmarshal(bytes.NewReader(common.TrimUTF8Bom(body)), &bis) if err != nil { panic(err) @@ -97,16 +91,11 @@ func getList() []*bizInfo { return bis } -type bizDetail struct { - Name string `csv:"name" json:"name"` - BizID string `csv:"bizid" json:"bizid"` -} - -func getDetails() []*bizDetail { +func getDetails() []*common.BizDetail { body := common.Fetch("https://github.com/hellodword/wechat-feeds/raw/feeds/details.json") - var bds []*bizDetail + var bds []*common.BizDetail err := json.Unmarshal(body, &bds) if err != nil { panic(err) diff --git a/check-transfer/main.go b/check-transfer/main.go new file mode 100644 index 00000000000..e52d3256e93 --- /dev/null +++ b/check-transfer/main.go @@ -0,0 +1,120 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/google/go-github/v33/github" + "github.com/hellodword/wechat-feeds/common" + "github.com/mmcdole/gofeed" + "math/rand" + "net/url" + "os" + "time" +) + +const ( + Owner = "hellodword" + Repo = "wechat-feeds" + + IssueID = 2387 +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func main() { + + details := getDetails() + if len(details) == 0 { + os.Exit(0) + } + + ctx := context.Background() + clientWithToken, client := common.MakeClients(ctx, os.Getenv("GITHUB_ACCESS_TOKEN")) + _, _ = clientWithToken, client + client = clientWithToken // for private test + + _, _, _ = clientWithToken.Issues.AddLabelsToIssue(ctx, Owner, Repo, + IssueID, + []string{string(common.LabelCheck)}) + + buf := bytes.NewBuffer(nil) + buf.WriteString(`| name | bizid | new | reason | +| --- | ----- | --- | ------ |`) + for i := range details { + buf.WriteString("\n") + buf.WriteString("| ") + buf.WriteString(details[i].Name) + buf.WriteString(" | ") + buf.WriteString(fmt.Sprintf(`[%s](https://github.com/hellodword/wechat-feeds/raw/feeds/%s.xml)`, + details[i].BizID, details[i].BizID)) + + newBiz, reason := checkTransfer(details[i].BizID) + buf.WriteString(" | ") + buf.WriteString(newBiz) + buf.WriteString(" | ") + buf.WriteString(reason) + buf.WriteString(" | ") + + } + + fmt.Println(buf.String()) + + _, _, err := clientWithToken.Issues.CreateComment(ctx, Owner, Repo, + IssueID, + &github.IssueComment{ + Body: github.String(buf.String()), + }) + if err != nil { + panic("Issues.CreateComment") // token privacy + } + + os.Exit(0) +} + +func checkTransfer(bizid string) (newBiz, reason string) { + fp := gofeed.NewParser() + feed, err := fp.ParseString(string(common.Fetch(fmt.Sprintf("https://github.com/hellodword/wechat-feeds/raw/feeds/%s.xml", url.QueryEscape(bizid))))) + if err != nil || feed == nil || len(feed.Items) == 0 { + return + } + + i := rand.Intn(len(feed.Items)) // 原文也失效 https://mp.weixin.qq.com/s/6kgFxZ6nm9dLRZV66-9RXA + + articleInfo, _ := common.FetchWX(feed.Items[i].Link) + + newBiz = articleInfo.BizID + reason = articleInfo.FailReason + + if newBiz == "" && articleInfo.TransferLink != "" { // 原文有问题,直接从链接取 bizid + // http://mp.weixin.qq.com/s?__biz=Mzk0MDIwNTQxNw==&mid=2247505407&idx=1&sn=62616bd14bfe1eb8f570442ffddefcd0#rd + newBiz = common.MatchBizID(articleInfo.TransferLink) + } + + return +} + +func getDetails() []*common.BizDetail { + + body := common.Fetch("https://github.com/hellodword/wechat-feeds/raw/feeds/details.json") + + var bds []*common.BizDetail + err := json.Unmarshal(body, &bds) + if err != nil { + panic(err) + } + + // pick out details without head_img + + var ret []*common.BizDetail + for i := range bds { + if bds[i].HeadIMG == "" { + ret = append(ret, bds[i]) + } + } + + return ret +} diff --git a/common/match.go b/common/match.go new file mode 100644 index 00000000000..b9bde7671e3 --- /dev/null +++ b/common/match.go @@ -0,0 +1,104 @@ +package common + +import ( + "errors" + "fmt" + "regexp" + "strings" +) + +func MatchTransferTargetLink(s string) string { + r := regexp.MustCompile(`transferTargetLink = '(https?://mp\.weixin\.qq\.com/s[^\s\r\n]+)'`).FindStringSubmatch(s) + if len(r) < 2 { + return "" + } else { + return r[1] + } +} + +func MatchBizID(s string) string { + + // https://mp.weixin.qq.com/s/1I33XLA5uK1Iljvn3-XVDg + // https://mp.weixin.qq.com/s/etTO4fTRwyvSUuh2qJlIaw + + r := regexp.MustCompile(`((var biz = [" =|]*")|(var appuin = [" =|]*")|(__biz=))([a-zA-Z\d/+=]+)`).FindStringSubmatch(s) + if len(r) == 6 { + return r[5] + } else { + return "" + } +} + +func MatchName(s string) string { + + // 图文 https://mp.weixin.qq.com/s/g0H8YxjN5kUR3Kx9cPgNlA + + r := regexp.MustCompile(`(var nickname = "([^\n]+)";)|(d\.nick_name = getXmlValue\('nick_name.DATA'\) \|\| '([^\n]+)';)|(([^\n]+))`).FindStringSubmatch(s) + if len(r) != 7 { + return "" + } + + if r[2] != "" { + return r[2] + } + if r[4] != "" { + return r[4] + } + if r[6] != "" { + return r[6] + } + + return "" + +} + +type WXArticle struct { + Name string + BizID string + Description string + + FailReason string + TransferLink string +} + +func FetchWX(u string) (article WXArticle, err error) { + fmt.Println("link", u) + + body := Fetch(u) + + s := string(body) + + if article.FailReason == "" { + if strings.Index(s, `此帐号已自主注销,内容无法查看`) != -1 { + article.FailReason = `此帐号已自主注销,内容无法查看` + err = errors.New(article.FailReason) + return + } else if strings.Index(s, `该公众号已迁移`) != -1 { + article.FailReason = `该公众号已迁移` + } + } + + link := MatchTransferTargetLink(s) + if link != "" { + fmt.Println("transfer link", link) + newArticle, err := FetchWX(link) + newArticle.TransferLink = link + if article.FailReason != "" && newArticle.FailReason == "" { + newArticle.FailReason = article.FailReason + } + return newArticle, err + } + + article.BizID = MatchBizID(s) + if article.BizID == "" { + err = errors.New("no biz id") + return + } + article.Name = MatchName(s) + if article.Name == "" { + err = errors.New("no name") + return + } + + return +} diff --git a/common/structs.go b/common/structs.go new file mode 100644 index 00000000000..65cf76797cf --- /dev/null +++ b/common/structs.go @@ -0,0 +1,13 @@ +package common + +type BizDetail struct { + Name string `csv:"name" json:"name"` + BizID string `csv:"bizid" json:"bizid"` + HeadIMG string `json:"head_img"` +} + +type BizInfo struct { + Name string `csv:"name"` + BizID string `csv:"bizid"` + Description string `csv:"description"` +} diff --git a/deal-with-issues/main.go b/deal-with-issues/main.go index cf3e8a0bba1..7786cc70b02 100644 --- a/deal-with-issues/main.go +++ b/deal-with-issues/main.go @@ -3,7 +3,6 @@ package main import ( "bytes" "context" - "errors" "fmt" "github.com/google/go-github/v33/github" "github.com/hellodword/wechat-feeds/common" @@ -51,7 +50,7 @@ func main() { } done := map[string]int{} - succ := map[string]WXArticle{} + succ := map[string]common.WXArticle{} fail := map[string]error{} for i := 0; i < len(us); i++ { @@ -66,7 +65,7 @@ func main() { time.Sleep(time.Second * 5) } - article, err := fetchWX(us[i]) + article, err := common.FetchWX(us[i]) if err == nil { succ[article.BizID] = article } else { @@ -108,84 +107,6 @@ func main() { os.Exit(0) } -type WXArticle struct { - Name string - BizID string - Description string -} - -func getTransferTargetLink(s string) string { - r := regexp.MustCompile(`transferTargetLink = '(https?://mp\.weixin\.qq\.com/s[^\s\r\n]+)'`).FindStringSubmatch(s) - if len(r) < 2 { - return "" - } else { - return r[1] - } -} - -func getBizID(s string) string { - - // https://mp.weixin.qq.com/s/1I33XLA5uK1Iljvn3-XVDg - // https://mp.weixin.qq.com/s/etTO4fTRwyvSUuh2qJlIaw - - r := regexp.MustCompile(`((var biz = [" =|]*")|(var appuin = [" =|]*")|(__biz=))([a-zA-Z\d/+=]+)`).FindStringSubmatch(s) - if len(r) == 6 { - return r[5] - } else { - return "" - } -} - -func getName(s string) string { - - // 图文 https://mp.weixin.qq.com/s/g0H8YxjN5kUR3Kx9cPgNlA - - r := regexp.MustCompile(`(var nickname = "([^\n]+)";)|(d\.nick_name = getXmlValue\('nick_name.DATA'\) \|\| '([^\n]+)';)|(([^\n]+))`).FindStringSubmatch(s) - if len(r) != 7 { - return "" - } - - if r[2] != "" { - return r[2] - } - if r[4] != "" { - return r[4] - } - if r[6] != "" { - return r[6] - } - - return "" - -} - -func fetchWX(u string) (article WXArticle, err error) { - fmt.Println("link", u) - - body := common.Fetch(u) - - s := string(body) - - link := getTransferTargetLink(s) - if link != "" { - fmt.Println("transfer link", link) - return fetchWX(link) - } - - article.BizID = getBizID(s) - if article.BizID == "" { - err = errors.New("no biz id") - return - } - article.Name = getName(s) - if article.Name == "" { - err = errors.New("no name") - return - } - - return -} - func closeIssue(ctx context.Context, clientWithToken *github.Client, issue *github.Issue, label common.Label, comment string) { fmt.Printf("closing issue #%d %s %s\n", issue.GetNumber(), issue.GetTitle(), label) diff --git a/go.mod b/go.mod index 3ea02499f2a..358f9c7ea59 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,6 @@ go 1.15 require ( github.com/gocarina/gocsv v0.0.0-20201208093247-67c824bc04d4 github.com/google/go-github/v33 v33.0.1-0.20210304192731-149aabe8d877 + github.com/mmcdole/gofeed v1.1.3 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be ) diff --git a/go.sum b/go.sum index a2bd6368d39..aac9a1f2262 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,54 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gocarina/gocsv v0.0.0-20201208093247-67c824bc04d4 h1:Q7s2AN3DhFJKOnzO0uTKLhJTfXTEcXcvw5ylf2BHJw4= github.com/gocarina/gocsv v0.0.0-20201208093247-67c824bc04d4/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-github/v33 v33.0.1-0.20210304192731-149aabe8d877 h1:s4iQs44eSWAHqxaolv7GC4iyfnGZZddaxap1sxKScCw= github.com/google/go-github/v33 v33.0.1-0.20210304192731-149aabe8d877/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/mmcdole/gofeed v1.1.3 h1:pdrvMb18jMSLidGp8j0pLvc9IGziX4vbmvVqmLH6z8o= +github.com/mmcdole/gofeed v1.1.3/go.mod h1:QQO3maftbOu+hiVOGOZDRLymqGQCos4zxbA4j89gMrE= +github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI= +github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf/go.mod h1:pasqhqstspkosTneA62Nc+2p9SOBBYAPbnmRRWPQ0V8= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/merge-bot/main.go b/merge-bot/main.go index b6b22bc852c..0584682bec3 100644 --- a/merge-bot/main.go +++ b/merge-bot/main.go @@ -33,12 +33,6 @@ const ( ServicePer = 32 ) -type bizInfo struct { - Name string `csv:"name"` - BizID string `csv:"bizid"` - Description string `csv:"description"` -} - /* */ @@ -182,7 +176,7 @@ func checkPRDetails(ctx context.Context, clientWithToken, client *github.Client, return false } - var newBis []*bizInfo + var newBis []*common.BizInfo err = gocsv.Unmarshal(bytes.NewReader(common.TrimUTF8Bom(newBody)), &newBis) if err != nil { closePR(ctx, clientWithToken, pr, common.LabelInvalid, @@ -191,7 +185,7 @@ func checkPRDetails(ctx context.Context, clientWithToken, client *github.Client, } var oldBody []byte - var oldBis []*bizInfo + var oldBis []*common.BizInfo for { oldBody = common.Fetch("https://github.com/hellodword/wechat-feeds/raw/main/list.csv") // fetch(fs[0].GetRawURL()) if !common.WithUTF8Bom(oldBody) {