├── .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 |

example

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 | --------------------------------------------------------------------------------