├── .gitignore
├── LICENSE
├── README.md
├── compatible
├── decode.go
├── decode_test.go
├── flatten_txt.go
├── json_format.go
└── self_json.go
├── config
└── config.go
├── core
├── exporter.go
├── importer.go
├── net_dir.go
├── net_file.go
├── network.go
└── network_test.go
├── dir
├── dir.go
└── dir_test.go
├── go.mod
├── go.sum
├── log
├── log.go
└── log_bench_test.go
├── main.go
├── static
└── export.png
├── utils
├── cipher.go
├── cipher_test.go
├── cookie.go
├── cookies.txt
├── pool.go
├── request.go
├── request_test.go
├── sha1.go
├── sha1_test.go
└── util.go
└── with_cookie_test.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/python
2 | # Edit at https://www.gitignore.io/?templates=python
3 |
4 | ### Python ###
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | wheels/
27 | pip-wheel-metadata/
28 | share/python-wheels/
29 | *.egg-info/
30 | .installed.cfg
31 | *.egg
32 | MANIFEST
33 |
34 | # PyInstaller
35 | # Usually these files are written by a python script from a template
36 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
37 | *.manifest
38 | *.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .nox/
48 | .coverage
49 | .coverage.*
50 | .cache
51 | nosetests.xml
52 | coverage.xml
53 | *.cover
54 | .hypothesis/
55 | .pytest_cache/
56 |
57 | # Translations
58 | *.mo
59 | *.pot
60 |
61 | # Django stuff:
62 | *.log
63 | local_settings.py
64 | db.sqlite3
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | .python-version
88 | *save/
89 |
90 | # celery beat schedule dir
91 | celerybeat-schedule
92 |
93 | # SageMath parsed files
94 | *.sage.py
95 |
96 | # Environments
97 | .env
98 | .venv
99 | env/
100 | venv/
101 | ENV/
102 | env.bak/
103 | venv.bak/
104 |
105 | # Spyder project settings
106 | .spyderproject
107 | .spyproject
108 |
109 | # Rope project settings
110 | .ropeproject
111 |
112 | # mkdocs documentation
113 | /site
114 |
115 | # mypy
116 | .mypy_cache/
117 | .dmypy.json
118 | dmypy.json
119 |
120 | # Pyre type checker
121 | .pyre/
122 |
123 | # pycharm
124 | *.idea/
125 |
126 | ### Python Patch ###
127 | .venv/
128 |
129 | # excel
130 | .xls
131 | .xlsx
132 | .pwd
133 | # End of https://www.gitignore.io/api/python
134 | .log
135 | logs/
136 |
137 | cookies.txt
138 | .goreleaser.yml
139 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Gawo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fake115-go
2 | 115网盘助手Go版本,完整保留导出、导入的结构,导出的目录什么样,导入就什么样😲,达到跟雷达功能一致的效果,且没有大小限制。
3 |
4 |
5 |
6 | 目前成功导出、导入多个文件夹,大的有70万个文件😄。115现在短时间内导出太多文件会直接将账号踢下线,只能暂时模仿115浏览器本身的下载和上传频率。
7 |
8 |

9 |
10 | 已更新新版导出文件方式,感谢`https://github.com/orzogc/fake115uploader`的加密代码😊。
11 |
12 | ## Download
13 |
14 |
15 |
16 | https://github.com/gawwo/fake115-go/releases
17 |
18 |
19 |
20 | ## Installation
21 |
22 |
23 |
24 | #### Clone
25 |
26 |
27 |
28 | ```bash
29 | git clone https://github.com/gawwo/fake115-go
30 | cd fake115-go
31 | ```
32 |
33 |
34 |
35 | #### Get the dependencies
36 |
37 |
38 |
39 | ```bash
40 | go get ./...
41 | ```
42 |
43 |
44 |
45 | #### Build
46 |
47 | ```bash
48 | go build -o fake115 .
49 | ```
50 |
51 |
52 |
53 | ## Getting Started
54 |
55 |
56 |
57 | #### Prepare
58 |
59 |
60 |
61 | 从115浏览器中获取自己登陆后的cookie。
62 |
63 |
64 |
65 | 可以在程序目录下创建一个`cookies.txt`的文件存放cookie,也可以在使用时,添加`-c`参数设置cookie。
66 |
67 |
68 |
69 | #### Export
70 |
71 |
72 |
73 | - cid是指115文件夹的id,F12的开发者工具中查看network能找到它。
74 |
75 |
76 |
77 | Usage:
78 |
79 | ```bash
80 | fake115 -c "cookiexxxxx"
81 |
82 | # 示例
83 | fake115 1898007427015248622
84 | ```
85 |
86 |
87 |
88 | #### Import
89 |
90 |
91 |
92 | Usage:
93 |
94 | ```bash
95 | fake115 -c "cookiexxxxx"
96 |
97 | # 示例
98 | fake115 353522044329243945 1898007427015248622_纪录片_438GB.json
99 | ```
100 |
101 | #### 参数
102 |
103 | 导出/导入过快,可能会被阻止暂时访问,导致任务失败。
104 |
105 | -n 同时进行的任务数,默认为1
106 |
107 | -i 网络等待间隔,默认为3s
108 |
109 | -f 过滤小于此大小的文件,单位KB,默认为0,不过滤
110 |
111 |
--------------------------------------------------------------------------------
/compatible/decode.go:
--------------------------------------------------------------------------------
1 | package compatible
2 |
3 | import (
4 | "fmt"
5 | "github.com/gawwo/fake115-go/config"
6 | "github.com/gawwo/fake115-go/dir"
7 | "go.uber.org/zap"
8 | "os"
9 | )
10 |
11 | const flattenTxtPrefix = "115://"
12 | const flattenTxtSplit = "|"
13 | const normalSplitLen = 4
14 |
15 | type Decoder interface {
16 | Decode(file *os.File) (*dir.Dir, error)
17 | }
18 |
19 | func Decode(metaPath string) *dir.Dir {
20 | decoders := []Decoder{
21 | &SelfJson{},
22 | &FlattenTxt{},
23 | &JsonFormat{},
24 | }
25 | found := false
26 | var metaDir = &dir.Dir{}
27 | for _, decoder := range decoders {
28 | f, err := os.Open(metaPath)
29 | if err != nil {
30 | config.Logger.Error("import file not exists",
31 | zap.String("reason", err.Error()),
32 | zap.String("path", metaPath))
33 | fmt.Println("读取导入文件错误")
34 | return nil
35 | }
36 |
37 | decodeDir, err := decoder.Decode(f)
38 | f.Close()
39 | if err != nil {
40 | continue
41 | }
42 | if decodeDir == nil {
43 | continue
44 | }
45 | if len(decodeDir.Files) == 0 && len(decodeDir.Dirs) == 0 {
46 | continue
47 | }
48 |
49 | found = true
50 | metaDir = decodeDir
51 | break
52 | }
53 |
54 | if !found {
55 | return nil
56 | }
57 | return metaDir
58 | }
59 |
--------------------------------------------------------------------------------
/compatible/decode_test.go:
--------------------------------------------------------------------------------
1 | package compatible
2 |
3 | import (
4 | "fmt"
5 | "github.com/gawwo/fake115-go/dir"
6 | "os"
7 | "testing"
8 | )
9 |
10 | func TestDecode(t *testing.T) {
11 | metaDir := Decode("shoucang.json")
12 | fmt.Println(metaDir)
13 | }
14 |
15 | func TestRebuildTree(t *testing.T) {
16 | parts := []string{"第一层", "第二层", "第三层"}
17 | metaDir := &dir.Dir{DirName: "meta", Dirs: []*dir.Dir{{DirName: "第一层"}}}
18 | last := rebuildTree(metaDir, parts)
19 | fmt.Println(metaDir)
20 | fmt.Println(last)
21 | }
22 |
23 | func TestFlattenTxtDecode(t *testing.T) {
24 | file, err := os.Open("WANZ 001-999.txt")
25 | if err != nil {
26 | println(err.Error())
27 | println("没有找到文件")
28 | return
29 | }
30 | defer file.Close()
31 |
32 | f := FlattenTxt{}
33 | metaDir, _ := f.Decode(file)
34 | fmt.Println(metaDir)
35 | }
36 |
37 | func TestJsonFormatDecode(t *testing.T) {
38 | file, err := os.Open("shoucang.json")
39 | if err != nil {
40 | println(err.Error())
41 | println("没有找到文件")
42 | return
43 | }
44 | defer file.Close()
45 |
46 | j := JsonFormat{}
47 | metaDir, _ := j.Decode(file)
48 | fmt.Println(metaDir)
49 | }
50 |
--------------------------------------------------------------------------------
/compatible/flatten_txt.go:
--------------------------------------------------------------------------------
1 | package compatible
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "strings"
8 |
9 | "github.com/gawwo/fake115-go/dir"
10 | "github.com/gawwo/fake115-go/utils"
11 | )
12 |
13 | type FlattenTxt struct{}
14 |
15 | func (f *FlattenTxt) Decode(file *os.File) (*dir.Dir, error) {
16 | metaDir := &dir.Dir{DirName: utils.FileNameStrip(file.Name())}
17 |
18 | scanner := bufio.NewScanner(file)
19 | for scanner.Scan() {
20 | line := scanner.Text()
21 | if strings.HasPrefix(line, flattenTxtPrefix) {
22 | line = line[len(flattenTxtPrefix):]
23 | }
24 |
25 | parts := strings.Split(line, flattenTxtSplit)
26 | if len(parts) < normalSplitLen {
27 | fmt.Printf("本行字符串有误 %s ", line)
28 | continue
29 |
30 | } else if len(parts) == normalSplitLen {
31 | metaDir.Files = append(metaDir.Files, line)
32 |
33 | } else {
34 | dirParts := parts[normalSplitLen:]
35 | treeNode := rebuildTree(metaDir, dirParts)
36 | treeNode.Files = append(treeNode.Files, strings.Join(parts[:normalSplitLen], flattenTxtSplit))
37 | }
38 | }
39 | return metaDir, nil
40 | }
41 |
42 | func rebuildTree(metaDir *dir.Dir, dirpaths []string) *dir.Dir {
43 | if len(dirpaths) == 0 {
44 | return metaDir
45 | }
46 |
47 | found := false
48 | expectDir := &dir.Dir{}
49 | for _, innerDir := range metaDir.Dirs {
50 | if innerDir.DirName == dirpaths[0] {
51 | found = true
52 | expectDir = innerDir
53 | }
54 | }
55 |
56 | if !found {
57 | expectDir.DirName = dirpaths[0]
58 | metaDir.Dirs = append(metaDir.Dirs, expectDir)
59 | }
60 |
61 | return rebuildTree(expectDir, dirpaths[1:])
62 | }
63 |
--------------------------------------------------------------------------------
/compatible/json_format.go:
--------------------------------------------------------------------------------
1 | package compatible
2 |
3 | import (
4 | "errors"
5 | "github.com/gawwo/fake115-go/config"
6 | "github.com/gawwo/fake115-go/dir"
7 | "go.uber.org/zap"
8 | "io/ioutil"
9 | "os"
10 | "strings"
11 | )
12 |
13 | type JsonFormat struct{}
14 |
15 | func (j *JsonFormat) Decode(file *os.File) (*dir.Dir, error) {
16 | metaBytes, err := ioutil.ReadAll(file)
17 | if err != nil {
18 | config.Logger.Error("import file not exists",
19 | zap.String("reason", err.Error()),
20 | zap.String("path", file.Name()))
21 | return nil, err
22 | }
23 |
24 | // 支持 115优化大师导出的json "fold_name":
25 | stringFold115 := string(metaBytes)
26 | if strings.Index(stringFold115, "\"fold_name\":") != -1 {
27 | stringFold115 = strings.Replace(stringFold115, "\"fold_name\":", "\"dir_name\":", -1)
28 | stringFold115 = strings.Replace(stringFold115, "\"sub_fold\": [", "\"dirs\": [", -1)
29 | metaBytes = []byte(stringFold115)
30 | } else {
31 | return nil, errors.New("Error Format ")
32 | }
33 |
34 | metaDir := dir.NewDir()
35 | err = metaDir.Load(metaBytes)
36 | if err != nil {
37 | return nil, err
38 | } else {
39 | return metaDir, nil
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/compatible/self_json.go:
--------------------------------------------------------------------------------
1 | package compatible
2 |
3 | import (
4 | "github.com/gawwo/fake115-go/config"
5 | "github.com/gawwo/fake115-go/dir"
6 | "go.uber.org/zap"
7 | "io/ioutil"
8 | "os"
9 | )
10 |
11 | type SelfJson struct{}
12 |
13 | func (s *SelfJson) Decode(file *os.File) (*dir.Dir, error) {
14 | metaBytes, err := ioutil.ReadAll(file)
15 | if err != nil {
16 | config.Logger.Error("import file not exists",
17 | zap.String("reason", err.Error()),
18 | zap.String("path", file.Name()))
19 | return nil, err
20 | }
21 |
22 | metaDir := dir.NewDir()
23 | err = metaDir.Load(metaBytes)
24 | if err != nil {
25 | return nil, err
26 | } else {
27 | return metaDir, nil
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/gawwo/fake115-go/log"
5 | "time"
6 | )
7 |
8 | const (
9 | Version = "1.4.0"
10 | ServerName = "fake115"
11 | AppVer = "25.2.2"
12 | EndString = "000000"
13 | UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 " +
14 | "Safari/537.36 115Browser/23.9.2"
15 |
16 | spiderCheckIntervalInt = 20
17 | SpiderCheckInterval = time.Second * spiderCheckIntervalInt
18 |
19 | LinkSep = "|"
20 | DirTargetPrefix = "U_1_"
21 | )
22 |
23 | var (
24 | RetryTimes = 10
25 | UserId = ""
26 | UserKey = ""
27 | Step = 1150
28 | // Cookie 提供文件读取和命令行设置两种方式;
29 | // 文件读取提供默认设置和命令行设置两种方式;
30 | Cookie = ""
31 | DefaultCookiePath = "cookies.txt"
32 | CookiePath = ""
33 |
34 | Debug bool
35 |
36 | WorkerNum = 1
37 | WorkerNumRate = 100
38 |
39 | // SpiderVerification 是否处于等待人机验证的状态
40 | // 不是一个重要的状态,且可能的操作中都是在处理网络请
41 | // 求处理之后,冲突可能极小,不加读写锁,随便改
42 | SpiderVerification = false
43 | SpiderStatWaitAliveTime = 0
44 | SpiderStatWaitTimeout = spiderCheckIntervalInt * 3
45 |
46 | Logger = log.InitLogger(ServerName, false)
47 |
48 | NetworkInterval = 3
49 | // FilterSize 115对文件导入导出频率进行限制后,不能再像以前那样大量导出
50 | // 文件,添加小文件过滤以提高效率
51 | FilterSize = 0
52 | )
53 |
54 | var fakeHeaders = map[string]string{
55 | "User-Agent": UserAgent,
56 | }
57 |
58 | func GetFakeHeaders(withCookie bool) map[string]string {
59 |
60 | copyMap := map[string]string{}
61 | for k, v := range fakeHeaders {
62 | copyMap[k] = v
63 | }
64 |
65 | if withCookie {
66 | copyMap["Cookie"] = Cookie
67 | }
68 |
69 | return copyMap
70 | }
71 |
72 | var fakeRangeHeaders = map[string]string{
73 | "Range": "bytes=0-131071",
74 | "User-Agent": UserAgent,
75 | }
76 |
77 | func GetFakeRangeHeaders() map[string]string {
78 | copyMap := map[string]string{}
79 | for k, v := range fakeRangeHeaders {
80 | copyMap[k] = v
81 | }
82 | return copyMap
83 | }
84 |
--------------------------------------------------------------------------------
/core/exporter.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "fmt"
5 | "github.com/gawwo/fake115-go/config"
6 | "github.com/gawwo/fake115-go/dir"
7 | "github.com/gawwo/fake115-go/utils"
8 | "go.uber.org/zap"
9 | "runtime"
10 | "sync"
11 | "time"
12 | )
13 |
14 | type ExportTask struct {
15 | Dir *dir.Dir
16 | File *NetFile
17 | }
18 |
19 | type exporter struct {
20 | taskChannel chan ExportTask
21 | consumerWaitGroup sync.WaitGroup
22 | // 通过pool支持设置上限
23 | producerWaitGroupPool *utils.WaitGroupPool
24 | lock sync.Mutex
25 | FileCount int
26 | FileTotalSize int
27 | }
28 |
29 | func NewExporter() *exporter {
30 | return &exporter{
31 | taskChannel: make(chan ExportTask, config.WorkerNum*config.WorkerNumRate),
32 | consumerWaitGroup: sync.WaitGroup{},
33 | producerWaitGroupPool: utils.NewWaitGroupPool(config.WorkerNum),
34 | FileCount: 0,
35 | FileTotalSize: 0,
36 | }
37 | }
38 |
39 | // 原地修改meta的信息,当调用结束,meta应该是一个完整的目录
40 | func (e *exporter) scanDir(cid string, meta *dir.Dir) {
41 | defer func() {
42 | if config.Debug {
43 | fmt.Println("Dir digger on work number: ", e.producerWaitGroupPool.Size())
44 | }
45 | // 防止goroutine过早的退出,过早退出会导致sem的Wait可能过早的
46 | // 返回,但实际上下一个goroutine还没有Add到信号量,Wait
47 | // 返回后还会导致传递task的通道关闭,进而导致整个任务提早结束
48 | runtime.Gosched()
49 | e.producerWaitGroupPool.Done()
50 | }()
51 |
52 | e.producerWaitGroupPool.Add()
53 |
54 | time.Sleep(time.Second * time.Duration(config.NetworkInterval))
55 |
56 | offset := 0
57 | for {
58 | dirInfo, err := ScanDirWithOffset(cid, offset)
59 | if err != nil {
60 | config.Logger.Warn(err.Error())
61 | return
62 | }
63 |
64 | meta.DirName = dirInfo.Path[len(dirInfo.Path)-1].Name
65 |
66 | for _, item := range dirInfo.Data {
67 | if item.Fid != "" {
68 | // 过滤太小的文件
69 | if config.FilterSize<<10 > item.Size {
70 | continue
71 | }
72 | // 处理文件
73 | // 把任务通过channel派发出去
74 | task := ExportTask{Dir: meta, File: item}
75 | e.taskChannel <- task
76 | } else if item.Cid != "" {
77 | // 处理文件夹
78 | innerMeta := dir.NewDir()
79 | meta.Dirs = append(meta.Dirs, innerMeta)
80 | go e.scanDir(item.Cid, innerMeta)
81 | }
82 | }
83 |
84 | // 翻页
85 | if dirInfo.Count-(offset+config.Step) > 0 {
86 | offset += config.Step
87 | continue
88 | } else {
89 | config.Logger.Info("scan dir success", zap.String("name", meta.DirName))
90 | return
91 | }
92 | }
93 | }
94 |
95 | func (e *exporter) ScanDir(cid string) *dir.Dir {
96 | // 开启消费者
97 | e.consumerWaitGroup.Add(config.WorkerNum)
98 | for i := 0; i < config.WorkerNum; i++ {
99 | go e.exportConsumer()
100 | }
101 |
102 | // 开启生产者
103 | // meta是提取资源的抓手
104 | meta := dir.NewDir()
105 | e.scanDir(cid, meta)
106 |
107 | // 等待生产者资源枯竭之后,关闭channel
108 | e.producerWaitGroupPool.Wait()
109 | close(e.taskChannel)
110 |
111 | // 等待消费者完成任务
112 | e.consumerWaitGroup.Wait()
113 |
114 | return meta
115 | }
116 |
117 | func (e *exporter) exportConsumer() {
118 | // WorkerChannel关闭前一直工作,直到生产者枯竭
119 | for task := range e.taskChannel {
120 | if config.Debug {
121 | fmt.Println("channel len: ", len(e.taskChannel))
122 | }
123 | // 有recover,保证这里不会panic,能让任务持续进行
124 | time.Sleep(time.Second * time.Duration(config.NetworkInterval))
125 | result := task.File.Export()
126 | if result == "" {
127 | config.Logger.Warn("export failed", zap.String("name", task.File.Name))
128 | continue
129 | }
130 |
131 | // 扫尾工作,添加记录到dir对象,累加文件总大小
132 | e.lock.Lock()
133 | task.Dir.Files = append(task.Dir.Files, result)
134 | e.FileTotalSize += task.File.Size
135 | e.FileCount += 1
136 | e.lock.Unlock()
137 | }
138 | e.consumerWaitGroup.Done()
139 | }
140 |
141 | func Export(cid string) (path string) {
142 | exporter := NewExporter()
143 | dirMeta := exporter.ScanDir(cid)
144 | exportName := fmt.Sprintf("115sha1_%s_%dGB.json", dirMeta.DirName,
145 | exporter.FileTotalSize>>30)
146 | outPath, err := dirMeta.Dump(exportName)
147 | if err != nil {
148 | fmt.Println("导出到文件失败")
149 | return
150 | }
151 |
152 | fmt.Printf("导出文件%s成功, 文件数: %d\n", exportName, exporter.FileCount)
153 | return outPath
154 | }
155 |
--------------------------------------------------------------------------------
/core/importer.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "fmt"
5 | "github.com/gawwo/fake115-go/compatible"
6 | "runtime"
7 | "sync"
8 | "time"
9 |
10 | "github.com/gawwo/fake115-go/dir"
11 |
12 | "github.com/gawwo/fake115-go/config"
13 | "github.com/gawwo/fake115-go/utils"
14 | "go.uber.org/zap"
15 | )
16 |
17 | type ImportTask struct {
18 | File *NetFile
19 | }
20 |
21 | type importer struct {
22 | taskChannel chan ImportTask
23 | consumerWaitGroup sync.WaitGroup
24 | // 通过pool支持设置上限
25 | producerWaitGroupPool *utils.WaitGroupPool
26 | lock sync.Mutex
27 | FileCount int
28 | FileTotalSize int
29 | }
30 |
31 | func NewImporter() *importer {
32 | return &importer{
33 | taskChannel: make(chan ImportTask, config.WorkerNum*config.WorkerNumRate),
34 | consumerWaitGroup: sync.WaitGroup{},
35 | producerWaitGroupPool: utils.NewWaitGroupPool(config.WorkerNum),
36 | FileCount: 0,
37 | FileTotalSize: 0,
38 | }
39 | }
40 |
41 | func (i *importer) importDir(pid string, meta *dir.Dir) {
42 | defer func() {
43 | if config.Debug {
44 | fmt.Println("Dir digger on work number: ", i.producerWaitGroupPool.Size())
45 | }
46 |
47 | runtime.Gosched()
48 | i.producerWaitGroupPool.Done()
49 | }()
50 |
51 | i.producerWaitGroupPool.Add()
52 |
53 | time.Sleep(time.Second * time.Duration(config.NetworkInterval))
54 |
55 | var cid string
56 |
57 | // 需要创建一下文件夹
58 | cid = meta.MakeNetDir(pid)
59 | if cid == "" {
60 | config.Logger.Warn("create dir fail",
61 | zap.String("name", meta.DirName))
62 | return
63 | }
64 |
65 | // 提交导入任务到channel中
66 | for _, fileString := range meta.Files {
67 | netFile := CreateNetFile(fileString)
68 | if netFile == nil {
69 | config.Logger.Warn("error format net file raw content",
70 | zap.String("content", fileString))
71 | continue
72 | }
73 | // 过滤太小的文件
74 | if config.FilterSize<<10 > netFile.Size {
75 | continue
76 | }
77 | netFile.Cid = cid
78 | task := ImportTask{File: netFile}
79 | i.taskChannel <- task
80 | }
81 |
82 | // 处理内层的文件夹
83 | for _, itemDir := range meta.Dirs {
84 | if itemDir.HasFile() {
85 | go i.importDir(cid, itemDir)
86 | }
87 | }
88 | }
89 |
90 | func (i *importer) importConsumer() {
91 | for task := range i.taskChannel {
92 | if config.Debug {
93 | fmt.Println("channel len: ", len(i.taskChannel))
94 | }
95 |
96 | time.Sleep(time.Second * time.Duration(config.NetworkInterval))
97 | result := task.File.Import()
98 | if !result {
99 | config.Logger.Warn("import failed", zap.String("name", task.File.Name))
100 | continue
101 | }
102 |
103 | i.lock.Lock()
104 | i.FileTotalSize += task.File.Size
105 | i.FileCount += 1
106 | i.lock.Unlock()
107 | }
108 | i.consumerWaitGroup.Done()
109 | }
110 |
111 | func (i *importer) ImportDir(cid string, meta *dir.Dir) {
112 | // 开启消费者
113 | i.consumerWaitGroup.Add(config.WorkerNum)
114 | for n := 0; n < config.WorkerNum; n++ {
115 | go i.importConsumer()
116 | }
117 |
118 | // 开启生产者
119 | i.importDir(cid, meta)
120 |
121 | // 等待生产者资源枯竭之后,关闭channel
122 | i.producerWaitGroupPool.Wait()
123 | close(i.taskChannel)
124 |
125 | i.consumerWaitGroup.Wait()
126 | }
127 |
128 | func Import(cid, metaPath string) {
129 | metaDir := compatible.Decode(metaPath)
130 | if metaDir == nil {
131 | fmt.Println("未找到可处理的格式")
132 | return
133 | }
134 |
135 | importer := NewImporter()
136 | importer.ImportDir(cid, metaDir)
137 |
138 | fmt.Printf("导入文件%dGB,文件数%d\n", importer.FileTotalSize>>30, importer.FileCount)
139 | }
140 |
--------------------------------------------------------------------------------
/core/net_dir.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | type NetDirTrace struct {
4 | Name string `json:"name"`
5 | }
6 |
7 | // NetDir 115的网络目录
8 | type NetDir struct {
9 | Count int `json:"count"`
10 | Path []*NetDirTrace `json:"path"`
11 | Data []*NetFile `json:"data"`
12 | }
13 |
--------------------------------------------------------------------------------
/core/net_file.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/gawwo/fake115-go/config"
7 | "github.com/gawwo/fake115-go/utils"
8 | "github.com/valyala/fastjson"
9 | "go.uber.org/zap"
10 | "os"
11 | "strconv"
12 | "strings"
13 | "time"
14 | )
15 |
16 | var (
17 | jsonParser fastjson.Parser
18 | )
19 |
20 | // NetFile 115的文件对象,这个对象指向的可能是文件,也可能是文件夹
21 | type NetFile struct {
22 | // 有fid就是文件
23 | Fid string `json:"fid"`
24 | // 有cid但没有Fid,就是文件夹
25 | Cid string `json:"cid"`
26 | // 文件大小
27 | Size int `json:"s"`
28 | Name string `json:"n"`
29 | Sha string `json:"sha"`
30 | Pc string `json:"pc"`
31 | }
32 |
33 | type downloadBody struct {
34 | State bool `json:"state"`
35 | Msg string `json:"msg"`
36 | Data string `json:"data"`
37 | Errno int `json:"errno"`
38 | }
39 |
40 | type importBody struct {
41 | Status int `json:"status"`
42 | StatusCode int `json:"statuscode"`
43 | }
44 |
45 | // Export 开启一定量的worker,通过channel接收任务,channel有一定的缓冲区
46 | // worker在接收到任务后执行任务,当遇到需要人机验证的时候,改变全局
47 | // 变量,然后进入循环等待模式,期间一直检测,直到人机验证完成;
48 | // Note: 只要用到Lock的地方,都要考虑超时问题
49 | // 将当前网络文件的内容导出到目录中
50 | func (file *NetFile) Export() string {
51 | // 保证worker不会panic
52 | defer func() {
53 | if err := recover(); err != nil {
54 | config.Logger.Error("export link error",
55 | zap.String("name", file.Name),
56 | zap.String("reason", fmt.Sprintf("%v", err)))
57 |
58 | // 在报错的情况下,如果依然处于人机验证的阻塞状态,就解除状态。不
59 | // 希望因为一个处于检测状态的任务panic而导致整个任务卡死
60 | if config.SpiderVerification {
61 | config.SpiderVerification = false
62 | }
63 | }
64 | }()
65 |
66 | url := file.extractDownloadInfo()
67 |
68 | fileSha1 := file.extractFileSha1(url)
69 | if fileSha1 == "" {
70 | return ""
71 | }
72 |
73 | file.Name = strings.ReplaceAll(file.Name, config.LinkSep, "_")
74 | joinStrings := []string{file.Name, strconv.Itoa(file.Size), file.Sha, fileSha1}
75 | result := strings.Join(joinStrings, config.LinkSep)
76 |
77 | var formatSize string
78 | sizeM := file.Size >> 20
79 | if sizeM == 0 {
80 | formatSize = "小于1MB"
81 | } else {
82 | formatSize = fmt.Sprintf("%dMB", sizeM)
83 | }
84 |
85 | nowFormat := time.Now().Format("2006-01-02 15:04:05")
86 | fmt.Printf("%s 导出成功,大小: %s\t文件: %s\n", nowFormat, formatSize, file.Name)
87 | config.Logger.Info("export success", zap.String("name", file.Name), zap.Int("size", file.Size))
88 | return result
89 | }
90 |
91 | func (file *NetFile) extractDownloadInfo() (downloadUrl string) {
92 | downUrl := "https://proapi.115.com/app/chrome/downurl"
93 | headers := config.GetFakeHeaders(true)
94 | for {
95 | // 先检查是否在等待人机验证状态
96 | headOff := config.SpiderVerification
97 | if headOff {
98 | // 太长时间的停滞之后,确保真的有worker去查询,设置
99 | // 一个超时时间
100 | if int(time.Now().Unix())-config.SpiderStatWaitAliveTime > config.SpiderStatWaitTimeout {
101 | config.SpiderStatWaitAliveTime = int(time.Now().Unix())
102 | goto Work
103 | }
104 | config.Logger.Info(fmt.Sprintf("waiting Man-machine verification: %s", file.Name))
105 | time.Sleep(config.SpiderCheckInterval / 2)
106 | continue
107 | }
108 |
109 | Work:
110 | cipher, err := utils.NewCipher()
111 | if err != nil {
112 | config.Logger.Fatal(err.Error())
113 | os.Exit(1)
114 | }
115 |
116 | text, err := cipher.Encrypt([]byte(fmt.Sprintf(`{"pickcode":"%s"}`, file.Pc)))
117 | if err != nil {
118 | config.Logger.Warn("encrypt data error",
119 | zap.String("name", file.Name))
120 | return
121 | }
122 | postData := map[string]string{"data": string(text)}
123 | body, err := utils.PostForm(downUrl, headers, postData)
124 | if err != nil {
125 | config.Logger.Warn("export file network error",
126 | zap.String("name", file.Name))
127 | return
128 | }
129 |
130 | parsedDownloadBody := new(downloadBody)
131 | err = json.Unmarshal(body, parsedDownloadBody)
132 | if err != nil {
133 | config.Logger.Warn("parse download body fail",
134 | zap.String("content", string(body)),
135 | zap.String("name", file.Name))
136 | return
137 | }
138 |
139 | // ==================验证文件下载地址是否正常==================
140 | // 文件状态异常
141 | if !parsedDownloadBody.State {
142 | config.Logger.Warn("download file state odd",
143 | zap.String("content", parsedDownloadBody.Msg),
144 | zap.String("name", file.Name))
145 | return
146 | }
147 |
148 | // 有多个worker因为时间差,都进入人机检测验证状态,也无所谓
149 | // 进入人机验证之后,反复检测状态
150 | if parsedDownloadBody.Errno == 911 {
151 | fmt.Println("发现人机验证,请到115浏览器中播放任意一个视频,完成人机检测...")
152 | config.Logger.Warn("found Man-machine verification, waiting...")
153 | config.SpiderVerification = true
154 | config.SpiderStatWaitAliveTime = int(time.Now().Unix())
155 | time.Sleep(config.SpiderCheckInterval)
156 | goto Work
157 | }
158 | // 如果有人机验证状态,取消人机验证状态
159 | if config.SpiderVerification {
160 | config.SpiderVerification = false
161 | }
162 |
163 | // 返回的下载信息中不包含下载地址
164 | if parsedDownloadBody.Data == "" {
165 | config.Logger.Warn("download file body not contain download url",
166 | zap.String("content", fmt.Sprintf("%v", parsedDownloadBody)),
167 | zap.String("name", file.Name))
168 | return
169 | }
170 |
171 | downloadUrlContent, err := cipher.Decrypt([]byte(parsedDownloadBody.Data))
172 | if err != nil {
173 | config.Logger.Warn("decrypt download url error",
174 | zap.String("name", file.Name))
175 | return
176 | }
177 |
178 | extractedDownloadUrl, err := extractDownloadUrl(downloadUrlContent, file.Fid)
179 | if err != nil {
180 | config.Logger.Warn("parse download url error",
181 | zap.String("name", file.Name))
182 | return
183 | }
184 | return extractedDownloadUrl
185 | }
186 | }
187 |
188 | func extractDownloadUrl(downloadContent []byte, fileId string) (string, error) {
189 | parsedValue, err := jsonParser.ParseBytes(downloadContent)
190 | if err != nil {
191 | return "", err
192 | }
193 | return string(parsedValue.GetStringBytes(fileId, "url", "url")), nil
194 | }
195 |
196 | func (file *NetFile) extractFileSha1(downloadUrl string) string {
197 | downloadHeader := config.GetFakeRangeHeaders()
198 |
199 | body, err := utils.Get(downloadUrl, downloadHeader, nil)
200 | if err != nil {
201 | config.Logger.Warn("get file header to calculate file sha1 fail", zap.String("name", file.Name))
202 | return ""
203 | }
204 |
205 | sha1 := utils.Sha1(body)
206 | return strings.ToUpper(sha1)
207 | }
208 |
209 | // Import 导入时,要指定文件所属的Cid(文件夹),文件夹不存在就需要创建;
210 | // 创建文件夹的方法是,指定这个文件夹的父文件夹,填入文件夹的名字
211 | // 之后创建,返回Cid,在做这个任务的时候,Cid需要是创建好的文件夹;
212 | // 创建文件夹的工作在调用这个函数的地方提前准备好,这里不涉及创建文
213 | // 件夹
214 | func (file *NetFile) Import() bool {
215 | // 保证worker不会panic
216 | defer func() {
217 | if err := recover(); err != nil {
218 | config.Logger.Error("import link error",
219 | zap.String("name", file.Name),
220 | zap.String("reason", fmt.Sprintf("%v", err)))
221 | }
222 | }()
223 |
224 | if file.Cid == "" {
225 | config.Logger.Warn("empty target dir")
226 | return false
227 | }
228 |
229 | target := config.DirTargetPrefix + file.Cid
230 | shaFirstJoinStrings := []string{config.UserId, file.Sha, file.Sha, target, "0"}
231 | shaFirstRaw := strings.Join(shaFirstJoinStrings, "")
232 | shaFirst := utils.Sha1([]byte(shaFirstRaw))
233 |
234 | shaSecondRaw := config.UserKey + shaFirst + config.EndString
235 | sig := strings.ToUpper(utils.Sha1([]byte(shaSecondRaw)))
236 |
237 | url := fmt.Sprintf("http://uplb.115.com/3.0/initupload.php?isp=0&appid=0&appversion=%s&format=json&sig=%s",
238 | config.AppVer, sig)
239 | postData := map[string]string{
240 | "preid": file.Pc,
241 | "filename": file.Name,
242 | "quickid": file.Sha,
243 | "app_ver": config.AppVer,
244 | "filesize": strconv.Itoa(file.Size),
245 | "userid": config.UserId,
246 | "exif": "",
247 | "target": target,
248 | "fileid": file.Sha,
249 | }
250 |
251 | headers := config.GetFakeHeaders(true)
252 | body, err := utils.PostForm(url, headers, postData)
253 | if err != nil {
254 | config.Logger.Warn("import file network error",
255 | zap.String("name", file.Name))
256 | return false
257 | }
258 |
259 | parsedImportBody := new(importBody)
260 | err = json.Unmarshal(body, parsedImportBody)
261 | if err != nil {
262 | config.Logger.Warn("parse import body fail",
263 | zap.String("content", string(body)),
264 | zap.String("name", file.Name))
265 | return false
266 | }
267 |
268 | if parsedImportBody.Status == 2 && parsedImportBody.StatusCode == 0 {
269 | var formatSize string
270 | sizeM := file.Size >> 20
271 | if sizeM == 0 {
272 | formatSize = "小于1MB"
273 | } else {
274 | formatSize = fmt.Sprintf("%dMB", sizeM)
275 | }
276 | nowFormat := time.Now().Format("2006-01-02 15:04:05")
277 | fmt.Printf("%s 导入成功,大小: %s\t文件: %s\n", nowFormat, formatSize, file.Name)
278 | return true
279 | } else {
280 | config.Logger.Warn("import info not expect",
281 | zap.String("content", string(body)),
282 | zap.String("name", file.Name))
283 | return false
284 | }
285 | }
286 |
287 | // CreateNetFile 格式不对,创建NetFile不一定成功
288 | func CreateNetFile(fileInfo string) *NetFile {
289 | splitStrings := strings.Split(fileInfo, config.LinkSep)
290 | if len(splitStrings) != 4 {
291 | return nil
292 | }
293 | size, err := strconv.Atoi(splitStrings[1])
294 | if err != nil {
295 | return nil
296 | }
297 |
298 | return &NetFile{
299 | Name: splitStrings[0],
300 | Size: size,
301 | Sha: splitStrings[2],
302 | Pc: splitStrings[3],
303 | }
304 | }
305 |
--------------------------------------------------------------------------------
/core/network.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "github.com/gawwo/fake115-go/config"
8 | "github.com/gawwo/fake115-go/utils"
9 | "go.uber.org/zap"
10 | "strconv"
11 | )
12 |
13 | type userInfo struct {
14 | UserId int `json:"user_id"`
15 | UserKey string `json:"userkey"`
16 | ErrorNo int `json:"errno"`
17 | }
18 |
19 | func SetUserInfoConfig() bool {
20 | url := "http://proapi.115.com/app/uploadinfo"
21 | header := config.GetFakeHeaders(true)
22 | body, err := utils.Get(url, header, nil)
23 | if err != nil {
24 | return false
25 | }
26 |
27 | jsonUserInfo := new(userInfo)
28 | err = json.Unmarshal(body, jsonUserInfo)
29 | if err != nil {
30 | config.Logger.Error("Get login information error",
31 | zap.Binary("content", body))
32 | return false
33 | }
34 |
35 | if jsonUserInfo.ErrorNo == 99 {
36 | config.Logger.Error("Login expire")
37 | return false
38 | } else if jsonUserInfo.UserKey == "" {
39 | config.Logger.Error("Get login information error",
40 | zap.String("content", string(body)))
41 | return false
42 | }
43 |
44 | // 设置用户信息
45 | config.UserId = strconv.Itoa(jsonUserInfo.UserId)
46 | config.UserKey = jsonUserInfo.UserKey
47 |
48 | return true
49 | }
50 |
51 | func ScanDirWithOffset(cid string, offset int) (*NetDir, error) {
52 | urls := []string{
53 | fmt.Sprintf("https://webapi.115.com/files?aid=1&cid=%s&o=file_name&asc=0&offset=%d&"+
54 | "show_dir=1&limit=%d&code=&scid=&snap=0&natsort=1&record_open_time=1&source=&format=json&type=&star=&"+
55 | "is_share=&suffix=&fc_mix=1&is_q=&custom_order=", cid, offset, config.Step),
56 | fmt.Sprintf("http://aps.115.com/natsort/files.php?aid=1&cid=%s&o=file_name&asc=1&offset=%d&show_dir=1"+
57 | "&limit=%d&code=&scid=&snap=0&natsort=1&source=&format=json&type=&star=&is_share=&suffix=&custom_order="+
58 | "&fc_mix=", cid, offset, config.Step),
59 | }
60 |
61 | for _, url := range urls {
62 | headers := config.GetFakeHeaders(true)
63 | body, err := utils.Get(url, headers, nil)
64 | if err != nil {
65 | continue
66 | }
67 |
68 | netDir := new(NetDir)
69 | err = json.Unmarshal(body, netDir)
70 | if err != nil {
71 | continue
72 | }
73 | if netDir.Path == nil {
74 | continue
75 | } else {
76 | return netDir, nil
77 | }
78 | }
79 | return nil, errors.New("both url fail get dir info, maybe login expire")
80 | }
81 |
--------------------------------------------------------------------------------
/core/network_test.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 | )
7 |
8 | func TestUserInfo(t *testing.T) {
9 | userInfoRaw := `{"uploadinfo":"","user_id":31395269,"app_version":0,"app_id":0,"userkey":"8684E264F90403CBE72F173843D2D83476287E67","url_upload":"http:\/\/uplb.115.com\/2.0\/upload.php","url_resume":"http:\/\/uplb.115.com\/2.0\/resume.php","url_cancel":"http:\/\/uplb.115.com\/2.0\/cancel.php","url_speed":"http:\/\/119.147.156.144\/2.0\/bigupload","url_speed_test":{"1":"http:\/\/119.147.156.144\/ST","2":"http:\/\/58.253.94.207\/ST"},"size_limit":123480309760,"size_limit_yun":209715200,"max_dir_level":25,"max_dir_level_yun":25,"max_file_num":50000,"max_file_num_yun":10000,"upload_allowed":true,"upload_allowed_msg":"","type_limit":["doc","docx","xls","pdf","ppt","wps","dps","et","mdb","reg","txt","wri","rtf","lrc","vob","sub","srt","ass","ssa","idx","jar","umd","xlsx",".xlsm","xltx","xltm","xlam","xlsb","odt","pptx","ods","odp","chm","pot","pps","ppsx"],"file_range":{"2":"0-67108864","1":"67108864-0"},"isp_type":0,"state":true,"error":"","errno":0}`
10 | jsonUserInfo := new(userInfo)
11 |
12 | err := json.Unmarshal([]byte(userInfoRaw), jsonUserInfo)
13 | if err != nil {
14 | t.Error()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/dir/dir.go:
--------------------------------------------------------------------------------
1 | package dir
2 |
3 | import (
4 | "bufio"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/gawwo/fake115-go/config"
8 | "github.com/gawwo/fake115-go/utils"
9 | "go.uber.org/zap"
10 | "os"
11 | )
12 |
13 | type Dir struct {
14 | DirName string `json:"dir_name"`
15 | Files []string `json:"files"`
16 | Dirs []*Dir `json:"dirs"`
17 | }
18 |
19 | type dirMake struct {
20 | State bool `json:"state"`
21 | Error string `json:"error"`
22 | Cid string `json:"cid"`
23 | }
24 |
25 | // NewDir 强行指定初始化,防止json之后,Dirs和Files为null
26 | func NewDir() *Dir {
27 | return &Dir{Dirs: []*Dir{}, Files: []string{}}
28 | }
29 |
30 | func (dir *Dir) Dumps() ([]byte, error) {
31 | data, err := json.Marshal(dir)
32 | if err != nil {
33 | return nil, err
34 | }
35 |
36 | return data, nil
37 | }
38 |
39 | // Dump 导出到文件中
40 | func (dir *Dir) Dump(outPath string) (string, error) {
41 | data, err := dir.Dumps()
42 | if err != nil {
43 | return "", err
44 | }
45 |
46 | f, err := os.OpenFile(outPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
47 | if err != nil {
48 | return "", err
49 | }
50 | defer f.Close()
51 |
52 | writer := bufio.NewWriter(f)
53 | _, err = writer.Write(data)
54 | if err != nil {
55 | return "", nil
56 | }
57 | writer.Flush()
58 |
59 | return outPath, nil
60 | }
61 |
62 | func (dir *Dir) Load(fileContent []byte) error {
63 | err := json.Unmarshal(fileContent, dir)
64 | if err != nil {
65 | return err
66 | }
67 | return nil
68 | }
69 |
70 | // HasFile 递归探测文件夹中是否有文件
71 | func (dir *Dir) HasFile() bool {
72 | if len(dir.Files) > 0 {
73 | return true
74 | }
75 |
76 | for _, innerDir := range dir.Dirs {
77 | if innerDir.HasFile() {
78 | return true
79 | }
80 | }
81 | return false
82 | }
83 |
84 | // MakeNetDir 创建新的文件夹
85 | func (dir *Dir) MakeNetDir(pid string) string {
86 | defer func() {
87 | if err := recover(); err != nil {
88 | config.Logger.Error("create dir error",
89 | zap.String("name", dir.DirName),
90 | zap.String("reason", fmt.Sprintf("%v", err)))
91 | }
92 | }()
93 |
94 | url := "https://webapi.115.com/files/add"
95 | for i := 0; i < config.RetryTimes; i++ {
96 | var dirName string
97 | if i != 0 {
98 | dirName = fmt.Sprintf("%s_%d", dir.DirName, i)
99 | } else {
100 | dirName = dir.DirName
101 | }
102 |
103 | data := map[string]string{
104 | "pid": pid,
105 | "cname": dirName,
106 | }
107 | headers := config.GetFakeHeaders(true)
108 | body, err := utils.PostForm(url, headers, data)
109 | if err != nil {
110 | config.Logger.Warn("make dir fail", zap.String("name", dirName))
111 | return ""
112 | }
113 |
114 | dirMakeResult := new(dirMake)
115 | err = json.Unmarshal(body, dirMakeResult)
116 | if err != nil {
117 | config.Logger.Warn("parse make dir result fail",
118 | zap.String("reason", err.Error()),
119 | zap.String("name", dir.DirName))
120 | return ""
121 | }
122 |
123 | if dirMakeResult.State {
124 | return dirMakeResult.Cid
125 | }
126 |
127 | if dirMakeResult.Error == "该目录名称已存在。" {
128 | config.Logger.Warn("dir had exists, change name to continue",
129 | zap.String("name", dir.DirName))
130 | continue
131 | }
132 |
133 | return ""
134 | }
135 | return ""
136 | }
137 |
--------------------------------------------------------------------------------
/dir/dir_test.go:
--------------------------------------------------------------------------------
1 | package dir
2 |
3 | import (
4 | "strings"
5 | "testing"
6 | )
7 |
8 | func TestLoad(t *testing.T) {
9 | info := `{"file_name": "\u4e16\u754c\u81ea\u7136\u9057\u4ea7", "files": ["readme.txt"], "dirs": [{"file_name": "\u4e2d\u56fd", "files": ["\u6210\u90fd.mp4"], "dirs": []}]}`
10 | file := NewDir()
11 | err := file.Load([]byte(info))
12 | if err != nil {
13 | t.Error("Load error", err.Error())
14 | }
15 | }
16 |
17 | func TestDump(t *testing.T) {
18 | mark := "中国"
19 | file := Dir{
20 | DirName: "mark",
21 | Files: []string{"readme.txt"},
22 | Dirs: []*Dir{
23 | {
24 | DirName: "中国",
25 | Files: []string{"成都.mp4"},
26 | Dirs: []*Dir{},
27 | },
28 | },
29 | }
30 |
31 | dump, err := file.Dumps()
32 | if err != nil {
33 | t.Errorf(err.Error())
34 | }
35 | contains := strings.Contains(string(dump), mark)
36 | if !contains {
37 | t.Errorf("format not contain mark: %s", dump)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gawwo/fake115-go
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/valyala/fastjson v1.6.3
7 | go.uber.org/zap v1.16.0
8 | gopkg.in/natefinch/lumberjack.v2 v2.0.0
9 | )
10 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
7 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
8 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
9 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
10 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
11 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
12 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
13 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
14 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
19 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
20 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
21 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
22 | github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
23 | github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
24 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
25 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
26 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
27 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
28 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
29 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
30 | go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
31 | go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
32 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
33 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
34 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
35 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
36 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
37 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
38 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
39 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
40 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
41 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
42 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
43 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
44 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
45 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
46 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
47 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
48 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
49 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
50 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
51 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
52 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
53 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
54 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
55 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
56 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
57 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
58 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
59 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
60 |
--------------------------------------------------------------------------------
/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "go.uber.org/zap"
5 | "go.uber.org/zap/zapcore"
6 | "gopkg.in/natefinch/lumberjack.v2"
7 | "os"
8 | "time"
9 | )
10 |
11 | var Logger *zap.Logger
12 |
13 | func InitLogger(serverName string, stdout bool) *zap.Logger {
14 | infoHook := lumberjack.Logger{
15 | Filename: "./logs/info.log", // 日志文件路径
16 | MaxSize: 32, // 每个日志文件保存的最大尺寸 单位:M
17 | MaxBackups: 30, // 日志文件最多保存多少个备份
18 | MaxAge: 30, // 文件最多保存多少天
19 | Compress: true, // 是否压缩
20 | LocalTime: true, // 本地时间
21 | }
22 | warnHook := lumberjack.Logger{
23 | Filename: "./logs/warn.log", // 日志文件路径
24 | MaxSize: 32, // 每个日志文件保存的最大尺寸 单位:M
25 | MaxBackups: 30, // 日志文件最多保存多少个备份
26 | MaxAge: 365, // 文件最多保存多少天
27 | Compress: true, // 是否压缩
28 | LocalTime: true, // 本地时间
29 |
30 | }
31 | errorHook := lumberjack.Logger{
32 | Filename: "./logs/error.log", // 日志文件路径
33 | MaxSize: 32, // 每个日志文件保存的最大尺寸 单位:M
34 | MaxBackups: 30, // 日志文件最多保存多少个备份
35 | MaxAge: 365, // 文件最多保存多少天
36 | Compress: true, // 是否压缩
37 | LocalTime: true, // 本地时间
38 |
39 | }
40 |
41 | encoderConfig := zapcore.EncoderConfig{
42 | TimeKey: "time",
43 | LevelKey: "level",
44 | NameKey: "logger",
45 | CallerKey: "line",
46 | MessageKey: "msg",
47 | StacktraceKey: "stacktrace",
48 | LineEnding: zapcore.DefaultLineEnding,
49 | EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写编码器
50 | EncodeTime: func(time time.Time, encoder zapcore.PrimitiveArrayEncoder) {
51 | encoder.AppendString(time.Format("2006-01-02 15:04:05"))
52 | },
53 | EncodeDuration: zapcore.SecondsDurationEncoder, //
54 | EncodeCaller: zapcore.ShortCallerEncoder, // 短路径编码器
55 | EncodeName: zapcore.FullNameEncoder,
56 | }
57 |
58 | // 设置日志级别
59 | atomicLevel := zap.NewAtomicLevel()
60 | atomicLevel.SetLevel(zap.InfoLevel)
61 |
62 | infoLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
63 | return level == zapcore.InfoLevel
64 | })
65 | warnLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
66 | return level == zapcore.WarnLevel
67 | })
68 | errorLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
69 | return level == zapcore.ErrorLevel
70 | })
71 |
72 | var (
73 | infoMultiWriter zapcore.WriteSyncer
74 | warnMultiWriter zapcore.WriteSyncer
75 | errorMultiWriter zapcore.WriteSyncer
76 | )
77 |
78 | if stdout {
79 | infoMultiWriter = zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&infoHook))
80 | warnMultiWriter = zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&warnHook))
81 | errorMultiWriter = zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&errorHook))
82 | } else {
83 | infoMultiWriter = zapcore.NewMultiWriteSyncer(zapcore.AddSync(&infoHook))
84 | warnMultiWriter = zapcore.NewMultiWriteSyncer(zapcore.AddSync(&warnHook))
85 | errorMultiWriter = zapcore.NewMultiWriteSyncer(zapcore.AddSync(&errorHook))
86 | }
87 | info := zapcore.NewCore(
88 | zapcore.NewJSONEncoder(encoderConfig), // 编码器配置
89 | infoMultiWriter, // 打印到控制台和文件
90 | infoLevel, // 日志级别
91 | )
92 | warn := zapcore.NewCore(
93 | zapcore.NewJSONEncoder(encoderConfig), // 编码器配置
94 | warnMultiWriter, // 打印到控制台和文件
95 | warnLevel, // 日志级别
96 | )
97 | _error := zapcore.NewCore(
98 | zapcore.NewJSONEncoder(encoderConfig), // 编码器配置
99 | errorMultiWriter, // 打印到控制台和文件
100 | errorLevel, // 日志级别
101 | )
102 |
103 | // 开启开发模式,堆栈跟踪
104 | caller := zap.AddCaller()
105 |
106 | // 当前打印日志的位置意义不大,跳过当前位置,线上上层调用
107 | //callSkip := zap.AddCallerSkip(1)
108 |
109 | // 调用堆栈trace
110 | trace := zap.AddStacktrace(zapcore.ErrorLevel)
111 |
112 | // 开启文件及行号
113 | development := zap.Development()
114 |
115 | // 设置初始化字段
116 | filed := zap.Fields(zap.String("serviceName", serverName))
117 |
118 | // 构造日志
119 | core := zapcore.NewTee(
120 | info,
121 | warn,
122 | _error,
123 | )
124 | return zap.New(core, caller, trace, development, filed)
125 | }
126 |
127 | // FileLogger 不区分不同级别日志文件
128 | func FileLogger() *zap.Logger {
129 | // 动态调整日志级别
130 | allLevel := zap.NewAtomicLevel()
131 |
132 | hook := lumberjack.Logger{
133 | Filename: "./logs/logs.log",
134 | MaxSize: 1024, // megabytes
135 | MaxBackups: 3,
136 | MaxAge: 7, //days
137 | Compress: true, // disabled by default
138 | }
139 | w := zapcore.AddSync(&hook)
140 |
141 | allLevel.SetLevel(zap.InfoLevel)
142 | encoderConfig := zap.NewProductionEncoderConfig()
143 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
144 |
145 | core := zapcore.NewCore(
146 | zapcore.NewConsoleEncoder(encoderConfig),
147 | w,
148 | allLevel,
149 | )
150 |
151 | return zap.New(core)
152 | }
153 |
--------------------------------------------------------------------------------
/log/log_bench_test.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "go.uber.org/zap"
5 | "go.uber.org/zap/zapcore"
6 | "io/ioutil"
7 | "testing"
8 | )
9 |
10 | type Syncer struct {
11 | err error
12 | called bool
13 | }
14 |
15 | func (s *Syncer) SetError(err error) {
16 | s.err = err
17 | }
18 |
19 | // Sync records that it was called, then returns the user-supplied error (if
20 | // any).
21 | func (s *Syncer) Sync() error {
22 | s.called = true
23 | return s.err
24 | }
25 |
26 | // Called reports whether the Sync method was called.
27 | func (s *Syncer) Called() bool {
28 | return s.called
29 | }
30 |
31 | type Discarder struct{ Syncer }
32 |
33 | func (d *Discarder) Write(b []byte) (int, error) {
34 | return ioutil.Discard.Write(b)
35 | }
36 |
37 | func TestStabilityLogger(t *testing.T) {
38 |
39 | }
40 |
41 | func withBenchedLogger(b *testing.B, f func(logger *zap.Logger)) {
42 | logger := InitLogger("test", false)
43 |
44 | b.ResetTimer()
45 | b.RunParallel(func(pb *testing.PB) {
46 | for pb.Next() {
47 | f(logger)
48 | }
49 | })
50 | }
51 |
52 | func withBenchedFileLogger(b *testing.B, f func(logger *zap.Logger)) {
53 | logger := FileLogger()
54 |
55 | b.ResetTimer()
56 | b.RunParallel(func(pb *testing.PB) {
57 | for pb.Next() {
58 | f(logger)
59 | }
60 | })
61 | }
62 |
63 | func withBenchedPureLogger(b *testing.B, f func(*zap.Logger)) {
64 | logger := zap.New(
65 | zapcore.NewCore(
66 | zapcore.NewJSONEncoder(zap.NewProductionConfig().EncoderConfig),
67 | &Discarder{},
68 | zap.DebugLevel,
69 | ))
70 | b.ResetTimer()
71 | b.RunParallel(func(pb *testing.PB) {
72 | for pb.Next() {
73 | f(logger)
74 | }
75 | })
76 | }
77 |
78 | func BenchmarkInitLogger(b *testing.B) {
79 | withBenchedLogger(b, func(logger *zap.Logger) {
80 | logger.Info("info")
81 | })
82 | }
83 |
84 | func BenchmarkFileLogger(b *testing.B) {
85 | withBenchedFileLogger(b, func(logger *zap.Logger) {
86 | logger.Info("info")
87 | })
88 | }
89 |
90 | func BenchmarkInitLogger10Field(b *testing.B) {
91 | withBenchedLogger(b, func(logger *zap.Logger) {
92 |
93 | logger.Info("info",
94 | zap.Int("one", 1),
95 | zap.Int("two", 2),
96 | zap.Int("three", 3),
97 | zap.Int("four", 4),
98 | zap.Int("five", 5),
99 | zap.Int("six", 6),
100 | zap.Int("seven", 7),
101 | zap.Int("eight", 8),
102 | zap.Int("nine", 9),
103 | zap.Int("ten", 10))
104 | })
105 | }
106 |
107 | func BenchmarkFileLogger10Field(b *testing.B) {
108 | withBenchedFileLogger(b, func(logger *zap.Logger) {
109 |
110 | logger.Info("info",
111 | zap.Int("one", 1),
112 | zap.Int("two", 2),
113 | zap.Int("three", 3),
114 | zap.Int("four", 4),
115 | zap.Int("five", 5),
116 | zap.Int("six", 6),
117 | zap.Int("seven", 7),
118 | zap.Int("eight", 8),
119 | zap.Int("nine", 9),
120 | zap.Int("ten", 10))
121 | })
122 | }
123 |
124 | func BenchmarkPureLogger10Field(b *testing.B) {
125 | withBenchedPureLogger(b, func(logger *zap.Logger) {
126 |
127 | logger.Info("info",
128 | zap.Int("one", 1),
129 | zap.Int("two", 2),
130 | zap.Int("three", 3),
131 | zap.Int("four", 4),
132 | zap.Int("five", 5),
133 | zap.Int("six", 6),
134 | zap.Int("seven", 7),
135 | zap.Int("eight", 8),
136 | zap.Int("nine", 9),
137 | zap.Int("ten", 10))
138 | })
139 | }
140 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/gawwo/fake115-go/config"
7 | "github.com/gawwo/fake115-go/core"
8 | "github.com/gawwo/fake115-go/log"
9 | "github.com/gawwo/fake115-go/utils"
10 | "os"
11 | "strings"
12 | )
13 |
14 | var showVersion bool
15 |
16 | func init() {
17 | flag.BoolVar(&showVersion, "v", false, "显示版本")
18 | flag.BoolVar(&config.Debug, "d", false, "调试模式")
19 | flag.IntVar(&config.WorkerNum, "n", config.WorkerNum, "同时进行的数量")
20 | flag.IntVar(&config.NetworkInterval, "i", config.NetworkInterval, "网络等待间隔")
21 | flag.IntVar(&config.FilterSize, "f", config.FilterSize, "过滤小于此大小的文件,单位KB")
22 |
23 | // 尝试从外来配置设置cookie文件路径
24 | flag.StringVar(&config.CookiePath, "cp", config.DefaultCookiePath, "Cookie文件路径")
25 |
26 | // 尝试从外来配置设置cookie
27 | flag.StringVar(&config.Cookie, "c", "", "Cookie内容")
28 | // 确保cookie是否真的存在
29 | if config.Cookie == "" {
30 | cookie, _ := utils.ReadCookieFile()
31 | cookie = strings.Trim(cookie, "\n")
32 | config.Cookie = cookie
33 | }
34 | }
35 |
36 | func main() {
37 | flag.Parse()
38 | args := flag.Args()
39 |
40 | if showVersion {
41 | fmt.Println(config.Version)
42 | return
43 | }
44 |
45 | if config.Debug {
46 | config.Logger = log.InitLogger(config.ServerName, true)
47 | }
48 |
49 | if config.WorkerNum <= 0 {
50 | config.WorkerNum = 1
51 | }
52 |
53 | // 确保cookie在登录状态
54 | loggedIn := core.SetUserInfoConfig()
55 | if !loggedIn {
56 | fmt.Println("Login expire or fail...")
57 | os.Exit(1)
58 | }
59 |
60 | if len(args) < 1 {
61 | fmt.Println("Too few arguments")
62 | return
63 | } else if len(args) == 1 {
64 | core.Export(args[0])
65 | } else if len(args) == 2 {
66 | core.Import(args[0], args[1])
67 | } else {
68 | fmt.Println("Too much arguments")
69 | return
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/static/export.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gawwo/fake115-go/cc257d6638473a85c3787bfab1edfc2690f21da3/static/export.png
--------------------------------------------------------------------------------
/utils/cipher.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "crypto/rand"
5 | "crypto/rsa"
6 | "crypto/x509"
7 | "encoding/base64"
8 | "encoding/pem"
9 | "fmt"
10 | )
11 |
12 | var (
13 | gKeyL = []byte{0x42, 0xda, 0x13, 0xba, 0x78, 0x76, 0x8d, 0x37, 0xe8, 0xee, 0x04, 0x91}
14 | gKts = []byte{0xf0, 0xe5, 0x69, 0xae, 0xbf, 0xdc, 0xbf, 0x5a, 0x1a, 0x45, 0xe8, 0xbe, 0x7d, 0xa6, 0x73, 0x88, 0xde, 0x8f, 0xe7, 0xc4, 0x45, 0xda, 0x86, 0x94, 0x9b, 0x69, 0x92, 0x0b, 0x6a, 0xb8, 0xf1, 0x7a, 0x38, 0x06, 0x3c, 0x95, 0x26, 0x6d, 0x2c, 0x56, 0x00, 0x70, 0x56, 0x9c, 0x36, 0x38, 0x62, 0x76, 0x2f, 0x9b, 0x5f, 0x0f, 0xf2, 0xfe, 0xfd, 0x2d, 0x70, 0x9c, 0x86, 0x44, 0x8f, 0x3d, 0x14, 0x27, 0x71, 0x93, 0x8a, 0xe4, 0x0e, 0xc1, 0x48, 0xae, 0xdc, 0x34, 0x7f, 0xcf, 0xfe, 0xb2, 0x7f, 0xf6, 0x55, 0x9a, 0x46, 0xc8, 0xeb, 0x37, 0x77, 0xa4, 0xe0, 0x6b, 0x72, 0x93, 0x7e, 0x51, 0xcb, 0xf1, 0x37, 0xef, 0xad, 0x2a, 0xde, 0xee, 0xf9, 0xc9, 0x39, 0x6b, 0x32, 0xa1, 0xba, 0x35, 0xb1, 0xb8, 0xbe, 0xda, 0x78, 0x73, 0xf8, 0x20, 0xd5, 0x27, 0x04, 0x5a, 0x6f, 0xfd, 0x5e, 0x72, 0x39, 0xcf, 0x3b, 0x9c, 0x2b, 0x57, 0x5c, 0xf9, 0x7c, 0x4b, 0x7b, 0xd2, 0x12, 0x66, 0xcc, 0x77, 0x09, 0xa6}
15 | )
16 |
17 | const (
18 | keySize = 16
19 | blockSize = 128
20 | publicKey = `-----BEGIN PUBLIC KEY-----
21 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDR3rWmeYnRClwLBB0Rq0dlm8Mr
22 | PmWpL5I23SzCFAoNpJX6Dn74dfb6y02YH15eO6XmeBHdc7ekEFJUIi+swganTokR
23 | IVRRr/z16/3oh7ya22dcAqg191y+d6YDr4IGg/Q5587UKJMj35yQVXaeFXmLlFPo
24 | kFiz4uPxhrB7BGqZbQIDAQAB
25 | -----END PUBLIC KEY-----`
26 | privateKey = `-----BEGIN RSA PRIVATE KEY-----
27 | MIICXAIBAAKBgQCMgUJLwWb0kYdW6feyLvqgNHmwgeYYlocst8UckQ1+waTOKHFC
28 | TVyRSb1eCKJZWaGa08mB5lEu/asruNo/HjFcKUvRF6n7nYzo5jO0li4IfGKdxso6
29 | FJIUtAke8rA2PLOubH7nAjd/BV7TzZP2w0IlanZVS76n8gNDe75l8tonQQIDAQAB
30 | AoGANwTasA2Awl5GT/t4WhbZX2iNClgjgRdYwWMI1aHbVfqADZZ6m0rt55qng63/
31 | 3NsjVByAuNQ2kB8XKxzMoZCyJNvnd78YuW3Zowqs6HgDUHk6T5CmRad0fvaVYi6t
32 | viOkxtiPIuh4QrQ7NUhsLRtbH6d9s1KLCRDKhO23pGr9vtECQQDpjKYssF+kq9iy
33 | A9WvXRjbY9+ca27YfarD9WVzWS2rFg8MsCbvCo9ebXcmju44QhCghQFIVXuebQ7Q
34 | pydvqF0lAkEAmgLnib1XonYOxjVJM2jqy5zEGe6vzg8aSwKCYec14iiJKmEYcP4z
35 | DSRms43hnQsp8M2ynjnsYCjyiegg+AZ87QJANuwwmAnSNDOFfjeQpPDLy6wtBeft
36 | 5VOIORUYiovKRZWmbGFwhn6BQL+VaafrNaezqUweBRi1PYiAF2l3yLZbUQJAf/nN
37 | 4Hz/pzYmzLlWnGugP5WCtnHKkJWoKZBqO2RfOBCq+hY4sxvn3BHVbXqGcXLnZPvo
38 | YuaK7tTXxZSoYLEzeQJBAL8Mt3AkF1Gci5HOug6jT4s4Z+qDDrUXo9BlTwSWP90v
39 | wlHF+mkTJpKd5Wacef0vV+xumqNorvLpIXWKwxNaoHM=
40 | -----END RSA PRIVATE KEY-----`
41 | )
42 |
43 | // Cipher 加密解密信息
44 | type Cipher struct {
45 | publicKey *rsa.PublicKey
46 | privateKey *rsa.PrivateKey
47 | randKey []byte
48 | keyS []byte
49 | }
50 |
51 | // NewCipher 新建Cipher
52 | func NewCipher() (*Cipher, error) {
53 | c := new(Cipher)
54 | block, _ := pem.Decode([]byte(publicKey))
55 | key, err := x509.ParsePKIXPublicKey(block.Bytes)
56 | if err != nil {
57 | return nil, err
58 | }
59 | publicKey, ok := key.(*rsa.PublicKey)
60 | if !ok {
61 | return nil, fmt.Errorf("public key is not a RSA public key: %+v", key)
62 | }
63 | c.publicKey = publicKey
64 |
65 | block, _ = pem.Decode([]byte(privateKey))
66 | privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
67 | if err != nil {
68 | return nil, err
69 | }
70 | c.privateKey = privateKey
71 |
72 | return c, nil
73 | }
74 |
75 | // 生成key
76 | func (c *Cipher) genKey() error {
77 | c.randKey = make([]byte, keySize)
78 | _, err := rand.Read(c.randKey)
79 | if err != nil {
80 | return err
81 | }
82 | c.keyS = genKey(c.randKey, 4)
83 |
84 | return nil
85 | }
86 |
87 | // Encrypt 加密,一次加密对应一次解密
88 | func (c *Cipher) Encrypt(plainText []byte) ([]byte, error) {
89 | err := c.genKey()
90 | if err != nil {
91 | return nil, err
92 | }
93 | tmp := xor(plainText, c.keyS)
94 | for i, j := 0, len(tmp)-1; i < j; i, j = i+1, j-1 {
95 | tmp[i], tmp[j] = tmp[j], tmp[i]
96 | }
97 | xorText := make([]byte, 0, len(c.randKey)+len(tmp))
98 | xorText = append(xorText, c.randKey...)
99 | xorText = append(xorText, xor(tmp, gKeyL)...)
100 | cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, c.publicKey, xorText)
101 | if err != nil {
102 | return nil, err
103 | }
104 | text := make([]byte, base64.StdEncoding.EncodedLen(len(cipherText)))
105 | base64.StdEncoding.Encode(text, cipherText)
106 |
107 | return text, nil
108 | }
109 |
110 | // Decrypt 解密,一次加密对应一次解密
111 | func (c *Cipher) Decrypt(cipherText []byte) ([]byte, error) {
112 | text := make([]byte, base64.StdEncoding.DecodedLen(len(cipherText)))
113 | n, err := base64.StdEncoding.Decode(text, cipherText)
114 | if err != nil {
115 | return nil, err
116 | }
117 | text = text[:n]
118 | blockCount := len(text) / blockSize
119 | plainText := make([]byte, 0, blockCount*blockSize)
120 | for i := 0; i < blockCount; i++ {
121 | t, err := rsa.DecryptPKCS1v15(rand.Reader, c.privateKey, text[i*blockSize:(i+1)*blockSize])
122 | if err != nil {
123 | return nil, err
124 | }
125 | plainText = append(plainText, t...)
126 | }
127 | randKey := plainText[:keySize]
128 | plainText = plainText[keySize:]
129 | keyL := genKey(randKey, 12)
130 | tmp := xor(plainText, keyL)
131 | for i, j := 0, len(tmp)-1; i < j; i, j = i+1, j-1 {
132 | tmp[i], tmp[j] = tmp[j], tmp[i]
133 | }
134 | plainText = xor(tmp, c.keyS)
135 |
136 | return plainText, nil
137 | }
138 |
139 | // 生成key
140 | func genKey(randKey []byte, keyLen int) []byte {
141 | xorKey := make([]byte, 0, keyLen)
142 | length := keyLen * (keyLen - 1)
143 | index := 0
144 | if len(randKey) != 0 {
145 | for i := 0; i < keyLen; i++ {
146 | x := randKey[i] + gKts[index]
147 | xorKey = append(xorKey, gKts[length]^x)
148 | length -= keyLen
149 | index += keyLen
150 | }
151 | }
152 |
153 | return xorKey
154 | }
155 |
156 | // 利用key进行异或操作
157 | func xor(src, key []byte) []byte {
158 | secret := make([]byte, 0, len(src))
159 | pad := len(src) % 4
160 | if pad > 0 {
161 | for i := 0; i < pad; i++ {
162 | secret = append(secret, src[i]^key[i])
163 | }
164 | src = src[pad:]
165 | }
166 | keyLen := len(key)
167 | num := 0
168 | for _, s := range src {
169 | if num >= keyLen {
170 | num = num % keyLen
171 | }
172 | secret = append(secret, s^key[num])
173 | num++
174 | }
175 |
176 | return secret
177 | }
178 |
--------------------------------------------------------------------------------
/utils/cipher_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "bytes"
5 | "crypto/rsa"
6 | "crypto/x509"
7 | "encoding/pem"
8 | "fmt"
9 | "io/ioutil"
10 | "net/http"
11 | "net/url"
12 | "strings"
13 | "testing"
14 |
15 | "github.com/valyala/fastjson"
16 | )
17 |
18 | func TestParseKey(t *testing.T) {
19 | block, _ := pem.Decode([]byte(publicKey))
20 | key, err := x509.ParsePKIXPublicKey(block.Bytes)
21 | if err != nil {
22 | t.Errorf("public key parsing error: %v", err)
23 | }
24 | if _, ok := key.(*rsa.PublicKey); !ok {
25 | t.Errorf("public key is not a RSA public key: %+v", key)
26 | }
27 |
28 | block, _ = pem.Decode([]byte(privateKey))
29 | _, err = x509.ParsePKCS1PrivateKey(block.Bytes)
30 | if err != nil {
31 | t.Errorf("private key parsing error: %v", err)
32 | }
33 | }
34 |
35 | func TestGenKey(t *testing.T) {
36 | randKey := []byte{50, 106, 98, 84, 84, 110, 119, 89, 87, 75, 84, 71, 53, 110, 121, 120}
37 | key := genKey(randKey, 4)
38 | keyS := []byte{95, 51, 195, 33}
39 | if !bytes.Equal(key, keyS) {
40 | t.Errorf("keyS want: %v, result: %v", keyS, key)
41 | }
42 |
43 | randKey = []byte{122, 53, 116, 69, 109, 102, 111, 48, 98, 68, 114, 100, 107, 105, 55, 101}
44 | key = genKey(randKey, 12)
45 | keyL := []byte{54, 182, 181, 92, 119, 41, 196, 52, 191, 101, 11, 48}
46 | if !bytes.Equal(key, keyL) {
47 | t.Errorf("keyL want: %v, result: %v", keyL, key)
48 | }
49 | }
50 |
51 | func TestXor(t *testing.T) {
52 | src := []byte{123, 36, 112, 100, 77, 107, 44, 111, 55, 101, 75, 58, 46, 98, 80, 50, 20, 117, 33, 122, 134, 108, 125, 92, 48, 125, 56, 54, 120, 128, 34, 185}
53 | key := []byte{23, 24, 239, 61}
54 | secret := xor(src, key)
55 | want := []byte{108, 60, 159, 89, 90, 115, 195, 82, 32, 125, 164, 7, 57, 122, 191, 15, 3, 109, 206, 71, 145, 116, 146, 97, 39, 101, 215, 11, 111, 152, 205, 132}
56 | if !bytes.Equal(secret, want) {
57 | t.Errorf("xor text want: %v, result: %v", want, secret)
58 | }
59 |
60 | src = []byte{64, 215, 94, 111, 5, 115, 92, 35, 97, 189, 216, 155, 76, 133, 189, 138, 15, 144, 123, 59, 71, 205, 135, 115, 85, 170, 115, 146, 84, 169, 51, 118}
61 | key = []byte{66, 218, 19, 186, 120, 118, 141, 55, 232, 238, 4, 145}
62 | secret = xor(src, key)
63 | want = []byte{2, 13, 77, 213, 125, 5, 209, 20, 137, 83, 220, 10, 14, 95, 174, 48, 119, 230, 246, 12, 175, 35, 131, 226, 23, 112, 96, 40, 44, 223, 190, 65}
64 | if !bytes.Equal(secret, want) {
65 | t.Errorf("xor text want: %v, result: %v", want, secret)
66 | }
67 | }
68 |
69 | func TestEncryptDecrypt(t *testing.T) {
70 | t.Skip("Need pickcode and cookie")
71 | const downURL = "http://proapi.115.com/app/chrome/downurl"
72 | c, err := NewCipher()
73 | if err != nil {
74 | t.Errorf("create cipher error: %v", err)
75 | }
76 | text, err := c.Encrypt([]byte(fmt.Sprintf(`{"pickcode":"%s"}`, "needPickcode")))
77 | if err != nil {
78 | t.Errorf("encrypt error: %v", err)
79 | }
80 | t.Log(string(text))
81 | client := &http.Client{}
82 | form := url.Values{}
83 | form.Set("data", string(text))
84 | req, err := http.NewRequest(http.MethodPost, downURL, strings.NewReader(form.Encode()))
85 | if err != nil {
86 | t.Errorf("create request error: %v", err)
87 | }
88 | req.Header.Set("User-Agent", "Mozilla/5.0 115disk/26.2.2")
89 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
90 | req.Header.Set("Cookie", "yourCookie")
91 | resp, err := client.Do(req)
92 | if err != nil {
93 | t.Errorf("http post request error: %v", err)
94 | }
95 | defer resp.Body.Close()
96 | t.Logf("%+v", resp.Header)
97 | body, err := ioutil.ReadAll(resp.Body)
98 | if err != nil {
99 | t.Errorf("read response body error: %v", err)
100 | }
101 | t.Log(string(body))
102 | var p fastjson.Parser
103 | v, err := p.ParseBytes(body)
104 | if err != nil {
105 | t.Errorf("json parse error: %v", err)
106 | }
107 | if !v.GetBool("state") {
108 | t.Error("encrypt failed")
109 | }
110 | text, err = c.Decrypt(v.GetStringBytes("data"))
111 | if err != nil {
112 | t.Errorf("decrypt error: %v", err)
113 | }
114 | t.Log(string(text))
115 | }
116 |
--------------------------------------------------------------------------------
/utils/cookie.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "github.com/gawwo/fake115-go/config"
5 | "io/ioutil"
6 | "os"
7 | )
8 |
9 | // ReadCookieFile 一定要在main中的init函数之后运行
10 | func ReadCookieFile() (string, error) {
11 | cookieFile, err := os.Open(config.CookiePath)
12 | if err != nil {
13 | if os.IsNotExist(err) {
14 | config.Logger.Warn("Fail to open cookie dir: " + err.Error())
15 | }
16 | return "", err
17 | }
18 | defer cookieFile.Close()
19 |
20 | cookie, err := ioutil.ReadAll(cookieFile)
21 | if err != nil {
22 | return "", err
23 | }
24 | return string(cookie), nil
25 | }
26 |
--------------------------------------------------------------------------------
/utils/cookies.txt:
--------------------------------------------------------------------------------
1 | key=for_test
--------------------------------------------------------------------------------
/utils/pool.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "math"
5 | "sync"
6 | )
7 |
8 | type WaitGroupPool struct {
9 | pool chan struct{}
10 | wg *sync.WaitGroup
11 | }
12 |
13 | func NewWaitGroupPool(size int) *WaitGroupPool {
14 | if size <= 0 {
15 | size = math.MaxInt32
16 | }
17 | return &WaitGroupPool{
18 | pool: make(chan struct{}, size),
19 | wg: &sync.WaitGroup{},
20 | }
21 | }
22 |
23 | func (p *WaitGroupPool) Add() {
24 | p.pool <- struct{}{}
25 | p.wg.Add(1)
26 | }
27 |
28 | func (p *WaitGroupPool) Done() {
29 | <-p.pool
30 | p.wg.Done()
31 | }
32 |
33 | func (p *WaitGroupPool) Wait() {
34 | p.wg.Wait()
35 | }
36 |
37 | func (p *WaitGroupPool) Size() int {
38 | return len(p.pool)
39 | }
40 |
--------------------------------------------------------------------------------
/utils/request.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "compress/flate"
5 | "compress/gzip"
6 | "crypto/tls"
7 | "fmt"
8 | "io"
9 | "io/ioutil"
10 | "net"
11 | "net/http"
12 | "net/url"
13 | "strconv"
14 | "strings"
15 | "time"
16 | )
17 |
18 | const RetryTimes = 10
19 |
20 | // NewClient 不要反复创建Client,它应该全局唯一
21 | func NewClient() *http.Client {
22 | dialer := &net.Dialer{
23 | Timeout: 3 * time.Second,
24 | KeepAlive: 15 * time.Second,
25 | }
26 | transport := &http.Transport{
27 | Proxy: http.ProxyFromEnvironment,
28 | TLSHandshakeTimeout: 2 * time.Second,
29 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
30 | DialContext: dialer.DialContext,
31 | MaxIdleConns: 200,
32 | MaxIdleConnsPerHost: 50,
33 | }
34 | client := &http.Client{
35 | Transport: transport,
36 | Timeout: 10 * time.Minute,
37 | }
38 | return client
39 | }
40 |
41 | var client = NewClient()
42 |
43 | func Request(method, url string, body io.Reader, headers map[string]string) (*http.Response, error) {
44 | req, err := http.NewRequest(method, url, body)
45 | if err != nil {
46 | return nil, err
47 | }
48 | if headers == nil {
49 | headers = map[string]string{}
50 | }
51 | // 设置可能缺少的默认参数
52 | if _, ok := headers["Connection"]; !ok {
53 | headers["Connection"] = "keep-alive"
54 | }
55 | if _, ok := headers["Accept"]; !ok {
56 | headers["Accept"] = "*/*"
57 | }
58 | if _, ok := headers["Accept-Encoding"]; !ok {
59 | headers["Accept-Encoding"] = "gzip, deflate"
60 | }
61 |
62 | for k, v := range headers {
63 | req.Header.Set(k, v)
64 | }
65 |
66 | var (
67 | res *http.Response
68 | requestError error
69 | )
70 |
71 | for i := 1; ; i++ {
72 | res, requestError = client.Do(req)
73 | if requestError == nil && res.StatusCode < 500 {
74 | break
75 | }
76 |
77 | var errMsg string
78 |
79 | // 重试到最大次数
80 | if i >= RetryTimes {
81 | return nil, fmt.Errorf(errMsg)
82 | }
83 |
84 | time.Sleep(time.Second)
85 | }
86 | return res, nil
87 | }
88 |
89 | func get(urlGet string, headers map[string]string, data map[string]string, withResponse bool) (
90 | []byte, *http.Response, error) {
91 | // 尝试拼接get的参数
92 | if data != nil {
93 | getData := url.Values{}
94 | for k, v := range data {
95 | getData.Set(k, v)
96 | }
97 | urlGet = urlGet + "?" + getData.Encode()
98 | }
99 |
100 | if headers == nil {
101 | headers = map[string]string{}
102 | }
103 |
104 | res, err := Request(http.MethodGet, urlGet, nil, headers)
105 | if err != nil {
106 | return nil, nil, err
107 | }
108 | return readBody(res, withResponse)
109 | }
110 |
111 | func Get(urlGet string, headers map[string]string, data map[string]string) ([]byte, error) {
112 | body, _, err := get(urlGet, headers, data, false)
113 | return body, err
114 | }
115 |
116 | func PostForm(urlPost string, headers map[string]string, data map[string]string) ([]byte, error) {
117 | body, _, err := postByte(urlPost, headers, data, false)
118 | return body, err
119 | }
120 |
121 | func readBody(res *http.Response, withResponse bool) ([]byte, *http.Response, error) {
122 | var reader io.ReadCloser
123 | defer res.Body.Close()
124 |
125 | switch res.Header.Get("Content-Encoding") {
126 | case "gzip":
127 | reader, _ = gzip.NewReader(res.Body)
128 | case "deflate":
129 | reader = flate.NewReader(res.Body)
130 | default:
131 | reader = res.Body
132 | }
133 | defer reader.Close()
134 |
135 | body, err := ioutil.ReadAll(reader)
136 | if err != nil && err != io.EOF {
137 | return nil, nil, err
138 | }
139 |
140 | if withResponse {
141 | return body, res, nil
142 | } else {
143 | return body, nil, nil
144 | }
145 |
146 | }
147 |
148 | func postByte(postUrl string, headers map[string]string, data map[string]string, withResponse bool) ([]byte, *http.Response, error) {
149 | postData := url.Values{}
150 | for k, v := range data {
151 | postData.Set(k, v)
152 | }
153 |
154 | // 不设置content-type,对方就可能认为没发送form body
155 | if _, ok := headers["Content-Type"]; !ok {
156 | headers["Content-Type"] = "application/x-www-form-urlencoded"
157 | }
158 |
159 | if headers == nil {
160 | headers = map[string]string{}
161 | }
162 |
163 | dataString := postData.Encode()
164 | if _, ok := headers["Content-Length"]; !ok {
165 | headers["Content-Length"] = strconv.Itoa(len(dataString))
166 | }
167 |
168 | res, err := Request(http.MethodPost, postUrl, strings.NewReader(dataString), headers)
169 | if err != nil {
170 | return nil, nil, err
171 | }
172 |
173 | return readBody(res, withResponse)
174 | }
175 |
--------------------------------------------------------------------------------
/utils/request_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "github.com/gawwo/fake115-go/config"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestGet(t *testing.T) {
11 | config.CookiePath = config.DefaultCookiePath
12 | config.Cookie, _ = ReadCookieFile()
13 |
14 | header := config.GetFakeHeaders(true)
15 | url := "https://webapi.115.com/files"
16 | data := map[string]string{
17 | "aid": "1",
18 | "cid": "353522044329243945",
19 | "o": "file_name",
20 | "asc": "0",
21 | "offset": "0",
22 | "show_dir": "1",
23 | "limit": "115",
24 | "code": "",
25 | "scid": "",
26 | "snap": "0",
27 | "natsrot": "1",
28 | "record_open_time": "1",
29 | "source": "",
30 | "format": "json",
31 | "type": "",
32 | "star": "",
33 | "is_share": "",
34 | "suffix": "",
35 | "is_q": "",
36 | "fc_mix": "1",
37 | }
38 |
39 | body, err := Get(url, header, data)
40 | if err != nil {
41 | t.Errorf("请求错误: %s\n", err)
42 | } else {
43 | fmt.Println(body)
44 | }
45 | }
46 |
47 | var wgp = NewWaitGroupPool(50)
48 |
49 | func doMultiRequest(url string) bool {
50 | defer wgp.Done()
51 |
52 | start := time.Now().Unix()
53 | _, err := Get(url, nil, nil)
54 | if err != nil {
55 | fmt.Println(err)
56 | return false
57 | }
58 | fmt.Println("elapsed: ", time.Now().Unix()-start)
59 | return true
60 | }
61 |
62 | func TestTimeout(t *testing.T) {
63 | url := "http://localhost:8441/"
64 | for i := 0; i < 5000000; i++ {
65 | wgp.Add()
66 | go doMultiRequest(url)
67 | }
68 |
69 | wgp.Wait()
70 | }
71 |
72 | func TestPostForm(t *testing.T) {
73 | config.CookiePath = config.DefaultCookiePath
74 | config.Cookie, _ = ReadCookieFile()
75 |
76 | header := config.GetFakeHeaders(true)
77 | url := "https://webapi.115.com/files/add"
78 | data := map[string]string{
79 | "pid": "353522044329243945",
80 | "cname": "1",
81 | }
82 |
83 | body, err := PostForm(url, header, data)
84 | if err != nil {
85 | t.Errorf("请求错误: %s\n", err)
86 | } else {
87 | fmt.Println(body)
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/utils/sha1.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "bufio"
5 | "crypto/sha1"
6 | "encoding/hex"
7 | "io"
8 | "os"
9 | )
10 |
11 | func FileSha1(filePath string) (hexDigest string, err error) {
12 | f, err := os.Open(filePath)
13 | if err != nil {
14 | return hexDigest, err
15 | }
16 | defer f.Close()
17 |
18 | reader := bufio.NewReader(f)
19 | buf := make([]byte, 64<<10)
20 | sha1Hash := sha1.New()
21 |
22 | for {
23 | n, err := reader.Read(buf)
24 | if err != nil {
25 | if err == io.EOF {
26 | goto readFinish
27 | }
28 | return hexDigest, err
29 | }
30 |
31 | sha1Hash.Write(buf[:n])
32 | }
33 |
34 | readFinish:
35 | hexDigest = hex.EncodeToString(sha1Hash.Sum(nil))
36 | return hexDigest, nil
37 | }
38 |
39 | func Sha1(content []byte) string {
40 | sha1Hash := sha1.New()
41 | sha1Hash.Write(content)
42 | return hex.EncodeToString(sha1Hash.Sum(nil))
43 | }
44 |
--------------------------------------------------------------------------------
/utils/sha1_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "testing"
7 | )
8 |
9 | func TestFileSha1(t *testing.T) {
10 | filePath := "/Users/cheny/Downloads/00416668afe59a1b2f55c0f02a7c277f.mp3"
11 | expectDigest := "e67bcdfc73ef493197fdd86957e1347fef4eb5de"
12 |
13 | digest, err := FileSha1(filePath)
14 | if err != nil {
15 | t.Errorf("摘要生成出错%s", err)
16 | } else if digest != expectDigest {
17 | t.Errorf("摘要不匹配, 期望: %s 实际: %s", expectDigest, digest)
18 | }
19 | }
20 |
21 | func TestSha1(t *testing.T) {
22 | filePath := "/Users/cheny/Downloads/00416668afe59a1b2f55c0f02a7c277f.mp3"
23 | expectDigest := "e67bcdfc73ef493197fdd86957e1347fef4eb5de"
24 | f, err := os.Open(filePath)
25 | if err != nil {
26 | t.Errorf("找不到文件")
27 | }
28 |
29 | buf, err := ioutil.ReadAll(f)
30 | if err != nil {
31 | t.Errorf("读取文件出错")
32 | }
33 | digest := Sha1(buf)
34 | if digest != expectDigest {
35 | t.Errorf("sha1计算不正确")
36 | }
37 | }
38 |
39 | func BenchmarkFileSha1(b *testing.B) {
40 | filePath := "/Users/cheny/Downloads/00416668afe59a1b2f55c0f02a7c277f.mp3"
41 |
42 | for i := 0; i < b.N; i++ {
43 | _, _ = FileSha1(filePath)
44 | }
45 | }
46 |
47 | func BenchmarkSha1(b *testing.B) {
48 | filePath := "/Users/cheny/Downloads/00416668afe59a1b2f55c0f02a7c277f.mp3"
49 | f, err := os.Open(filePath)
50 | if err != nil {
51 | b.Errorf("找不到文件")
52 | }
53 |
54 | buf, err := ioutil.ReadAll(f)
55 | if err != nil {
56 | b.Errorf("读取文件出错")
57 | }
58 |
59 | for i := 0; i < b.N; i++ {
60 | Sha1(buf)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/utils/util.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "path/filepath"
5 | )
6 |
7 | func FileNameStrip(fullName string) string {
8 | fileNameBase := filepath.Base(fullName)
9 | fileSuffix := filepath.Ext(fullName)
10 | filePrefix := fileNameBase[0 : len(fileNameBase)-len(fileSuffix)]
11 | return filePrefix
12 | }
13 |
--------------------------------------------------------------------------------
/with_cookie_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gawwo/fake115-go/config"
6 | "github.com/gawwo/fake115-go/core"
7 | "github.com/gawwo/fake115-go/dir"
8 | "os"
9 | "testing"
10 | )
11 |
12 | // 测试前确定cookie是否已登录
13 | func init() {
14 | // 确保cookie在登录状态
15 | loggedIn := core.SetUserInfoConfig()
16 | if !loggedIn {
17 | fmt.Println("Login expire or fail...")
18 | os.Exit(1)
19 | }
20 | }
21 |
22 | // 扫描当前层的文件夹,不涉及下一层文件夹
23 | func TestScanDirWithOffset(t *testing.T) {
24 | netDir, err := core.ScanDirWithOffset("1932902800904947822", 0)
25 | if err != nil {
26 | t.Errorf(err.Error())
27 | }
28 | fmt.Println(netDir)
29 | }
30 |
31 | // 扫描整个文件夹
32 | func TestExport(t *testing.T) {
33 | config.Debug = true
34 | config.WorkerNum = 5
35 | exporter := core.NewExporter()
36 | dirMeta := exporter.ScanDir("1898007427015248622")
37 | _, err := dirMeta.Dump("ump_result.txt")
38 | if err != nil {
39 | t.Error(err.Error())
40 | }
41 | fmt.Printf("total size: %dGB, files: %d\n", exporter.FileTotalSize>>30, exporter.FileCount)
42 | }
43 |
44 | // 手动查看任务执行情况
45 | func TestImport(t *testing.T) {
46 | config.Debug = true
47 | core.Import("1951041685426014426", "ump_result.txt")
48 | }
49 |
50 | func TestNetFileExport(t *testing.T) {
51 | netFile := core.NetFile{
52 | Fid: "1932902801198549107",
53 | Cid: "1932902800904947822",
54 | Size: 3153756278,
55 | Name: "raised.by.wolves.2020.s01e07.1080p.web.h264-videohole.mkv",
56 | Sha: "44451C2DDCE125722FBA9DE1760E55E265023A73",
57 | Pc: "b9zzwuk9729f283dt",
58 | }
59 | result := netFile.Export()
60 | if result == "" {
61 | t.Error("export file fail")
62 | }
63 | }
64 |
65 | func TestMakeNetDir(t *testing.T) {
66 | testDir := dir.Dir{DirName: "for test"}
67 | testDir.MakeNetDir("0")
68 | }
69 |
70 | func TestImportFile(t *testing.T) {
71 | testFileString := "352Dora.wmv|1447618913|06CCC77F31F4269B5FEB32E7762D0FD" +
72 | "7C62B1DB9|F29386C9F238CD578BCCAD824FE549F851551473"
73 | netFile := core.CreateNetFile(testFileString)
74 | netFile.Cid = "0"
75 | netFile.Import()
76 | }
77 |
--------------------------------------------------------------------------------