fix(eh): 自动放行 EH 的 Content Warning 拦截页
现象:部分画廊(多被标成 Offensive For Everyone)解析必然失败,报 「页面结构无法解析(EH 可能又改版)」——这条报错是误导的,跟改版无关。 根因:EH 对这类画廊第一次访问只返回一个 3~4KB 的黄框拦截页, <title> 和 meta 都在,但一个 /s/ 链接都没有,正则自然一条都匹配不上。 放行需要请求一次 ?nw=always,而 EH 靠下发的 nw=1 Cookie 记住 「别再警告」——可 Client 根本没设 CookieJar,Cookie 下发即丢, 于是永远过不去。 实测:4166903 / 4021921 / 3799635 / 3627469 四个画廊修复后 分别解析出 45/95/36/73 页,与站点声明完全一致。 改动: - Client 持有 cookiejar 并挂到 http.Client.Jar (Go 1.26 起 http.Transport 已经没有 Jar 字段了,只能挂 Client) - 新增 getTextAutoWarn:撞上拦截页自动 ?nw=always 放行一次再重试, nw=1 进 jar 后全站不再警告 - 放行失败时的报错明说是内容警告,并提示去浏览器点一次, 不再甩锅给「EH 可能又改版」
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
package eh
|
||||
|
||||
// 回归测试:EH「Content Warning」拦截页(2026-10 线上问题)
|
||||
//
|
||||
// 现象:部分画廊(多被标成 Offensive For Everyone)第一次访问只返回
|
||||
// 一个 3~4KB 的黄框页 —— <title> 和 meta 都在,但一个 /s/ 链接都没有,
|
||||
// 旧代码因此报「页面结构无法解析(EH 可能又改版)」,纯属误导。
|
||||
//
|
||||
// 放行方式:请求一次 ?nw=always,EH 会种下 nw=1 Cookie。
|
||||
// 关键是这个 Cookie 必须被记住,所以 Client 必须带 cookie jar。
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// warnPage 复刻 EH 的内容警告页(关键特征:没有 /s/ 链接、没有 taglist)
|
||||
func warnPage(gid, token string) string {
|
||||
return fmt.Sprintf(`<!DOCTYPE html><html><head>
|
||||
<title>[Some Circle] Flagged Gallery - E-Hentai Galleries</title>
|
||||
<meta name="description" content="Free Hentai Doujinshi Gallery: [Some Circle] Flagged Gallery" />
|
||||
</head><body>
|
||||
<div style="background:#FF6; width:800px; margin:100px auto">
|
||||
<h1>Content Warning</h1>
|
||||
<p>This gallery has been flagged as <strong>Offensive For Everyone</strong>.</p>
|
||||
<p>[<a href="%s/g/%s/%s/?nw=session">View Gallery</a>]</p>
|
||||
<p>[<a href="%s/g/%s/%s/?nw=always">Never Warn Me Again</a>]</p>
|
||||
</div></body></html>`, EHBase, gid, token, EHBase, gid, token)
|
||||
}
|
||||
|
||||
// 拦截页 + nw=always 放行(真实行为:带 nw=always 的请求会被种下 nw=1)
|
||||
func TestFetchGalleryPageContentWarningAutoBypass(t *testing.T) {
|
||||
const (
|
||||
gid = "4166903"
|
||||
tok = "97d35eebb2"
|
||||
)
|
||||
|
||||
real := mockGalleryHTML(t, gid, tok, 25)[0]
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/g/"+gid+"/"+tok+"/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// 真实 EH:看到 nw=always 就种下 nw=1 Cookie,然后进画廊
|
||||
if r.URL.Query().Get("nw") == "always" {
|
||||
http.SetCookie(w, &http.Cookie{Name: "nw", Value: "1", Path: "/"})
|
||||
w.Write([]byte(real))
|
||||
return
|
||||
}
|
||||
c, err := r.Cookie("nw")
|
||||
if err != nil || c.Value != "1" {
|
||||
w.Write([]byte(warnPage(gid, tok))) // 还没放行 -> 拦截页
|
||||
return
|
||||
}
|
||||
w.Write([]byte(real)) // 已放行 -> 真正的画廊页
|
||||
})
|
||||
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
oldBase := EHBase
|
||||
EHBase = srv.URL
|
||||
defer func() { EHBase = oldBase }()
|
||||
|
||||
c := NewClient(5*time.Second, "", "off", "auto")
|
||||
defer c.Close()
|
||||
|
||||
g, err := c.FetchGalleryPage(context.Background(), gid, tok, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("撞到内容警告页应该自动放行,不该失败: %v", err)
|
||||
}
|
||||
if len(g.Pages) == 0 {
|
||||
t.Fatal("自动放行后应至少解析出 1 页")
|
||||
}
|
||||
if len(g.Pages) != 20 {
|
||||
t.Errorf("第 1 屏应解析出 20 页,实际 %d", len(g.Pages))
|
||||
}
|
||||
t.Logf("自动放行成功,解析出 %d 页(总 %d)", len(g.Pages), g.TotalPages)
|
||||
}
|
||||
|
||||
// 只在 jar 缺失时才会失败——所以单独锁住「Client 必须带 jar」
|
||||
func TestClientHasCookieJar(t *testing.T) {
|
||||
c := NewClient(5*time.Second, "", "off", "auto")
|
||||
defer c.Close()
|
||||
if c.httpClient(0).Jar == nil {
|
||||
t.Fatal("http.Client 没有 cookie jar —— EH 的 nw=1 记不住,内容警告页永远过不去")
|
||||
}
|
||||
}
|
||||
|
||||
// 拦截页里识别不出来时的报错文案,不能再说「EH 可能又改版」了
|
||||
func TestContentWarningDetection(t *testing.T) {
|
||||
if !isContentWarning(warnPage("1", "aa")) {
|
||||
t.Error("应识别出内容警告页")
|
||||
}
|
||||
if !isContentWarning(`<h1>Content Warning</h1>`) {
|
||||
t.Error("大小写/空白差异也应识别")
|
||||
}
|
||||
if isContentWarning(`<html><body><a href="/s/k/1-1">thumb</a></body></html>`) {
|
||||
t.Error("正常画廊页不该被误判成警告页")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContentWarningErrorMessage(t *testing.T) {
|
||||
const (
|
||||
gid = "4999999"
|
||||
tok = "deadbeef01"
|
||||
)
|
||||
// 永远只返回拦截页:即使放行也拿不到内容
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/g/"+gid+"/"+tok+"/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(warnPage(gid, tok)))
|
||||
})
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
oldBase := EHBase
|
||||
EHBase = srv.URL
|
||||
defer func() { EHBase = oldBase }()
|
||||
|
||||
c := NewClient(5*time.Second, "", "off", "auto")
|
||||
defer c.Close()
|
||||
|
||||
_, err := c.FetchGalleryPage(context.Background(), gid, tok, nil)
|
||||
if err == nil {
|
||||
t.Fatal("应当失败")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Offensive For Everyone") {
|
||||
t.Errorf("报错应说明是内容警告,实际: %v", err)
|
||||
}
|
||||
if strings.Contains(err.Error(), "改版") {
|
||||
t.Errorf("报错不该再提「改版」,那会误导排查: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user