mirror of
https://github.com/putyy/res-downloader.git
synced 2026-01-12 14:14:55 +08:00
368 lines
9.8 KiB
Go
368 lines
9.8 KiB
Go
package core
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/elazarl/goproxy"
|
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Proxy struct {
|
|
ctx context.Context
|
|
Proxy *goproxy.ProxyHttpServer
|
|
Is bool
|
|
}
|
|
|
|
type MediaInfo struct {
|
|
Id string
|
|
Url string
|
|
UrlSign string
|
|
CoverUrl string
|
|
Size string
|
|
Domain string
|
|
Classify string
|
|
Suffix string
|
|
SavePath string
|
|
Status string
|
|
DecodeKey string
|
|
Description string
|
|
ContentType string
|
|
OtherData map[string]string
|
|
}
|
|
|
|
func initProxy() *Proxy {
|
|
if proxyOnce == nil {
|
|
proxyOnce = &Proxy{}
|
|
proxyOnce.Startup()
|
|
}
|
|
return proxyOnce
|
|
}
|
|
|
|
func (p *Proxy) Startup() {
|
|
err := p.setCa()
|
|
if err != nil {
|
|
DialogErr("启动代理服务失败:" + err.Error())
|
|
return
|
|
}
|
|
|
|
p.Proxy = goproxy.NewProxyHttpServer()
|
|
//p.Proxy.KeepDestinationHeaders = true
|
|
//p.Proxy.Verbose = false
|
|
p.setTransport()
|
|
p.Proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
|
|
p.Proxy.OnRequest().DoFunc(p.httpRequestEvent)
|
|
p.Proxy.OnResponse().DoFunc(p.httpResponseEvent)
|
|
}
|
|
|
|
func (p *Proxy) setCa() error {
|
|
ca, err := tls.X509KeyPair(appOnce.PublicCrt, appOnce.PrivateKey)
|
|
if err != nil {
|
|
DialogErr("启动代理服务失败1")
|
|
return err
|
|
}
|
|
if ca.Leaf, err = x509.ParseCertificate(ca.Certificate[0]); err != nil {
|
|
return err
|
|
}
|
|
goproxy.GoproxyCa = ca
|
|
goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: goproxy.TLSConfigFromCA(&ca)}
|
|
goproxy.MitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&ca)}
|
|
goproxy.HTTPMitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectHTTPMitm, TLSConfig: goproxy.TLSConfigFromCA(&ca)}
|
|
goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: goproxy.TLSConfigFromCA(&ca)}
|
|
return nil
|
|
}
|
|
|
|
func (p *Proxy) setTransport() {
|
|
transport := &http.Transport{
|
|
DisableKeepAlives: false,
|
|
// MaxIdleConnsPerHost: 10,
|
|
DialContext: (&net.Dialer{
|
|
Timeout: 60 * time.Second,
|
|
}).DialContext,
|
|
TLSHandshakeTimeout: 60 * time.Second,
|
|
ResponseHeaderTimeout: 60 * time.Second,
|
|
IdleConnTimeout: 30 * time.Second,
|
|
}
|
|
|
|
if globalConfig.UpstreamProxy != "" && globalConfig.OpenProxy && !strings.Contains(globalConfig.UpstreamProxy, globalConfig.Port) {
|
|
proxyURL, err := url.Parse(globalConfig.UpstreamProxy)
|
|
if err == nil {
|
|
transport.Proxy = http.ProxyURL(proxyURL)
|
|
}
|
|
}
|
|
p.Proxy.Tr = transport
|
|
}
|
|
|
|
func (p *Proxy) httpRequestEvent(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
|
|
if strings.Contains(r.Host, "res-downloader.666666.com") && strings.Contains(r.URL.Path, "/wechat") {
|
|
if globalConfig.WxAction && r.URL.Query().Get("type") == "1" {
|
|
return p.handleWechatRequest(r, ctx)
|
|
} else if !globalConfig.WxAction && r.URL.Query().Get("type") == "2" {
|
|
return p.handleWechatRequest(r, ctx)
|
|
} else {
|
|
return r, p.buildEmptyResponse(r)
|
|
}
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func (p *Proxy) handleWechatRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return r, p.buildEmptyResponse(r)
|
|
}
|
|
|
|
isAll, _ := resourceOnce.getResType("all")
|
|
isClassify, _ := resourceOnce.getResType("video")
|
|
|
|
if !isAll && !isClassify {
|
|
return r, p.buildEmptyResponse(r)
|
|
}
|
|
go func(body []byte) {
|
|
var result map[string]interface{}
|
|
err = json.Unmarshal(body, &result)
|
|
if err != nil {
|
|
return
|
|
}
|
|
media, ok := result["media"].([]interface{})
|
|
if !ok || len(media) <= 0 {
|
|
return
|
|
}
|
|
firstMedia, ok := media[0].(map[string]interface{})
|
|
if !ok {
|
|
return
|
|
}
|
|
rowUrl, ok := firstMedia["url"]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
urlSign := Md5(rowUrl.(string))
|
|
if resourceOnce.mediaIsMarked(urlSign) {
|
|
return
|
|
}
|
|
|
|
id, err := gonanoid.New()
|
|
if err != nil {
|
|
id = urlSign
|
|
}
|
|
res := MediaInfo{
|
|
Id: id,
|
|
Url: rowUrl.(string),
|
|
UrlSign: urlSign,
|
|
CoverUrl: "",
|
|
Size: "0",
|
|
Domain: GetTopLevelDomain(rowUrl.(string)),
|
|
Classify: "video",
|
|
Suffix: ".mp4",
|
|
Status: DownloadStatusReady,
|
|
SavePath: "",
|
|
DecodeKey: "",
|
|
OtherData: map[string]string{},
|
|
Description: "",
|
|
ContentType: "video/mp4",
|
|
}
|
|
|
|
if mediaType, ok := firstMedia["mediaType"].(float64); ok && mediaType == 9 {
|
|
res.Classify = "image"
|
|
res.Suffix = ".png"
|
|
res.ContentType = "image/png"
|
|
}
|
|
|
|
if urlToken, ok := firstMedia["urlToken"].(string); ok {
|
|
res.Url = res.Url + urlToken
|
|
}
|
|
if fileSize, ok := firstMedia["fileSize"].(float64); ok {
|
|
res.Size = FormatSize(fileSize)
|
|
}
|
|
if coverUrl, ok := firstMedia["coverUrl"].(string); ok {
|
|
res.CoverUrl = coverUrl
|
|
}
|
|
if fileSize, ok := firstMedia["fileSize"].(string); ok {
|
|
value, err := strconv.ParseFloat(fileSize, 64)
|
|
if err == nil {
|
|
res.Size = FormatSize(value)
|
|
}
|
|
}
|
|
if decodeKey, ok := firstMedia["decodeKey"].(string); ok {
|
|
res.DecodeKey = decodeKey
|
|
}
|
|
if desc, ok := result["description"].(string); ok {
|
|
res.Description = desc
|
|
}
|
|
if spec, ok := firstMedia["spec"].([]interface{}); ok {
|
|
var fileFormats []string
|
|
for _, item := range spec {
|
|
if itemMap, ok := item.(map[string]interface{}); ok {
|
|
if format, exists := itemMap["fileFormat"].(string); exists {
|
|
fileFormats = append(fileFormats, format)
|
|
}
|
|
}
|
|
}
|
|
|
|
res.OtherData["wx_file_formats"] = strings.Join(fileFormats, "#")
|
|
}
|
|
resourceOnce.markMedia(urlSign)
|
|
httpServerOnce.send("newResources", res)
|
|
}(body)
|
|
return r, p.buildEmptyResponse(r)
|
|
}
|
|
|
|
func (p *Proxy) buildEmptyResponse(r *http.Request) *http.Response {
|
|
body := "内容不存在"
|
|
resp := &http.Response{
|
|
Status: "200 OK",
|
|
StatusCode: http.StatusOK,
|
|
Header: make(http.Header),
|
|
Body: io.NopCloser(strings.NewReader(body)),
|
|
ContentLength: int64(len(body)),
|
|
Request: r,
|
|
}
|
|
resp.Header.Set("Content-Type", "text/plain")
|
|
return resp
|
|
}
|
|
|
|
func (p *Proxy) httpResponseEvent(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
|
|
if resp == nil || resp.Request == nil || (resp.StatusCode != 200 && resp.StatusCode != 206) {
|
|
return resp
|
|
}
|
|
|
|
host := resp.Request.Host
|
|
Path := resp.Request.URL.Path
|
|
|
|
if strings.HasSuffix(host, "channels.weixin.qq.com") &&
|
|
(strings.Contains(Path, "/web/pages/feed") || strings.Contains(Path, "/web/pages/home")) {
|
|
return p.replaceWxJsContent(resp, ".js\"", ".js?v="+p.v()+"\"")
|
|
}
|
|
|
|
if strings.HasSuffix(host, "res.wx.qq.com") {
|
|
respTemp := resp
|
|
is := false
|
|
if strings.HasSuffix(respTemp.Request.URL.RequestURI(), ".js?v="+p.v()) {
|
|
respTemp = p.replaceWxJsContent(respTemp, ".js\"", ".js?v="+p.v()+"\"")
|
|
is = true
|
|
}
|
|
|
|
if strings.Contains(Path, "web/web-finder/res/js/virtual_svg-icons-register.publish") {
|
|
body, err := io.ReadAll(respTemp.Body)
|
|
if err != nil {
|
|
return respTemp
|
|
}
|
|
bodyStr := string(body)
|
|
newBody := regexp.MustCompile(`get\s*media\(\)\{`).
|
|
ReplaceAllString(bodyStr, `
|
|
get media(){
|
|
if(this.objectDesc){
|
|
fetch("https://res-downloader.666666.com/wechat?type=1", {
|
|
method: "POST",
|
|
mode: "no-cors",
|
|
body: JSON.stringify(this.objectDesc),
|
|
});
|
|
};
|
|
|
|
`)
|
|
|
|
newBody = regexp.MustCompile(`async\s*finderGetCommentDetail\((\w+)\)\s*\{return(.*?)\s*}\s*async`).
|
|
ReplaceAllString(newBody, `
|
|
async finderGetCommentDetail($1) {
|
|
var res = await$2;
|
|
if (res?.data?.object?.objectDesc) {
|
|
fetch("https://res-downloader.666666.com/wechat?type=2", {
|
|
method: "POST",
|
|
mode: "no-cors",
|
|
body: JSON.stringify(res.data.object.objectDesc),
|
|
});
|
|
}
|
|
return res;
|
|
}async
|
|
`)
|
|
newBodyBytes := []byte(newBody)
|
|
respTemp.Body = io.NopCloser(bytes.NewBuffer(newBodyBytes))
|
|
respTemp.ContentLength = int64(len(newBodyBytes))
|
|
respTemp.Header.Set("Content-Length", fmt.Sprintf("%d", len(newBodyBytes)))
|
|
return respTemp
|
|
}
|
|
if is {
|
|
return respTemp
|
|
}
|
|
}
|
|
|
|
classify, suffix := TypeSuffix(resp.Header.Get("Content-Type"))
|
|
if classify == "" {
|
|
return resp
|
|
}
|
|
|
|
if classify == "video" && strings.HasSuffix(host, "finder.video.qq.com") {
|
|
//if !globalConfig.WxAction && classify == "video" && strings.HasSuffix(host, "finder.video.qq.com") {
|
|
return resp
|
|
}
|
|
|
|
rawUrl := resp.Request.URL.String()
|
|
isAll, _ := resourceOnce.getResType("all")
|
|
isClassify, _ := resourceOnce.getResType(classify)
|
|
|
|
urlSign := Md5(rawUrl)
|
|
if ok := resourceOnce.mediaIsMarked(urlSign); !ok && (isAll || isClassify) {
|
|
value, _ := strconv.ParseFloat(resp.Header.Get("content-length"), 64)
|
|
id, err := gonanoid.New()
|
|
if err != nil {
|
|
id = urlSign
|
|
}
|
|
res := MediaInfo{
|
|
Id: id,
|
|
Url: rawUrl,
|
|
UrlSign: urlSign,
|
|
CoverUrl: "",
|
|
Size: FormatSize(value),
|
|
Domain: GetTopLevelDomain(rawUrl),
|
|
Classify: classify,
|
|
Suffix: suffix,
|
|
Status: DownloadStatusReady,
|
|
SavePath: "",
|
|
DecodeKey: "",
|
|
OtherData: map[string]string{},
|
|
Description: "",
|
|
ContentType: resp.Header.Get("Content-Type"),
|
|
}
|
|
|
|
// Store entire request headers as JSON
|
|
if headers, err := json.Marshal(resp.Request.Header); err == nil {
|
|
res.OtherData["headers"] = string(headers)
|
|
}
|
|
|
|
resourceOnce.markMedia(urlSign)
|
|
httpServerOnce.send("newResources", res)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func (p *Proxy) replaceWxJsContent(resp *http.Response, old, new string) *http.Response {
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return resp
|
|
}
|
|
bodyString := string(body)
|
|
newBodyString := strings.ReplaceAll(bodyString, old, new)
|
|
newBodyBytes := []byte(newBodyString)
|
|
resp.Body = io.NopCloser(bytes.NewBuffer(newBodyBytes))
|
|
resp.ContentLength = int64(len(newBodyBytes))
|
|
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(newBodyBytes)))
|
|
return resp
|
|
}
|
|
|
|
func (p *Proxy) v() string {
|
|
return appOnce.Version
|
|
}
|