sing-geosite/main.go
2022-07-05 09:13:57 +08:00

177 lines
4.6 KiB
Go

package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/google/go-github/v45/github"
"github.com/sagernet/sing-box/common/geosite"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sirupsen/logrus"
"github.com/v2fly/v2ray-core/v5/app/router/routercommon"
"google.golang.org/protobuf/proto"
)
var githubClient *github.Client
func init() {
accessToken, loaded := os.LookupEnv("ACCESS_TOKEN")
if !loaded {
githubClient = github.NewClient(nil)
return
}
transport := &github.BasicAuthTransport{
Username: accessToken,
}
githubClient = github.NewClient(transport.Client())
}
func fetch(from string) (*github.RepositoryRelease, error) {
names := strings.SplitN(from, "/", 2)
latestRelease, _, err := githubClient.Repositories.GetLatestRelease(context.Background(), names[0], names[1])
if err != nil {
return nil, err
}
return latestRelease, err
}
func get(downloadURL *string) ([]byte, error) {
logrus.Info("download ", *downloadURL)
response, err := http.Get(*downloadURL)
if err != nil {
return nil, err
}
defer response.Body.Close()
return io.ReadAll(response.Body)
}
func download(release *github.RepositoryRelease) ([]byte, error) {
geositeAsset := common.Find(release.Assets, func(it *github.ReleaseAsset) bool {
return *it.Name == "dlc.dat"
})
geositeChecksumAsset := common.Find(release.Assets, func(it *github.ReleaseAsset) bool {
return *it.Name == "dlc.dat.sha256sum"
})
if geositeAsset == nil {
return nil, E.New("geosite asset not found in upstream release ", release.Name)
}
if geositeChecksumAsset == nil {
return nil, E.New("geosite asset not found in upstream release ", release.Name)
}
data, err := get(geositeAsset.BrowserDownloadURL)
if err != nil {
return nil, err
}
remoteChecksum, err := get(geositeChecksumAsset.BrowserDownloadURL)
if err != nil {
return nil, err
}
checksum := sha256.Sum256(data)
if hex.EncodeToString(checksum[:]) != string(remoteChecksum[:64]) {
return nil, E.New("checksum mismatch")
}
return data, nil
}
func parse(vGeositeData []byte) (map[string][]geosite.Item, error) {
vGeositeList := routercommon.GeoSiteList{}
err := proto.Unmarshal(vGeositeData, &vGeositeList)
if err != nil {
return nil, err
}
domainMap := make(map[string][]geosite.Item)
for _, vGeositeEntry := range vGeositeList.Entry {
domains := make([]geosite.Item, 0, len(vGeositeEntry.Domain)*2)
for _, domain := range vGeositeEntry.Domain {
switch domain.Type {
case routercommon.Domain_Plain:
domains = append(domains, geosite.Item{
Type: geosite.RuleTypeDomainKeyword,
Value: domain.Value,
})
case routercommon.Domain_Regex:
domains = append(domains, geosite.Item{
Type: geosite.RuleTypeDomainRegex,
Value: domain.Value,
})
case routercommon.Domain_RootDomain:
domains = append(domains, geosite.Item{
Type: geosite.RuleTypeDomain,
Value: domain.Value,
})
domains = append(domains, geosite.Item{
Type: geosite.RuleTypeDomainSuffix,
Value: "." + domain.Value,
})
case routercommon.Domain_Full:
domains = append(domains, geosite.Item{
Type: geosite.RuleTypeDomain,
Value: domain.Value,
})
}
}
domainMap[strings.ToLower(vGeositeEntry.CountryCode)] = common.Uniq(domains)
}
return domainMap, nil
}
func generate(release *github.RepositoryRelease, output string) error {
outputFile, err := os.Create(output)
if err != nil {
return err
}
defer outputFile.Close()
vData, err := download(release)
if err != nil {
return err
}
domainMap, err := parse(vData)
if err != nil {
return err
}
outputPath, _ := filepath.Abs(output)
os.Stderr.WriteString("write " + outputPath + "\n")
return geosite.Write(outputFile, domainMap)
}
func setActionOutput(name string, content string) {
os.Stdout.WriteString("::set-output name=" + name + "::" + content + "\n")
}
func release(source string, destination string, output string) error {
sourceRelease, err := fetch(source)
if err != nil {
return err
}
destinationRelease, err := fetch(destination)
if err != nil {
logrus.Warn("missing destination latest release")
} else {
if os.Getenv("NO_SKIP") != "true" && strings.Contains(*destinationRelease.Name, *sourceRelease.Name) {
logrus.Info("already latest")
setActionOutput("skip", "true")
return nil
}
}
err = generate(sourceRelease, output)
if err != nil {
return err
}
setActionOutput("tag", *sourceRelease.Name)
return nil
}
func main() {
err := release("v2fly/domain-list-community", "sagernet/sing-geosite", "geosite.db")
if err != nil {
logrus.Fatal(err)
}
}