mirror of
https://github.com/fish2018/pansou.git
synced 2025-11-25 19:37:43 +08:00
优化gying精确搜索
This commit is contained in:
@@ -27,15 +27,15 @@ import (
|
|||||||
"pansou/model"
|
"pansou/model"
|
||||||
"pansou/plugin"
|
"pansou/plugin"
|
||||||
"pansou/util/json"
|
"pansou/util/json"
|
||||||
|
|
||||||
cloudscraper "github.com/Advik-B/cloudscraper/lib"
|
cloudscraper "github.com/Advik-B/cloudscraper/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 插件配置参数
|
// 插件配置参数
|
||||||
const (
|
const (
|
||||||
MaxConcurrentUsers = 10 // 最多使用的用户数
|
MaxConcurrentUsers = 10 // 最多使用的用户数
|
||||||
MaxConcurrentDetails = 50 // 最大并发详情请求数
|
MaxConcurrentDetails = 50 // 最大并发详情请求数
|
||||||
DebugLog = false // 调试日志开关(排查问题时改为true)
|
DebugLog = false // 调试日志开关(排查问题时改为true)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 默认账户配置(可通过Web界面添加更多账户)
|
// 默认账户配置(可通过Web界面添加更多账户)
|
||||||
@@ -57,9 +57,9 @@ func init() {
|
|||||||
if cachePath == "" {
|
if cachePath == "" {
|
||||||
cachePath = "./cache"
|
cachePath = "./cache"
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageDir = filepath.Join(cachePath, "gying_users")
|
StorageDir = filepath.Join(cachePath, "gying_users")
|
||||||
|
|
||||||
if err := os.MkdirAll(StorageDir, 0755); err != nil {
|
if err := os.MkdirAll(StorageDir, 0755); err != nil {
|
||||||
fmt.Printf("⚠️ 警告: 无法创建Gying存储目录 %s: %v\n", StorageDir, err)
|
fmt.Printf("⚠️ 警告: 无法创建Gying存储目录 %s: %v\n", StorageDir, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -382,9 +382,9 @@ const HTMLTemplate = `<!DOCTYPE html>
|
|||||||
// GyingPlugin 插件结构
|
// GyingPlugin 插件结构
|
||||||
type GyingPlugin struct {
|
type GyingPlugin struct {
|
||||||
*plugin.BaseAsyncPlugin
|
*plugin.BaseAsyncPlugin
|
||||||
users sync.Map // 内存缓存:hash -> *User
|
users sync.Map // 内存缓存:hash -> *User
|
||||||
scrapers sync.Map // cloudscraper实例缓存:hash -> *cloudscraper.Scraper
|
scrapers sync.Map // cloudscraper实例缓存:hash -> *cloudscraper.Scraper
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
searchCache sync.Map // 插件级缓存:关键词->model.PluginSearchResult
|
searchCache sync.Map // 插件级缓存:关键词->model.PluginSearchResult
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,8 +420,8 @@ type SearchData struct {
|
|||||||
|
|
||||||
// DetailData 详情接口JSON数据结构
|
// DetailData 详情接口JSON数据结构
|
||||||
type DetailData struct {
|
type DetailData struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
WP bool `json:"wp"`
|
WP bool `json:"wp"`
|
||||||
Panlist struct {
|
Panlist struct {
|
||||||
ID []string `json:"id"`
|
ID []string `json:"id"`
|
||||||
Name []string `json:"name"`
|
Name []string `json:"name"`
|
||||||
@@ -457,7 +457,7 @@ func init() {
|
|||||||
|
|
||||||
// 启动定期清理任务
|
// 启动定期清理任务
|
||||||
go p.startCleanupTask()
|
go p.startCleanupTask()
|
||||||
|
|
||||||
// 启动session保活任务(防止session超时)
|
// 启动session保活任务(防止session超时)
|
||||||
go p.startSessionKeepAlive()
|
go p.startSessionKeepAlive()
|
||||||
|
|
||||||
@@ -471,7 +471,7 @@ func (p *GyingPlugin) RegisterWebRoutes(router *gin.RouterGroup) {
|
|||||||
gying := router.Group("/gying")
|
gying := router.Group("/gying")
|
||||||
gying.GET("/:param", p.handleManagePage)
|
gying.GET("/:param", p.handleManagePage)
|
||||||
gying.POST("/:param", p.handleManagePagePOST)
|
gying.POST("/:param", p.handleManagePagePOST)
|
||||||
|
|
||||||
fmt.Printf("[Gying] Web路由已注册: /gying/:param\n")
|
fmt.Printf("[Gying] Web路由已注册: /gying/:param\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,7 +580,7 @@ func (p *GyingPlugin) loadAllUsers() {
|
|||||||
// scraper实例将在initDefaultAccounts中通过重新登录获取
|
// scraper实例将在initDefaultAccounts中通过重新登录获取
|
||||||
p.users.Store(user.Hash, &user)
|
p.users.Store(user.Hash, &user)
|
||||||
count++
|
count++
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
hasPassword := "无"
|
hasPassword := "无"
|
||||||
if user.EncryptedPassword != "" {
|
if user.EncryptedPassword != "" {
|
||||||
@@ -596,8 +596,8 @@ func (p *GyingPlugin) loadAllUsers() {
|
|||||||
// initDefaultAccounts 初始化所有账户(异步执行,不阻塞启动)
|
// initDefaultAccounts 初始化所有账户(异步执行,不阻塞启动)
|
||||||
// 包括:1. DefaultAccounts(代码配置) 2. 从文件加载的用户(使用加密密码重新登录)
|
// 包括:1. DefaultAccounts(代码配置) 2. 从文件加载的用户(使用加密密码重新登录)
|
||||||
func (p *GyingPlugin) initDefaultAccounts() {
|
func (p *GyingPlugin) initDefaultAccounts() {
|
||||||
fmt.Printf("[Gying] ========== 异步初始化所有账户 ==========\n")
|
// fmt.Printf("[Gying] ========== 异步初始化所有账户 ==========\n")
|
||||||
|
|
||||||
// 步骤1:处理DefaultAccounts(代码中配置的默认账户)
|
// 步骤1:处理DefaultAccounts(代码中配置的默认账户)
|
||||||
for i, account := range DefaultAccounts {
|
for i, account := range DefaultAccounts {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
@@ -606,7 +606,7 @@ func (p *GyingPlugin) initDefaultAccounts() {
|
|||||||
|
|
||||||
p.initOrRestoreUser(account.Username, account.Password, "default")
|
p.initOrRestoreUser(account.Username, account.Password, "default")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 步骤2:遍历所有已加载的用户,恢复没有scraper的用户
|
// 步骤2:遍历所有已加载的用户,恢复没有scraper的用户
|
||||||
var usersToRestore []*User
|
var usersToRestore []*User
|
||||||
p.users.Range(func(key, value interface{}) bool {
|
p.users.Range(func(key, value interface{}) bool {
|
||||||
@@ -618,32 +618,32 @@ func (p *GyingPlugin) initDefaultAccounts() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(usersToRestore) > 0 {
|
if len(usersToRestore) > 0 {
|
||||||
fmt.Printf("[Gying] 发现 %d 个需要恢复的用户(使用加密密码重新登录)\n", len(usersToRestore))
|
fmt.Printf("[Gying] 发现 %d 个需要恢复的用户(使用加密密码重新登录)\n", len(usersToRestore))
|
||||||
for i, user := range usersToRestore {
|
for i, user := range usersToRestore {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] [恢复用户 %d/%d] 处理: %s\n", i+1, len(usersToRestore), user.UsernameMasked)
|
fmt.Printf("[Gying] [恢复用户 %d/%d] 处理: %s\n", i+1, len(usersToRestore), user.UsernameMasked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解密密码
|
// 解密密码
|
||||||
password, err := p.decryptPassword(user.EncryptedPassword)
|
password, err := p.decryptPassword(user.EncryptedPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[Gying] ❌ 用户 %s 解密密码失败: %v\n", user.UsernameMasked, err)
|
fmt.Printf("[Gying] ❌ 用户 %s 解密密码失败: %v\n", user.UsernameMasked, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
p.initOrRestoreUser(user.Username, password, "restore")
|
p.initOrRestoreUser(user.Username, password, "restore")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("[Gying] ========== 所有账户初始化完成 ==========\n")
|
// fmt.Printf("[Gying] ========== 所有账户初始化完成 ==========\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// initOrRestoreUser 初始化或恢复单个用户(登录并保存)
|
// initOrRestoreUser 初始化或恢复单个用户(登录并保存)
|
||||||
func (p *GyingPlugin) initOrRestoreUser(username, password, source string) {
|
func (p *GyingPlugin) initOrRestoreUser(username, password, source string) {
|
||||||
hash := p.generateHash(username)
|
hash := p.generateHash(username)
|
||||||
|
|
||||||
// 检查scraper是否已存在
|
// 检查scraper是否已存在
|
||||||
_, scraperExists := p.scrapers.Load(hash)
|
_, scraperExists := p.scrapers.Load(hash)
|
||||||
if scraperExists {
|
if scraperExists {
|
||||||
@@ -652,7 +652,7 @@ func (p *GyingPlugin) initOrRestoreUser(username, password, source string) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 开始登录账户: %s\n", username)
|
fmt.Printf("[Gying] 开始登录账户: %s\n", username)
|
||||||
@@ -673,7 +673,7 @@ func (p *GyingPlugin) initOrRestoreUser(username, password, source string) {
|
|||||||
fmt.Printf("[Gying] ❌ 加密密码失败: %v\n", err)
|
fmt.Printf("[Gying] ❌ 加密密码失败: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存用户
|
// 保存用户
|
||||||
user := &User{
|
user := &User{
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
@@ -687,10 +687,10 @@ func (p *GyingPlugin) initOrRestoreUser(username, password, source string) {
|
|||||||
ExpireAt: time.Now().AddDate(0, 4, 0), // 121天有效期
|
ExpireAt: time.Now().AddDate(0, 4, 0), // 121天有效期
|
||||||
LastAccessAt: time.Now(),
|
LastAccessAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存scraper实例到内存
|
// 保存scraper实例到内存
|
||||||
p.scrapers.Store(hash, scraper)
|
p.scrapers.Store(hash, scraper)
|
||||||
|
|
||||||
if err := p.saveUser(user); err != nil {
|
if err := p.saveUser(user); err != nil {
|
||||||
fmt.Printf("[Gying] ❌ 保存账户失败: %v\n", err)
|
fmt.Printf("[Gying] ❌ 保存账户失败: %v\n", err)
|
||||||
return
|
return
|
||||||
@@ -734,7 +734,7 @@ func (p *GyingPlugin) deleteUser(hash string) error {
|
|||||||
// getActiveUsers 获取有效用户
|
// getActiveUsers 获取有效用户
|
||||||
func (p *GyingPlugin) getActiveUsers() []*User {
|
func (p *GyingPlugin) getActiveUsers() []*User {
|
||||||
var users []*User
|
var users []*User
|
||||||
|
|
||||||
p.users.Range(func(key, value interface{}) bool {
|
p.users.Range(func(key, value interface{}) bool {
|
||||||
user := value.(*User)
|
user := value.(*User)
|
||||||
if user.Status == "active" && user.Cookie != "" {
|
if user.Status == "active" && user.Cookie != "" {
|
||||||
@@ -742,7 +742,7 @@ func (p *GyingPlugin) getActiveUsers() []*User {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,13 +823,13 @@ func (p *GyingPlugin) handleGetStatus(c *gin.Context, hash string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
respondSuccess(c, "获取成功", gin.H{
|
respondSuccess(c, "获取成功", gin.H{
|
||||||
"hash": hash,
|
"hash": hash,
|
||||||
"logged_in": loggedIn,
|
"logged_in": loggedIn,
|
||||||
"status": user.Status,
|
"status": user.Status,
|
||||||
"username_masked": user.UsernameMasked,
|
"username_masked": user.UsernameMasked,
|
||||||
"login_time": user.LoginAt.Format("2006-01-02 15:04:05"),
|
"login_time": user.LoginAt.Format("2006-01-02 15:04:05"),
|
||||||
"expire_time": user.ExpireAt.Format("2006-01-02 15:04:05"),
|
"expire_time": user.ExpireAt.Format("2006-01-02 15:04:05"),
|
||||||
"expires_in_days": expiresInDays,
|
"expires_in_days": expiresInDays,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -859,7 +859,7 @@ func (p *GyingPlugin) handleLogin(c *gin.Context, hash string, reqData map[strin
|
|||||||
respondError(c, "加密密码失败: "+err.Error())
|
respondError(c, "加密密码失败: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存用户
|
// 保存用户
|
||||||
user := &User{
|
user := &User{
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
@@ -872,7 +872,7 @@ func (p *GyingPlugin) handleLogin(c *gin.Context, hash string, reqData map[strin
|
|||||||
ExpireAt: time.Now().AddDate(0, 4, 0), // 121天
|
ExpireAt: time.Now().AddDate(0, 4, 0), // 121天
|
||||||
LastAccessAt: time.Now(),
|
LastAccessAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := p.getUserByHash(hash); !exists {
|
if _, exists := p.getUserByHash(hash); !exists {
|
||||||
user.CreatedAt = time.Now()
|
user.CreatedAt = time.Now()
|
||||||
}
|
}
|
||||||
@@ -929,13 +929,13 @@ func (p *GyingPlugin) handleTestSearch(c *gin.Context, hash string, reqData map[
|
|||||||
respondError(c, "用户scraper实例不存在,请重新登录")
|
respondError(c, "用户scraper实例不存在,请重新登录")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scraper, ok := scraperVal.(*cloudscraper.Scraper)
|
scraper, ok := scraperVal.(*cloudscraper.Scraper)
|
||||||
if !ok || scraper == nil {
|
if !ok || scraper == nil {
|
||||||
respondError(c, "scraper实例无效,请重新登录")
|
respondError(c, "scraper实例无效,请重新登录")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行搜索(带403自动重新登录)
|
// 执行搜索(带403自动重新登录)
|
||||||
results, err := p.searchWithScraperWithRetry(keyword, scraper, user)
|
results, err := p.searchWithScraperWithRetry(keyword, scraper, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -980,27 +980,27 @@ func (p *GyingPlugin) handleTestSearch(c *gin.Context, hash string, reqData map[
|
|||||||
func (p *GyingPlugin) encryptPassword(password string) (string, error) {
|
func (p *GyingPlugin) encryptPassword(password string) (string, error) {
|
||||||
// 使用固定密钥(实际应用中可以使用配置或环境变量)
|
// 使用固定密钥(实际应用中可以使用配置或环境变量)
|
||||||
key := []byte("gying-secret-key-32bytes-long!!!") // 32字节密钥用于AES-256
|
key := []byte("gying-secret-key-32bytes-long!!!") // 32字节密钥用于AES-256
|
||||||
|
|
||||||
block, err := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建GCM模式
|
// 创建GCM模式
|
||||||
gcm, err := cipher.NewGCM(block)
|
gcm, err := cipher.NewGCM(block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成随机nonce
|
// 生成随机nonce
|
||||||
nonce := make([]byte, gcm.NonceSize())
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加密
|
// 加密
|
||||||
ciphertext := gcm.Seal(nonce, nonce, []byte(password), nil)
|
ciphertext := gcm.Seal(nonce, nonce, []byte(password), nil)
|
||||||
|
|
||||||
// 返回base64编码的密文
|
// 返回base64编码的密文
|
||||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||||
}
|
}
|
||||||
@@ -1009,34 +1009,34 @@ func (p *GyingPlugin) encryptPassword(password string) (string, error) {
|
|||||||
func (p *GyingPlugin) decryptPassword(encrypted string) (string, error) {
|
func (p *GyingPlugin) decryptPassword(encrypted string) (string, error) {
|
||||||
// 使用与加密相同的密钥
|
// 使用与加密相同的密钥
|
||||||
key := []byte("gying-secret-key-32bytes-long!!!")
|
key := []byte("gying-secret-key-32bytes-long!!!")
|
||||||
|
|
||||||
// base64解码
|
// base64解码
|
||||||
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
|
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
gcm, err := cipher.NewGCM(block)
|
gcm, err := cipher.NewGCM(block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
nonceSize := gcm.NonceSize()
|
nonceSize := gcm.NonceSize()
|
||||||
if len(ciphertext) < nonceSize {
|
if len(ciphertext) < nonceSize {
|
||||||
return "", fmt.Errorf("ciphertext too short")
|
return "", fmt.Errorf("ciphertext too short")
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(plaintext), nil
|
return string(plaintext), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1049,64 +1049,64 @@ func (p *GyingPlugin) createScraperWithCookies(cookieStr string) (*cloudscraper.
|
|||||||
// 创建cloudscraper实例,配置以保护cookies不被刷新
|
// 创建cloudscraper实例,配置以保护cookies不被刷新
|
||||||
scraper, err := cloudscraper.New(
|
scraper, err := cloudscraper.New(
|
||||||
cloudscraper.WithSessionConfig(
|
cloudscraper.WithSessionConfig(
|
||||||
false, // refreshOn403 = false,禁用403时自动刷新
|
false, // refreshOn403 = false,禁用403时自动刷新
|
||||||
365*24*time.Hour, // interval = 1年,基本不刷新
|
365*24*time.Hour, // interval = 1年,基本不刷新
|
||||||
0, // maxRetries = 0
|
0, // maxRetries = 0
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("创建cloudscraper失败: %w", err)
|
return nil, fmt.Errorf("创建cloudscraper失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有保存的cookies,使用反射设置到scraper的内部http.Client
|
// 如果有保存的cookies,使用反射设置到scraper的内部http.Client
|
||||||
if cookieStr != "" {
|
if cookieStr != "" {
|
||||||
cookies := parseCookieString(cookieStr)
|
cookies := parseCookieString(cookieStr)
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 正在恢复 %d 个cookie到scraper实例\n", len(cookies))
|
fmt.Printf("[Gying] 正在恢复 %d 个cookie到scraper实例\n", len(cookies))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用反射访问scraper的unexported client字段
|
// 使用反射访问scraper的unexported client字段
|
||||||
scraperValue := reflect.ValueOf(scraper).Elem()
|
scraperValue := reflect.ValueOf(scraper).Elem()
|
||||||
clientField := scraperValue.FieldByName("client")
|
clientField := scraperValue.FieldByName("client")
|
||||||
|
|
||||||
if clientField.IsValid() && !clientField.IsNil() {
|
if clientField.IsValid() && !clientField.IsNil() {
|
||||||
// 使用反射访问client (需要使用Elem()因为是指针)
|
// 使用反射访问client (需要使用Elem()因为是指针)
|
||||||
clientValue := reflect.NewAt(clientField.Type(), unsafe.Pointer(clientField.UnsafeAddr())).Elem()
|
clientValue := reflect.NewAt(clientField.Type(), unsafe.Pointer(clientField.UnsafeAddr())).Elem()
|
||||||
client, ok := clientValue.Interface().(*http.Client)
|
client, ok := clientValue.Interface().(*http.Client)
|
||||||
|
|
||||||
if ok && client != nil && client.Jar != nil {
|
if ok && client != nil && client.Jar != nil {
|
||||||
// 将cookies设置到cookiejar
|
// 将cookies设置到cookiejar
|
||||||
// 注意:必须使用正确的URL和cookie属性
|
// 注意:必须使用正确的URL和cookie属性
|
||||||
gyingURL, _ := url.Parse("https://www.gying.net")
|
gyingURL, _ := url.Parse("https://www.gying.net")
|
||||||
var httpCookies []*http.Cookie
|
var httpCookies []*http.Cookie
|
||||||
|
|
||||||
for name, value := range cookies {
|
for name, value := range cookies {
|
||||||
cookie := &http.Cookie{
|
cookie := &http.Cookie{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: value,
|
Value: value,
|
||||||
// 不设置Domain和Path,让cookiejar根据URL自动推导
|
// 不设置Domain和Path,让cookiejar根据URL自动推导
|
||||||
// cookiejar.SetCookies会根据提供的URL自动设置正确的Domain和Path
|
// cookiejar.SetCookies会根据提供的URL自动设置正确的Domain和Path
|
||||||
}
|
}
|
||||||
httpCookies = append(httpCookies, cookie)
|
httpCookies = append(httpCookies, cookie)
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 准备恢复Cookie: %s=%s\n",
|
fmt.Printf("[Gying] 准备恢复Cookie: %s=%s\n",
|
||||||
cookie.Name, cookie.Value[:min(10, len(cookie.Value))])
|
cookie.Name, cookie.Value[:min(10, len(cookie.Value))])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.Jar.SetCookies(gyingURL, httpCookies)
|
client.Jar.SetCookies(gyingURL, httpCookies)
|
||||||
|
|
||||||
// 验证cookies是否被正确设置
|
// 验证cookies是否被正确设置
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
storedCookies := client.Jar.Cookies(gyingURL)
|
storedCookies := client.Jar.Cookies(gyingURL)
|
||||||
fmt.Printf("[Gying] ✅ 成功恢复 %d 个cookie到scraper的cookiejar\n", len(cookies))
|
fmt.Printf("[Gying] ✅ 成功恢复 %d 个cookie到scraper的cookiejar\n", len(cookies))
|
||||||
fmt.Printf("[Gying] 验证: cookiejar中现有 %d 个cookie\n", len(storedCookies))
|
fmt.Printf("[Gying] 验证: cookiejar中现有 %d 个cookie\n", len(storedCookies))
|
||||||
|
|
||||||
// 详细打印每个cookie以便调试
|
// 详细打印每个cookie以便调试
|
||||||
for i, c := range storedCookies {
|
for i, c := range storedCookies {
|
||||||
fmt.Printf("[Gying] 设置后Cookie[%d]: %s=%s (Domain:%s, Path:%s)\n",
|
fmt.Printf("[Gying] 设置后Cookie[%d]: %s=%s (Domain:%s, Path:%s)\n",
|
||||||
i, c.Name, c.Value[:min(10, len(c.Value))], c.Domain, c.Path)
|
i, c.Name, c.Value[:min(10, len(c.Value))], c.Domain, c.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1121,7 +1121,7 @@ func (p *GyingPlugin) createScraperWithCookies(cookieStr string) (*cloudscraper.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return scraper, nil
|
return scraper, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1129,7 +1129,7 @@ func (p *GyingPlugin) createScraperWithCookies(cookieStr string) (*cloudscraper.
|
|||||||
func parseCookieString(cookieStr string) map[string]string {
|
func parseCookieString(cookieStr string) map[string]string {
|
||||||
cookies := make(map[string]string)
|
cookies := make(map[string]string)
|
||||||
parts := strings.Split(cookieStr, ";")
|
parts := strings.Split(cookieStr, ";")
|
||||||
|
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
part = strings.TrimSpace(part)
|
part = strings.TrimSpace(part)
|
||||||
if idx := strings.Index(part, "="); idx > 0 {
|
if idx := strings.Index(part, "="); idx > 0 {
|
||||||
@@ -1138,18 +1138,18 @@ func parseCookieString(cookieStr string) map[string]string {
|
|||||||
cookies[name] = value
|
cookies[name] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cookies
|
return cookies
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ 登录逻辑 ============
|
// ============ 登录逻辑 ============
|
||||||
|
|
||||||
// doLogin 执行登录,返回scraper实例和cookie字符串
|
// doLogin 执行登录,返回scraper实例和cookie字符串
|
||||||
//
|
//
|
||||||
// 登录流程(3步):
|
// 登录流程(3步):
|
||||||
// 1. GET登录页 (https://www.gying.net/user/login/) → 获取PHPSESSID
|
// 1. GET登录页 (https://www.gying.net/user/login/) → 获取PHPSESSID
|
||||||
// 2. POST登录 (https://www.gying.net/user/login) → 获取BT_auth、BT_cookietime等认证cookies
|
// 2. POST登录 (https://www.gying.net/user/login) → 获取BT_auth、BT_cookietime等认证cookies
|
||||||
// 3. GET详情页 (https://www.gying.net/mv/wkMn) → 触发防爬cookies (vrg_sc、vrg_go等)
|
// 3. GET详情页 (https://www.gying.net/mv/wkMn) → 触发防爬cookies (vrg_sc、vrg_go等)
|
||||||
//
|
//
|
||||||
// 返回: (*cloudscraper.Scraper, cookie字符串, error)
|
// 返回: (*cloudscraper.Scraper, cookie字符串, error)
|
||||||
func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper, string, error) {
|
func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper, string, error) {
|
||||||
@@ -1163,9 +1163,9 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
// 关键配置:禁用403自动刷新,防止cookie被清空
|
// 关键配置:禁用403自动刷新,防止cookie被清空
|
||||||
scraper, err := cloudscraper.New(
|
scraper, err := cloudscraper.New(
|
||||||
cloudscraper.WithSessionConfig(
|
cloudscraper.WithSessionConfig(
|
||||||
false, // refreshOn403 = false,禁用403时自动刷新(重要!)
|
false, // refreshOn403 = false,禁用403时自动刷新(重要!)
|
||||||
365*24*time.Hour, // interval = 1年,基本不刷新
|
365*24*time.Hour, // interval = 1年,基本不刷新
|
||||||
0, // maxRetries = 0
|
0, // maxRetries = 0
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1181,7 +1181,7 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
|
|
||||||
// 创建cookieMap用于收集所有cookies
|
// 创建cookieMap用于收集所有cookies
|
||||||
cookieMap := make(map[string]string)
|
cookieMap := make(map[string]string)
|
||||||
|
|
||||||
// ========== 步骤1: GET登录页 (获取初始PHPSESSID) ==========
|
// ========== 步骤1: GET登录页 (获取初始PHPSESSID) ==========
|
||||||
loginPageURL := "https://www.gying.net/user/login/"
|
loginPageURL := "https://www.gying.net/user/login/"
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
@@ -1201,7 +1201,7 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 登录页面状态码: %d\n", getResp.StatusCode)
|
fmt.Printf("[Gying] 登录页面状态码: %d\n", getResp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从登录页响应中收集cookies
|
// 从登录页响应中收集cookies
|
||||||
for _, setCookie := range getResp.Header["Set-Cookie"] {
|
for _, setCookie := range getResp.Header["Set-Cookie"] {
|
||||||
parts := strings.Split(setCookie, ";")
|
parts := strings.Split(setCookie, ";")
|
||||||
@@ -1246,7 +1246,7 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 响应状态码: %d\n", resp.StatusCode)
|
fmt.Printf("[Gying] 响应状态码: %d\n", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从POST登录响应中收集cookies
|
// 从POST登录响应中收集cookies
|
||||||
for _, setCookie := range resp.Header["Set-Cookie"] {
|
for _, setCookie := range resp.Header["Set-Cookie"] {
|
||||||
parts := strings.Split(setCookie, ";")
|
parts := strings.Split(setCookie, ";")
|
||||||
@@ -1296,7 +1296,7 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
// 检查登录结果(兼容多种类型:int、float64、json.Number、string)
|
// 检查登录结果(兼容多种类型:int、float64、json.Number、string)
|
||||||
var codeValue int
|
var codeValue int
|
||||||
codeInterface := loginResp["code"]
|
codeInterface := loginResp["code"]
|
||||||
|
|
||||||
switch v := codeInterface.(type) {
|
switch v := codeInterface.(type) {
|
||||||
case int:
|
case int:
|
||||||
codeValue = v
|
codeValue = v
|
||||||
@@ -1332,16 +1332,16 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 步骤3: GET详情页收集完整Cookie\n")
|
fmt.Printf("[Gying] 步骤3: GET详情页收集完整Cookie\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
detailResp, err := scraper.Get("https://www.gying.net/mv/wkMn")
|
detailResp, err := scraper.Get("https://www.gying.net/mv/wkMn")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer detailResp.Body.Close()
|
defer detailResp.Body.Close()
|
||||||
ioutil.ReadAll(detailResp.Body)
|
ioutil.ReadAll(detailResp.Body)
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 详情页状态码: %d\n", detailResp.StatusCode)
|
fmt.Printf("[Gying] 详情页状态码: %d\n", detailResp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从详情页响应中收集cookies
|
// 从详情页响应中收集cookies
|
||||||
for _, setCookie := range detailResp.Header["Set-Cookie"] {
|
for _, setCookie := range detailResp.Header["Set-Cookie"] {
|
||||||
parts := strings.Split(setCookie, ";")
|
parts := strings.Split(setCookie, ";")
|
||||||
@@ -1362,14 +1362,14 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建cookie字符串
|
// 构建cookie字符串
|
||||||
var cookieParts []string
|
var cookieParts []string
|
||||||
for name, value := range cookieMap {
|
for name, value := range cookieMap {
|
||||||
cookieParts = append(cookieParts, fmt.Sprintf("%s=%s", name, value))
|
cookieParts = append(cookieParts, fmt.Sprintf("%s=%s", name, value))
|
||||||
}
|
}
|
||||||
cookieStr := strings.Join(cookieParts, "; ")
|
cookieStr := strings.Join(cookieParts, "; ")
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] ✅ 登录成功!提取到 %d 个Cookie\n", len(cookieMap))
|
fmt.Printf("[Gying] ✅ 登录成功!提取到 %d 个Cookie\n", len(cookieMap))
|
||||||
fmt.Printf("[Gying] Cookie字符串长度: %d\n", len(cookieStr))
|
fmt.Printf("[Gying] Cookie字符串长度: %d\n", len(cookieStr))
|
||||||
@@ -1402,7 +1402,7 @@ func (p *GyingPlugin) reloginUser(user *User) error {
|
|||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 🔄 开始重新登录用户: %s\n", user.UsernameMasked)
|
fmt.Printf("[Gying] 🔄 开始重新登录用户: %s\n", user.UsernameMasked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解密密码
|
// 解密密码
|
||||||
password, err := p.decryptPassword(user.EncryptedPassword)
|
password, err := p.decryptPassword(user.EncryptedPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1411,7 +1411,7 @@ func (p *GyingPlugin) reloginUser(user *User) error {
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("解密密码失败: %w", err)
|
return fmt.Errorf("解密密码失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行登录
|
// 执行登录
|
||||||
scraper, cookie, err := p.doLogin(user.Username, password)
|
scraper, cookie, err := p.doLogin(user.Username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1420,26 +1420,26 @@ func (p *GyingPlugin) reloginUser(user *User) error {
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("重新登录失败: %w", err)
|
return fmt.Errorf("重新登录失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新scraper实例
|
// 更新scraper实例
|
||||||
p.scrapers.Store(user.Hash, scraper)
|
p.scrapers.Store(user.Hash, scraper)
|
||||||
|
|
||||||
// 更新用户信息
|
// 更新用户信息
|
||||||
user.Cookie = cookie
|
user.Cookie = cookie
|
||||||
user.LoginAt = time.Now()
|
user.LoginAt = time.Now()
|
||||||
user.ExpireAt = time.Now().AddDate(0, 4, 0)
|
user.ExpireAt = time.Now().AddDate(0, 4, 0)
|
||||||
user.Status = "active"
|
user.Status = "active"
|
||||||
|
|
||||||
if err := p.saveUser(user); err != nil {
|
if err := p.saveUser(user); err != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] ⚠️ 保存用户失败: %v\n", err)
|
fmt.Printf("[Gying] ⚠️ 保存用户失败: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] ✅ 用户 %s 重新登录成功\n", user.UsernameMasked)
|
fmt.Printf("[Gying] ✅ 用户 %s 重新登录成功\n", user.UsernameMasked)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1459,12 +1459,12 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
// 获取用户的scraper实例
|
// 获取用户的scraper实例
|
||||||
scraperVal, exists := p.scrapers.Load(u.Hash)
|
scraperVal, exists := p.scrapers.Load(u.Hash)
|
||||||
var scraper *cloudscraper.Scraper
|
var scraper *cloudscraper.Scraper
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 用户 %s 没有scraper实例,尝试使用已保存的cookie创建\n", u.UsernameMasked)
|
fmt.Printf("[Gying] 用户 %s 没有scraper实例,尝试使用已保存的cookie创建\n", u.UsernameMasked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用已保存的cookie创建scraper实例(关键!)
|
// 使用已保存的cookie创建scraper实例(关键!)
|
||||||
newScraper, err := p.createScraperWithCookies(u.Cookie)
|
newScraper, err := p.createScraperWithCookies(u.Cookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1473,11 +1473,11 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存储新创建的scraper实例
|
// 存储新创建的scraper实例
|
||||||
p.scrapers.Store(u.Hash, newScraper)
|
p.scrapers.Store(u.Hash, newScraper)
|
||||||
scraper = newScraper
|
scraper = newScraper
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 已为用户 %s 恢复scraper实例(含cookie)\n", u.UsernameMasked)
|
fmt.Printf("[Gying] 已为用户 %s 恢复scraper实例(含cookie)\n", u.UsernameMasked)
|
||||||
}
|
}
|
||||||
@@ -1515,13 +1515,13 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
// searchWithScraperWithRetry 使用scraper搜索(带403自动重新登录重试)
|
// searchWithScraperWithRetry 使用scraper搜索(带403自动重新登录重试)
|
||||||
func (p *GyingPlugin) searchWithScraperWithRetry(keyword string, scraper *cloudscraper.Scraper, user *User) ([]model.SearchResult, error) {
|
func (p *GyingPlugin) searchWithScraperWithRetry(keyword string, scraper *cloudscraper.Scraper, user *User) ([]model.SearchResult, error) {
|
||||||
results, err := p.searchWithScraper(keyword, scraper)
|
results, err := p.searchWithScraper(keyword, scraper)
|
||||||
|
|
||||||
// 检测是否为403错误
|
// 检测是否为403错误
|
||||||
if err != nil && strings.Contains(err.Error(), "403") {
|
if err != nil && strings.Contains(err.Error(), "403") {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] ⚠️ 检测到403错误,尝试重新登录用户 %s\n", user.UsernameMasked)
|
fmt.Printf("[Gying] ⚠️ 检测到403错误,尝试重新登录用户 %s\n", user.UsernameMasked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试重新登录
|
// 尝试重新登录
|
||||||
if reloginErr := p.reloginUser(user); reloginErr != nil {
|
if reloginErr := p.reloginUser(user); reloginErr != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
@@ -1529,18 +1529,18 @@ func (p *GyingPlugin) searchWithScraperWithRetry(keyword string, scraper *clouds
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("403错误且重新登录失败: %w", reloginErr)
|
return nil, fmt.Errorf("403错误且重新登录失败: %w", reloginErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取新的scraper实例
|
// 获取新的scraper实例
|
||||||
scraperVal, exists := p.scrapers.Load(user.Hash)
|
scraperVal, exists := p.scrapers.Load(user.Hash)
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("重新登录后未找到scraper实例")
|
return nil, fmt.Errorf("重新登录后未找到scraper实例")
|
||||||
}
|
}
|
||||||
|
|
||||||
newScraper, ok := scraperVal.(*cloudscraper.Scraper)
|
newScraper, ok := scraperVal.(*cloudscraper.Scraper)
|
||||||
if !ok || newScraper == nil {
|
if !ok || newScraper == nil {
|
||||||
return nil, fmt.Errorf("重新登录后scraper实例无效")
|
return nil, fmt.Errorf("重新登录后scraper实例无效")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用新scraper重试搜索
|
// 使用新scraper重试搜索
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 🔄 使用新登录状态重试搜索\n")
|
fmt.Printf("[Gying] 🔄 使用新登录状态重试搜索\n")
|
||||||
@@ -1550,7 +1550,7 @@ func (p *GyingPlugin) searchWithScraperWithRetry(keyword string, scraper *clouds
|
|||||||
return nil, fmt.Errorf("重新登录后搜索仍然失败: %w", err)
|
return nil, fmt.Errorf("重新登录后搜索仍然失败: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, err
|
return results, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1563,7 +1563,7 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
|
|
||||||
// 1. 使用cloudscraper请求搜索页面
|
// 1. 使用cloudscraper请求搜索页面
|
||||||
searchURL := fmt.Sprintf("https://www.gying.net/s/1---1/%s", url.QueryEscape(keyword))
|
searchURL := fmt.Sprintf("https://www.gying.net/s/1---1/%s", url.QueryEscape(keyword))
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 搜索URL: %s\n", searchURL)
|
fmt.Printf("[Gying] 搜索URL: %s\n", searchURL)
|
||||||
fmt.Printf("[Gying] 使用cloudscraper发送请求\n")
|
fmt.Printf("[Gying] 使用cloudscraper发送请求\n")
|
||||||
@@ -1581,7 +1581,7 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 搜索响应状态码: %d\n", resp.StatusCode)
|
fmt.Printf("[Gying] 搜索响应状态码: %d\n", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取响应body
|
// 读取响应body
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1602,7 +1602,7 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
fmt.Printf("[Gying] 响应预览: %s\n", preview)
|
fmt.Printf("[Gying] 响应预览: %s\n", preview)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查403错误
|
// 检查403错误
|
||||||
if resp.StatusCode == 403 {
|
if resp.StatusCode == 403 {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
@@ -1621,7 +1621,7 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
// 2. 提取 _obj.search JSON
|
// 2. 提取 _obj.search JSON
|
||||||
re := regexp.MustCompile(`_obj\.search=(\{.*?\});`)
|
re := regexp.MustCompile(`_obj\.search=(\{.*?\});`)
|
||||||
matches := re.FindSubmatch(body)
|
matches := re.FindSubmatch(body)
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 正则匹配结果: 找到 %d 个匹配\n", len(matches))
|
fmt.Printf("[Gying] 正则匹配结果: 找到 %d 个匹配\n", len(matches))
|
||||||
}
|
}
|
||||||
@@ -1679,9 +1679,9 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
fmt.Printf("[Gying] 防爬cookies刷新成功 (状态码: %d)\n", refreshResp.StatusCode)
|
fmt.Printf("[Gying] 防爬cookies刷新成功 (状态码: %d)\n", refreshResp.StatusCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 并发请求详情接口
|
// 4. 并发请求详情接口
|
||||||
results, err := p.fetchAllDetails(&searchData, scraper)
|
results, err := p.fetchAllDetails(&searchData, scraper, keyword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] fetchAllDetails 失败: %v\n", err)
|
fmt.Printf("[Gying] fetchAllDetails 失败: %v\n", err)
|
||||||
@@ -1689,7 +1689,7 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] fetchAllDetails 返回 %d 条结果\n", len(results))
|
fmt.Printf("[Gying] fetchAllDetails 返回 %d 条结果\n", len(results))
|
||||||
fmt.Printf("[Gying] ---------- searchWithScraper 结束 ----------\n")
|
fmt.Printf("[Gying] ---------- searchWithScraper 结束 ----------\n")
|
||||||
@@ -1699,10 +1699,10 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetchAllDetails 并发获取所有详情
|
// fetchAllDetails 并发获取所有详情
|
||||||
func (p *GyingPlugin) fetchAllDetails(searchData *SearchData, scraper *cloudscraper.Scraper) ([]model.SearchResult, error) {
|
func (p *GyingPlugin) fetchAllDetails(searchData *SearchData, scraper *cloudscraper.Scraper, keyword string) ([]model.SearchResult, error) {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] >>> fetchAllDetails 开始\n")
|
fmt.Printf("[Gying] >>> fetchAllDetails 开始\n")
|
||||||
fmt.Printf("[Gying] 需要获取 %d 个详情\n", len(searchData.L.I))
|
fmt.Printf("[Gying] 需要获取 %d 个详情,关键词: %s\n", len(searchData.L.I), keyword)
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []model.SearchResult
|
var results []model.SearchResult
|
||||||
@@ -1715,6 +1715,9 @@ func (p *GyingPlugin) fetchAllDetails(searchData *SearchData, scraper *cloudscra
|
|||||||
successCount := 0
|
successCount := 0
|
||||||
failCount := 0
|
failCount := 0
|
||||||
has403 := false
|
has403 := false
|
||||||
|
|
||||||
|
// 将关键词转为小写,用于不区分大小写的匹配
|
||||||
|
keywordLower := strings.ToLower(keyword)
|
||||||
|
|
||||||
for i := 0; i < len(searchData.L.I); i++ {
|
for i := 0; i < len(searchData.L.I); i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
@@ -1732,9 +1735,28 @@ func (p *GyingPlugin) fetchAllDetails(searchData *SearchData, scraper *cloudscra
|
|||||||
}
|
}
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
|
||||||
|
// 检查标题是否包含搜索关键词
|
||||||
|
if index >= len(searchData.L.Title) {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] [%d/%d] ⏭️ 跳过: 索引超出标题数组范围\n",
|
||||||
|
index+1, len(searchData.L.I))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
title := searchData.L.Title[index]
|
||||||
|
titleLower := strings.ToLower(title)
|
||||||
|
if !strings.Contains(titleLower, keywordLower) {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] [%d/%d] ⏭️ 跳过: 标题不包含关键词 '%s' (标题: %s)\n",
|
||||||
|
index+1, len(searchData.L.I), keyword, title)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] [%d/%d] 获取详情: ID=%s, Type=%s\n",
|
fmt.Printf("[Gying] [%d/%d] 获取详情: ID=%s, Type=%s, 标题=%s\n",
|
||||||
index+1, len(searchData.L.I), searchData.L.I[index], searchData.L.D[index])
|
index+1, len(searchData.L.I), searchData.L.I[index], searchData.L.D[index], title)
|
||||||
}
|
}
|
||||||
|
|
||||||
detail, err := p.fetchDetail(searchData.L.I[index], searchData.L.D[index], scraper)
|
detail, err := p.fetchDetail(searchData.L.I[index], searchData.L.D[index], scraper)
|
||||||
@@ -1742,7 +1764,7 @@ func (p *GyingPlugin) fetchAllDetails(searchData *SearchData, scraper *cloudscra
|
|||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] [%d/%d] ❌ 获取详情失败: %v\n", index+1, len(searchData.L.I), err)
|
fmt.Printf("[Gying] [%d/%d] ❌ 获取详情失败: %v\n", index+1, len(searchData.L.I), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是403错误
|
// 检查是否是403错误
|
||||||
if strings.Contains(err.Error(), "403") {
|
if strings.Contains(err.Error(), "403") {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@@ -1755,7 +1777,7 @@ func (p *GyingPlugin) fetchAllDetails(searchData *SearchData, scraper *cloudscra
|
|||||||
}
|
}
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
failCount++
|
failCount++
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
@@ -1765,7 +1787,7 @@ func (p *GyingPlugin) fetchAllDetails(searchData *SearchData, scraper *cloudscra
|
|||||||
result := p.buildResult(detail, searchData, index)
|
result := p.buildResult(detail, searchData, index)
|
||||||
if result.Title != "" && len(result.Links) > 0 {
|
if result.Title != "" && len(result.Links) > 0 {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] [%d/%d] ✅ 成功: %s (%d个链接)\n",
|
fmt.Printf("[Gying] [%d/%d] ✅ 成功: %s (%d个链接)\n",
|
||||||
index+1, len(searchData.L.I), result.Title, len(result.Links))
|
index+1, len(searchData.L.I), result.Title, len(result.Links))
|
||||||
}
|
}
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@@ -1774,7 +1796,7 @@ func (p *GyingPlugin) fetchAllDetails(searchData *SearchData, scraper *cloudscra
|
|||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
} else {
|
} else {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] [%d/%d] ⚠️ 跳过: 标题或链接为空 (标题:%s, 链接数:%d)\n",
|
fmt.Printf("[Gying] [%d/%d] ⚠️ 跳过: 标题或链接为空 (标题:%s, 链接数:%d)\n",
|
||||||
index+1, len(searchData.L.I), result.Title, len(result.Links))
|
index+1, len(searchData.L.I), result.Title, len(result.Links))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1794,7 +1816,7 @@ func (p *GyingPlugin) fetchAllDetails(searchData *SearchData, scraper *cloudscra
|
|||||||
}
|
}
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] <<< fetchAllDetails 完成: 成功=%d, 失败=%d, 总计=%d\n",
|
fmt.Printf("[Gying] <<< fetchAllDetails 完成: 成功=%d, 失败=%d, 总计=%d\n",
|
||||||
successCount, failCount, len(searchData.L.I))
|
successCount, failCount, len(searchData.L.I))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1804,7 +1826,7 @@ func (p *GyingPlugin) fetchAllDetails(searchData *SearchData, scraper *cloudscra
|
|||||||
// fetchDetail 获取详情
|
// fetchDetail 获取详情
|
||||||
func (p *GyingPlugin) fetchDetail(resourceID, resourceType string, scraper *cloudscraper.Scraper) (*DetailData, error) {
|
func (p *GyingPlugin) fetchDetail(resourceID, resourceType string, scraper *cloudscraper.Scraper) (*DetailData, error) {
|
||||||
detailURL := fmt.Sprintf("https://www.gying.net/res/downurl/%s/%s", resourceType, resourceID)
|
detailURL := fmt.Sprintf("https://www.gying.net/res/downurl/%s/%s", resourceType, resourceID)
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] fetchDetail: %s\n", detailURL)
|
fmt.Printf("[Gying] fetchDetail: %s\n", detailURL)
|
||||||
}
|
}
|
||||||
@@ -1929,7 +1951,7 @@ func (p *GyingPlugin) extractPanLinks(detail *DetailData) []model.Link {
|
|||||||
|
|
||||||
for i := 0; i < len(detail.Panlist.URL); i++ {
|
for i := 0; i < len(detail.Panlist.URL); i++ {
|
||||||
linkURL := strings.TrimSpace(detail.Panlist.URL[i])
|
linkURL := strings.TrimSpace(detail.Panlist.URL[i])
|
||||||
|
|
||||||
// 去除URL中的访问码标记
|
// 去除URL中的访问码标记
|
||||||
linkURL = regexp.MustCompile(`(访问码:.*?)`).ReplaceAllString(linkURL, "")
|
linkURL = regexp.MustCompile(`(访问码:.*?)`).ReplaceAllString(linkURL, "")
|
||||||
linkURL = regexp.MustCompile(`\(访问码:.*?\)`).ReplaceAllString(linkURL, "")
|
linkURL = regexp.MustCompile(`\(访问码:.*?\)`).ReplaceAllString(linkURL, "")
|
||||||
@@ -1951,7 +1973,7 @@ func (p *GyingPlugin) extractPanLinks(detail *DetailData) []model.Link {
|
|||||||
if i < len(detail.Panlist.P) && detail.Panlist.P[i] != "" {
|
if i < len(detail.Panlist.P) && detail.Panlist.P[i] != "" {
|
||||||
password = detail.Panlist.P[i]
|
password = detail.Panlist.P[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从URL提取提取码(优先)
|
// 从URL提取提取码(优先)
|
||||||
if urlPwd := p.extractPasswordFromURL(linkURL); urlPwd != "" {
|
if urlPwd := p.extractPasswordFromURL(linkURL); urlPwd != "" {
|
||||||
password = urlPwd
|
password = urlPwd
|
||||||
@@ -1984,9 +2006,9 @@ func (p *GyingPlugin) determineLinkType(linkURL string) string {
|
|||||||
return "tianyi"
|
return "tianyi"
|
||||||
case strings.Contains(linkURL, "115.com") || strings.Contains(linkURL, "115cdn.com") || strings.Contains(linkURL, "anxia.com"):
|
case strings.Contains(linkURL, "115.com") || strings.Contains(linkURL, "115cdn.com") || strings.Contains(linkURL, "anxia.com"):
|
||||||
return "115"
|
return "115"
|
||||||
case strings.Contains(linkURL, "123684.com") || strings.Contains(linkURL, "123685.com") ||
|
case strings.Contains(linkURL, "123684.com") || strings.Contains(linkURL, "123685.com") ||
|
||||||
strings.Contains(linkURL, "123912.com") || strings.Contains(linkURL, "123pan.com") ||
|
strings.Contains(linkURL, "123912.com") || strings.Contains(linkURL, "123pan.com") ||
|
||||||
strings.Contains(linkURL, "123pan.cn") || strings.Contains(linkURL, "123592.com"):
|
strings.Contains(linkURL, "123pan.cn") || strings.Contains(linkURL, "123592.com"):
|
||||||
return "123"
|
return "123"
|
||||||
default:
|
default:
|
||||||
return "others"
|
return "others"
|
||||||
@@ -2002,7 +2024,7 @@ func (p *GyingPlugin) extractPasswordFromURL(linkURL string) string {
|
|||||||
return matches[1]
|
return matches[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 115网盘: ?password=xxxx
|
// 115网盘: ?password=xxxx
|
||||||
if strings.Contains(linkURL, "?password=") {
|
if strings.Contains(linkURL, "?password=") {
|
||||||
re := regexp.MustCompile(`\?password=([a-zA-Z0-9]+)`)
|
re := regexp.MustCompile(`\?password=([a-zA-Z0-9]+)`)
|
||||||
@@ -2149,10 +2171,10 @@ func decryptCookie(encrypted string) (string, error) {
|
|||||||
func (p *GyingPlugin) startSessionKeepAlive() {
|
func (p *GyingPlugin) startSessionKeepAlive() {
|
||||||
// 首次启动后延迟3分钟再开始(避免启动时过多请求)
|
// 首次启动后延迟3分钟再开始(避免启动时过多请求)
|
||||||
time.Sleep(3 * time.Minute)
|
time.Sleep(3 * time.Minute)
|
||||||
|
|
||||||
// 立即执行一次保活
|
// 立即执行一次保活
|
||||||
p.keepAllSessionsAlive()
|
p.keepAllSessionsAlive()
|
||||||
|
|
||||||
// 每3分钟执行一次保活
|
// 每3分钟执行一次保活
|
||||||
ticker := time.NewTicker(3 * time.Minute)
|
ticker := time.NewTicker(3 * time.Minute)
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
@@ -2163,26 +2185,26 @@ func (p *GyingPlugin) startSessionKeepAlive() {
|
|||||||
// keepAllSessionsAlive 保持所有用户的session活跃
|
// keepAllSessionsAlive 保持所有用户的session活跃
|
||||||
func (p *GyingPlugin) keepAllSessionsAlive() {
|
func (p *GyingPlugin) keepAllSessionsAlive() {
|
||||||
count := 0
|
count := 0
|
||||||
|
|
||||||
p.users.Range(func(key, value interface{}) bool {
|
p.users.Range(func(key, value interface{}) bool {
|
||||||
user := value.(*User)
|
user := value.(*User)
|
||||||
|
|
||||||
// 只为active状态的用户保活
|
// 只为active状态的用户保活
|
||||||
if user.Status != "active" {
|
if user.Status != "active" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取scraper实例
|
// 获取scraper实例
|
||||||
scraperVal, exists := p.scrapers.Load(user.Hash)
|
scraperVal, exists := p.scrapers.Load(user.Hash)
|
||||||
if !exists {
|
if !exists {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
scraper, ok := scraperVal.(*cloudscraper.Scraper)
|
scraper, ok := scraperVal.(*cloudscraper.Scraper)
|
||||||
if !ok || scraper == nil {
|
if !ok || scraper == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 访问首页保持session活跃
|
// 访问首页保持session活跃
|
||||||
go func(s *cloudscraper.Scraper, username string) {
|
go func(s *cloudscraper.Scraper, username string) {
|
||||||
resp, err := s.Get("https://www.gying.net/")
|
resp, err := s.Get("https://www.gying.net/")
|
||||||
@@ -2193,11 +2215,11 @@ func (p *GyingPlugin) keepAllSessionsAlive() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(scraper, user.UsernameMasked)
|
}(scraper, user.UsernameMasked)
|
||||||
|
|
||||||
count++
|
count++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
if DebugLog && count > 0 {
|
if DebugLog && count > 0 {
|
||||||
fmt.Printf("[Gying] 💓 已为 %d 个用户执行session保活\n", count)
|
fmt.Printf("[Gying] 💓 已为 %d 个用户执行session保活\n", count)
|
||||||
}
|
}
|
||||||
@@ -2255,3 +2277,4 @@ func (p *GyingPlugin) markInactiveUsers() int {
|
|||||||
|
|
||||||
return markedCount
|
return markedCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user