mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-03 20:26:36 +08:00
开发
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// 本文件:书源会话状态(变量 / 登录信息 / 登录请求头 / Cookie)的抽象。
|
||||
// 对应 legado BaseSource 的 getVariable/putVariable/getLoginInfo/putLoginInfo/
|
||||
// getLoginHeader/putLoginHeader 与 CookieStore。实现由服务层落库提供;
|
||||
// 未注入时退回进程内 MemoryState(单测、冒烟 CLI 用)。
|
||||
|
||||
// SourceState 书源会话状态读写接口。
|
||||
type SourceState interface {
|
||||
// GetVariable / SetVariable 对应 source.getVariable / source.setVariable。
|
||||
GetVariable() string
|
||||
SetVariable(v string)
|
||||
|
||||
// GetLoginInfo / SetLoginInfo 对应 source.getLoginInfo / source.putLoginInfo。
|
||||
// 内容为登录表单的 JSON(键为字段名)。
|
||||
GetLoginInfo() string
|
||||
SetLoginInfo(v string)
|
||||
|
||||
// GetLoginHeader / SetLoginHeader 对应 source 的登录请求头(JSON),
|
||||
// 其中 Cookie 键会在请求时合并进 Cookie 头。
|
||||
GetLoginHeader() string
|
||||
SetLoginHeader(v string)
|
||||
|
||||
// Cookie 读写:rawURL 可为完整 URL 或裸域名,按有效顶级域+1 归并。
|
||||
GetCookie(rawURL string) string
|
||||
GetCookieKey(rawURL, key string) string
|
||||
SetCookie(rawURL, cookie string)
|
||||
RemoveCookie(rawURL string)
|
||||
|
||||
// Toast 收集宿主提示(java.toast / java.longToast),登录反馈靠它回传前端。
|
||||
Toast(msg string)
|
||||
// OpenBrowser 记录需要浏览器完成的地址(java.startBrowser)——
|
||||
// 服务端无法弹窗,前端据此提供「在新标签打开」。
|
||||
OpenBrowser(url, title string)
|
||||
}
|
||||
|
||||
// CookieDomain 取 URL 的有效顶级域 +1(对应 legado NetworkUtils.getSubDomain)。
|
||||
// 裸域名(无 scheme)按 http 处理;IP 原样返回;解析失败回退 host 本身。
|
||||
func CookieDomain(rawURL string) string {
|
||||
s := strings.TrimSpace(rawURL)
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
if !strings.Contains(s, "://") {
|
||||
if strings.HasPrefix(s, "//") {
|
||||
s = "http:" + s
|
||||
} else {
|
||||
s = "http://" + s
|
||||
}
|
||||
}
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
host := u.Hostname()
|
||||
if host == "" {
|
||||
return ""
|
||||
}
|
||||
if net.ParseIP(host) != nil {
|
||||
return host
|
||||
}
|
||||
etld1, err := publicsuffix.EffectiveTLDPlusOne(host)
|
||||
if err != nil {
|
||||
return host
|
||||
}
|
||||
return etld1
|
||||
}
|
||||
|
||||
// ParseCookie 把 "a=1; b=2" 拆为键值对(值不 Trim 内部空白,仅去首尾)。
|
||||
func ParseCookie(cookie string) map[string]string {
|
||||
out := map[string]string{}
|
||||
for _, part := range strings.Split(cookie, ";") {
|
||||
kv := strings.SplitN(strings.TrimSpace(part), "=", 2)
|
||||
if len(kv) != 2 || kv[0] == "" {
|
||||
continue
|
||||
}
|
||||
out[kv[0]] = kv[1]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// MergeCookie 将新 cookie 覆盖式合并进旧 cookie(对应 CookieStore.replaceCookie)。
|
||||
func MergeCookie(old, newCookie string) string {
|
||||
merged := ParseCookie(old)
|
||||
for k, v := range ParseCookie(newCookie) {
|
||||
merged[k] = v
|
||||
}
|
||||
return formatCookie(merged)
|
||||
}
|
||||
|
||||
func formatCookie(kv map[string]string) string {
|
||||
parts := make([]string, 0, len(kv))
|
||||
for k, v := range kv {
|
||||
parts = append(parts, k+"="+v)
|
||||
}
|
||||
return strings.Join(parts, "; ")
|
||||
}
|
||||
|
||||
// MemoryState 进程内会话状态(未注入持久化实现时的回退)。
|
||||
type MemoryState struct {
|
||||
mu sync.Mutex
|
||||
variable string
|
||||
loginInfo string
|
||||
loginHeader string
|
||||
cookies map[string]map[string]string // domain → name → value
|
||||
toasts []string
|
||||
browsers []BrowserRequest
|
||||
}
|
||||
|
||||
// BrowserRequest 前端可代为打开的浏览器地址(java.startBrowser 收集)。
|
||||
type BrowserRequest struct {
|
||||
URL string `json:"url"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// NewMemoryState 创建进程内状态。
|
||||
func NewMemoryState() *MemoryState {
|
||||
return &MemoryState{cookies: map[string]map[string]string{}}
|
||||
}
|
||||
|
||||
func (m *MemoryState) GetVariable() string { m.mu.Lock(); defer m.mu.Unlock(); return m.variable }
|
||||
func (m *MemoryState) SetVariable(v string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.variable = v
|
||||
}
|
||||
|
||||
func (m *MemoryState) GetLoginInfo() string { m.mu.Lock(); defer m.mu.Unlock(); return m.loginInfo }
|
||||
func (m *MemoryState) SetLoginInfo(v string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.loginInfo = v
|
||||
}
|
||||
|
||||
func (m *MemoryState) GetLoginHeader() string { m.mu.Lock(); defer m.mu.Unlock(); return m.loginHeader }
|
||||
func (m *MemoryState) SetLoginHeader(v string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.loginHeader = v
|
||||
}
|
||||
|
||||
func (m *MemoryState) GetCookie(rawURL string) string {
|
||||
return m.GetCookieKey(rawURL, "")
|
||||
}
|
||||
|
||||
func (m *MemoryState) GetCookieKey(rawURL, key string) string {
|
||||
domain := CookieDomain(rawURL)
|
||||
if domain == "" {
|
||||
return ""
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
jar, ok := m.cookies[domain]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if key != "" {
|
||||
return jar[key]
|
||||
}
|
||||
return formatCookie(jar)
|
||||
}
|
||||
|
||||
func (m *MemoryState) SetCookie(rawURL, cookie string) {
|
||||
domain := CookieDomain(rawURL)
|
||||
if domain == "" || strings.TrimSpace(cookie) == "" {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
jar, ok := m.cookies[domain]
|
||||
if !ok {
|
||||
jar = map[string]string{}
|
||||
m.cookies[domain] = jar
|
||||
}
|
||||
for k, v := range ParseCookie(cookie) {
|
||||
jar[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MemoryState) RemoveCookie(rawURL string) {
|
||||
domain := CookieDomain(rawURL)
|
||||
if domain == "" {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
delete(m.cookies, domain)
|
||||
}
|
||||
|
||||
func (m *MemoryState) Toast(msg string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.toasts = append(m.toasts, msg)
|
||||
}
|
||||
|
||||
func (m *MemoryState) OpenBrowser(url, title string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.browsers = append(m.browsers, BrowserRequest{URL: url, Title: title})
|
||||
}
|
||||
|
||||
// Toasts 返回并清空已收集的宿主提示。
|
||||
func (m *MemoryState) Toasts() []string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
out := m.toasts
|
||||
m.toasts = nil
|
||||
return out
|
||||
}
|
||||
|
||||
// Browsers 返回并清空已收集的浏览器地址。
|
||||
func (m *MemoryState) Browsers() []BrowserRequest {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
out := m.browsers
|
||||
m.browsers = nil
|
||||
return out
|
||||
}
|
||||
|
||||
// Snapshot 返回当前 Cookie(domain → cookie 串)副本。
|
||||
func (m *MemoryState) Snapshot() map[string]string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
out := make(map[string]string, len(m.cookies))
|
||||
for d, jar := range m.cookies {
|
||||
out[d] = formatCookie(jar)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var _ SourceState = (*MemoryState)(nil)
|
||||
Reference in New Issue
Block a user