Files
res-downloader/core/resource.go
2025-09-11 17:04:07 +08:00

269 lines
6.2 KiB
Go

package core
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"regexp"
"res-downloader/core/shared"
"strconv"
"strings"
"sync"
)
type WxFileDecodeResult struct {
SavePath string
Message string
}
type Resource struct {
mediaMark sync.Map
resType map[string]bool
resTypeMux sync.RWMutex
}
func initResource() *Resource {
if resourceOnce == nil {
resourceOnce = &Resource{}
resourceOnce.resType = resourceOnce.buildResType(globalConfig.MimeMap)
}
return resourceOnce
}
func (r *Resource) buildResType(mime map[string]MimeInfo) map[string]bool {
t := map[string]bool{
"all": true,
}
for _, item := range mime {
if _, ok := t[item.Type]; !ok {
t[item.Type] = true
}
}
return t
}
func (r *Resource) mediaIsMarked(key string) bool {
_, loaded := r.mediaMark.Load(key)
return loaded
}
func (r *Resource) markMedia(key string) {
r.mediaMark.Store(key, true)
}
func (r *Resource) getResType(key string) (bool, bool) {
r.resTypeMux.RLock()
value, ok := r.resType[key]
r.resTypeMux.RUnlock()
return value, ok
}
func (r *Resource) setResType(n []string) {
r.resTypeMux.Lock()
for key := range r.resType {
r.resType[key] = false
}
for _, value := range n {
if _, ok := r.resType[value]; ok {
r.resType[value] = true
}
}
r.resTypeMux.Unlock()
}
func (r *Resource) clear() {
r.mediaMark.Clear()
}
func (r *Resource) delete(sign string) {
r.mediaMark.Delete(sign)
}
func (r *Resource) download(mediaInfo MediaInfo, decodeStr string) {
if globalConfig.SaveDirectory == "" {
return
}
go func(mediaInfo MediaInfo) {
rawUrl := mediaInfo.Url
fileName := shared.Md5(rawUrl)
if v := shared.GetFileNameFromURL(rawUrl); v != "" {
fileName = v
}
if mediaInfo.Description != "" {
fileName = regexp.MustCompile(`[^\w\p{Han}]`).ReplaceAllString(mediaInfo.Description, "")
fileLen := globalConfig.FilenameLen
if fileLen <= 0 {
fileLen = 10
}
runes := []rune(fileName)
if len(runes) > fileLen {
fileName = string(runes[:fileLen])
}
}
if globalConfig.FilenameTime {
mediaInfo.SavePath = filepath.Join(globalConfig.SaveDirectory, fileName+"_"+shared.GetCurrentDateTimeFormatted())
} else {
mediaInfo.SavePath = filepath.Join(globalConfig.SaveDirectory, fileName)
}
if !strings.HasSuffix(mediaInfo.SavePath, mediaInfo.Suffix) {
mediaInfo.SavePath = mediaInfo.SavePath + mediaInfo.Suffix
}
if strings.Contains(rawUrl, "qq.com") {
if globalConfig.Quality == 1 &&
strings.Contains(rawUrl, "encfilekey=") &&
strings.Contains(rawUrl, "token=") {
parseUrl, err := url.Parse(rawUrl)
queryParams := parseUrl.Query()
if err == nil && queryParams.Has("encfilekey") && queryParams.Has("token") {
rawUrl = parseUrl.Scheme + "://" + parseUrl.Host + "/" + parseUrl.Path +
"?encfilekey=" + queryParams.Get("encfilekey") +
"&token=" + queryParams.Get("token")
}
} else if globalConfig.Quality > 1 && mediaInfo.OtherData["wx_file_formats"] != "" {
format := strings.Split(mediaInfo.OtherData["wx_file_formats"], "#")
qualityMap := []string{
format[0],
format[len(format)/2],
format[len(format)-1],
}
rawUrl += "&X-snsvideoflag=" + qualityMap[globalConfig.Quality-2]
}
}
headers, _ := r.parseHeaders(mediaInfo)
downloader := NewFileDownloader(rawUrl, mediaInfo.SavePath, globalConfig.TaskNumber, headers)
downloader.progressCallback = func(totalDownloaded, totalSize float64, taskID int, taskProgress float64) {
r.progressEventsEmit(mediaInfo, strconv.Itoa(int(totalDownloaded*100/totalSize))+"%", shared.DownloadStatusRunning)
}
err := downloader.Start()
if err != nil {
r.progressEventsEmit(mediaInfo, err.Error())
return
}
if decodeStr != "" {
r.progressEventsEmit(mediaInfo, "decrypting in progress", shared.DownloadStatusRunning)
if err := r.decodeWxFile(mediaInfo.SavePath, decodeStr); err != nil {
r.progressEventsEmit(mediaInfo, "decryption error: "+err.Error())
return
}
}
r.progressEventsEmit(mediaInfo, "complete", shared.DownloadStatusDone)
}(mediaInfo)
}
func (r *Resource) parseHeaders(mediaInfo MediaInfo) (map[string]string, error) {
headers := make(map[string]string)
if hh, ok := mediaInfo.OtherData["headers"]; ok {
var tempHeaders map[string][]string
if err := json.Unmarshal([]byte(hh), &tempHeaders); err != nil {
return headers, fmt.Errorf("parse headers JSON err: %v", err)
}
for key, values := range tempHeaders {
if len(values) > 0 {
headers[key] = values[0]
}
}
}
return headers, nil
}
func (r *Resource) wxFileDecode(mediaInfo MediaInfo, fileName, decodeStr string) (string, error) {
sourceFile, err := os.Open(fileName)
if err != nil {
return "", err
}
defer sourceFile.Close()
mediaInfo.SavePath = strings.ReplaceAll(fileName, ".mp4", "_decrypt.mp4")
destinationFile, err := os.Create(mediaInfo.SavePath)
if err != nil {
return "", err
}
defer destinationFile.Close()
_, err = io.Copy(destinationFile, sourceFile)
if err != nil {
return "", err
}
err = r.decodeWxFile(mediaInfo.SavePath, decodeStr)
if err != nil {
return "", err
}
return mediaInfo.SavePath, nil
}
func (r *Resource) progressEventsEmit(mediaInfo MediaInfo, args ...string) {
Status := shared.DownloadStatusError
Message := "ok"
if len(args) > 0 {
Message = args[0]
}
if len(args) > 1 {
Status = args[1]
}
httpServerOnce.send("downloadProgress", map[string]interface{}{
"Id": mediaInfo.Id,
"Status": Status,
"SavePath": mediaInfo.SavePath,
"Message": Message,
})
return
}
func (r *Resource) decodeWxFile(fileName, decodeStr string) error {
decodedBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return err
}
file, err := os.OpenFile(fileName, os.O_RDWR, 0644)
if err != nil {
return err
}
defer file.Close()
byteCount := len(decodedBytes)
fileBytes := make([]byte, byteCount)
n, err := file.Read(fileBytes)
if err != nil && err != io.EOF {
return err
}
if n < byteCount {
byteCount = n
}
xorResult := make([]byte, byteCount)
for i := 0; i < byteCount; i++ {
xorResult[i] = decodedBytes[i] ^ fileBytes[i]
}
_, err = file.Seek(0, 0)
if err != nil {
return err
}
_, err = file.Write(xorResult)
if err != nil {
return err
}
return nil
}