├── .gitattributes ├── .gitignore ├── README.md ├── hotupdate.go ├── .goreleaser.yml ├── .github └── workflows │ └── release.yml ├── go.mod ├── dark_dmzj.go └── public ├── index.html └── dmzj.css /.gitattributes: -------------------------------------------------------------------------------- 1 | /public/*.html linguist-detectable=false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | /go.sum 3 | /dark_dmzj 4 | /public/data.json 5 | /test 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dark-dmzj 2 | 3 | 方便得浏览动漫之家藏起来的漫画. [dark-dmzj.hloli.net](https://dark-dmzj.hloli.net) 4 | 5 | ## usage 6 | 1. 下载安装动漫之家手机APP. 7 | 2. 手机浏览器打开 [https://dark-dmzj.hloli.net](https://dark-dmzj.hloli.net). 8 | 9 |  10 | 11 |  12 | -------------------------------------------------------------------------------- /hotupdate.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package main 4 | 5 | import ( 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | ) 10 | 11 | func init() { 12 | c := make(chan os.Signal, 1) 13 | signal.Notify(c, syscall.SIGUSR1) 14 | go func() { 15 | for { 16 | <-c 17 | downloadBooks() 18 | } 19 | }() 20 | } 21 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - env: 3 | - CGO_ENABLED=0 4 | goos: 5 | - linux 6 | - darwin 7 | - windows 8 | goarch: 9 | - 386 10 | - amd64 11 | - arm 12 | - arm64 13 | checksum: 14 | name_template: "{{ .ProjectName }}_checksums.txt" 15 | archives: 16 | - name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 17 | format_overrides: 18 | - goos: windows 19 | format: zip 20 | files: 21 | - README.md 22 | - public/**/* 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | goreleaser: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v1 12 | - name: Set up Go 13 | uses: actions/setup-go@v1 14 | with: 15 | go-version: "1.13.x" 16 | - name: Run GoReleaser 17 | uses: goreleaser/goreleaser-action@v1 18 | with: 19 | version: latest 20 | args: release 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/torta/dark-dmzj 2 | 3 | require ( 4 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 5 | github.com/fatih/color v1.7.0 // indirect 6 | github.com/labstack/echo v3.3.10+incompatible 7 | github.com/labstack/gommon v0.2.8 // indirect 8 | github.com/mattn/go-colorable v0.0.9 // indirect 9 | github.com/mattn/go-isatty v0.0.4 // indirect 10 | github.com/mattn/go-runewidth v0.0.4 // indirect 11 | github.com/stretchr/testify v1.3.0 // indirect 12 | github.com/tidwall/gjson v1.1.5 13 | github.com/tidwall/match v1.0.1 // indirect 14 | github.com/valyala/bytebufferpool v1.0.0 // indirect 15 | github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 // indirect 16 | golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 // indirect 17 | golang.org/x/sys v0.0.0-20190130150945-aca44879d564 // indirect 18 | gopkg.in/cheggaaa/pb.v1 v1.0.27 19 | ) 20 | -------------------------------------------------------------------------------- /dark_dmzj.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/labstack/echo" 8 | "github.com/labstack/echo/middleware" 9 | "github.com/tidwall/gjson" 10 | "gopkg.in/cheggaaa/pb.v1" 11 | "io" 12 | "io/ioutil" 13 | "math/rand" 14 | "net/http" 15 | "os" 16 | "path/filepath" 17 | "sort" 18 | "time" 19 | ) 20 | 21 | var client *http.Client 22 | var bar *pb.ProgressBar 23 | var isDownloading bool 24 | 25 | type dmzjBook struct { 26 | ID uint64 `json:"id"` 27 | Title string `json:"title"` 28 | IsLong int64 `json:"islong"` 29 | Authors []string `json:"authors"` 30 | Types []string `json:"types"` 31 | Status []string `json:"status"` 32 | Cover string `json:"cover"` 33 | LastUpdateChapterName string `json:"last_update_chapter_name"` 34 | LastUpdateChapterID uint64 `json:"last_update_chapter_id"` 35 | LastUpdateTime int64 `json:"last_updatetime"` 36 | } 37 | 38 | func init() { 39 | client = &http.Client{ 40 | Transport: &http.Transport{ 41 | MaxIdleConnsPerHost: 256, 42 | MaxIdleConns: 256, 43 | ResponseHeaderTimeout: 10 * time.Second, 44 | TLSHandshakeTimeout: 10 * time.Second, 45 | TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), 46 | }, 47 | Timeout: time.Second * 10, 48 | } 49 | 50 | ex, err := os.Executable() 51 | if err != nil { 52 | panic(err) 53 | } 54 | exPath := filepath.Dir(ex) 55 | if _, err := os.Stat(filepath.Join(exPath, "public", "index.html")); !os.IsNotExist(err) { 56 | if err := os.Chdir(exPath); err != nil { 57 | panic(err) 58 | } 59 | } 60 | } 61 | 62 | func apiWithRetry(id int, try int) string { 63 | for i := 0; i < try; i++ { 64 | domains := []string{"v3api.dmzj1.com/comic/comic_"} 65 | res, err := client.Get(fmt.Sprintf("http://%s%d.json", domains[rand.Intn(1)], id)) 66 | resCk, errCk := client.Get(fmt.Sprintf("https://api.m.dmzj.com/info/%d.html", id)) 67 | if err == nil { 68 | defer res.Body.Close() 69 | defer io.Copy(ioutil.Discard, res.Body) 70 | } 71 | if errCk == nil { 72 | defer resCk.Body.Close() 73 | defer io.Copy(ioutil.Discard, resCk.Body) 74 | } 75 | if err == nil && res.StatusCode == 200 { 76 | body, _ := ioutil.ReadAll(res.Body) 77 | if errCk == nil && resCk.StatusCode == 200 { 78 | bodyCk, _ := ioutil.ReadAll(resCk.Body) 79 | if len(bodyCk) > 1024 { 80 | return "" 81 | } 82 | return string(body) 83 | } 84 | } 85 | } 86 | return "" 87 | } 88 | 89 | func getItem(id int, c chan<- string) { 90 | c <- apiWithRetry(id, 5) 91 | bar.Increment() 92 | } 93 | 94 | func arrayMap(vs []string, f func(string) []string) [][]string { 95 | vsm := make([][]string, len(vs)) 96 | for i, v := range vs { 97 | vsm[i] = f(v) 98 | } 99 | return vsm 100 | } 101 | 102 | func downloadBooks() { 103 | if isDownloading { 104 | return 105 | } 106 | isDownloading = true 107 | defer func() { isDownloading = false }() 108 | 109 | MaxRoutines := 50 110 | MaxBooks := 70000 111 | 112 | bar = pb.New(MaxBooks - 1).Prefix("Updating ") 113 | bar.SetWidth(60) 114 | bar.ShowTimeLeft = true 115 | bar.ShowCounters = false 116 | bar.ShowSpeed = true 117 | bar.Start() 118 | defer bar.FinishPrint("Finish!") 119 | 120 | c := make(chan string, MaxBooks) 121 | jobs := make(chan int, MaxBooks) 122 | for i := 0; i < MaxRoutines; i++ { 123 | go func() { 124 | for e := range jobs { 125 | getItem(e, c) 126 | } 127 | }() 128 | } 129 | for i := 1; i < MaxBooks; i++ { 130 | jobs <- i 131 | } 132 | 133 | items := []dmzjBook{} 134 | for p := 1; p < MaxBooks; p++ { 135 | dat := gjson.Parse(<-c) 136 | if dat.Get("id").Exists() { 137 | tags := arrayMap([]string{"authors", "types", "status"}, func(v string) []string { 138 | tag := []string{} 139 | for _, e := range dat.Get(v).Array() { 140 | tag = append(tag, e.Get("tag_name").String()) 141 | } 142 | return tag 143 | }) 144 | items = append(items, dmzjBook{ 145 | ID: dat.Get("id").Uint(), 146 | Title: dat.Get("title").String(), 147 | IsLong: dat.Get("islong").Int(), 148 | Authors: tags[0], 149 | Types: tags[1], 150 | Status: tags[2], 151 | Cover: dat.Get("cover").String(), 152 | LastUpdateChapterName: dat.Get("chapters.0.data.0.chapter_title").String(), 153 | LastUpdateChapterID: dat.Get("chapters.0.data.0.chapter_id").Uint(), 154 | LastUpdateTime: dat.Get("last_updatetime").Int(), 155 | }) 156 | } 157 | } 158 | 159 | sort.Slice(items, func(a, b int) bool { 160 | return items[a].LastUpdateTime > items[b].LastUpdateTime 161 | }) 162 | 163 | jsonDat, _ := json.Marshal(items) 164 | ioutil.WriteFile("public/data.json", jsonDat, 0644) 165 | } 166 | 167 | func main() { 168 | go func() { 169 | time.Sleep(1 * time.Second) 170 | downloadBooks() 171 | }() 172 | 173 | e := echo.New() 174 | e.HideBanner = true 175 | e.Static("/", "public") 176 | e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ 177 | Level: 5, 178 | })) 179 | e.GET("/webpic/*", func(c echo.Context) error { 180 | req, _ := http.NewRequest("GET", fmt.Sprintf("http://images.dmzj.com%s", c.Request().URL.Path), nil) 181 | req.Header.Set("Referer", "https://m.dmzj.com/") 182 | res, err := client.Do(req) 183 | if err != nil { 184 | return c.NoContent(http.StatusBadGateway) 185 | } 186 | defer res.Body.Close() 187 | data, _ := ioutil.ReadAll(res.Body) 188 | return c.Blob(res.StatusCode, res.Header.Get("Content-Type"), data) 189 | }) 190 | e.Logger.Fatal(e.Start(":7777")) 191 | } 192 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |{{ item.authors.join('/') }}
91 |92 | 93 | {{ type }} 94 |
95 |96 | 97 | {{ moment(item.last_updatetime * 1000).format('YYYY-MM-DD H:mm') }} 98 |
99 |