mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 20:16:36 +08:00
241 lines
6.1 KiB
Go
241 lines
6.1 KiB
Go
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)
|