fix(eh): 标题不再带 " - Tags",并附批量改名脚本
meta description 的真实格式是 "Free Hentai Misc Gallery: <title> - Tags: a, b, c" 旧正则 `Gallery:\s*([^:]+):` 里的 [^:]+ 是贪婪的, 一路吃到标签段自己的冒号为止,把 " - Tags" 也吞进了标题, 导致下载目录/文件名结尾凭空多出 " - Tags"。 改为显式认 " - Tags:" 这个分隔符(Tags 段可选,无标签时认行尾), reTitleSuffix 同时认 Tags 结尾,提取后再兜底剥一次。 线上实测(g/4239272):标题由 "Hinata l Boruto (CriticalHAI) [AI Generated] - Tags" 变为 "Hinata l Boruto (CriticalHAI) [AI Generated]" 附带 remove-tags-suffix.bat/.ps1:递归去掉已有压缩包文件名结尾的 " - Tags",覆盖 zip/rar/7z/cbz/cbr/cb7/tar/gz,幂等。
This commit is contained in:
+8
-3
@@ -156,7 +156,7 @@ type Gallery struct {
|
|||||||
var (
|
var (
|
||||||
reTitleTag = regexp.MustCompile(`(?is)<title>(.*?)</title>`)
|
reTitleTag = regexp.MustCompile(`(?is)<title>(.*?)</title>`)
|
||||||
reMetaDesc = regexp.MustCompile(`(?i)<meta\s+name="description"\s+content="([^"]*)"`)
|
reMetaDesc = regexp.MustCompile(`(?i)<meta\s+name="description"\s+content="([^"]*)"`)
|
||||||
reTitleSuffix = regexp.MustCompile(`(?i)\s*-\s*E-Hentai Galleries\s*$`)
|
reTitleSuffix = regexp.MustCompile(`(?i)\s*-\s*(?:E-Hentai Galleries|Tags)\s*$`)
|
||||||
reTotal = regexp.MustCompile(`(?i)Showing\s+\d+\s*-\s*\d+\s+of\s+(\d+)\s+images`)
|
reTotal = regexp.MustCompile(`(?i)Showing\s+\d+\s*-\s*\d+\s+of\s+(\d+)\s+images`)
|
||||||
// <a href="/s/xxx"><div title="Page 1: 原名"
|
// <a href="/s/xxx"><div title="Page 1: 原名"
|
||||||
reThumbLink = regexp.MustCompile(`(?is)<a\s+href="((?:https://e-hentai\.org)?/s/[^"]+)"[^>]*>\s*<div\s+title="Page\s+(\d+):\s*([^"]*)"`)
|
reThumbLink = regexp.MustCompile(`(?is)<a\s+href="((?:https://e-hentai\.org)?/s/[^"]+)"[^>]*>\s*<div\s+title="Page\s+(\d+):\s*([^"]*)"`)
|
||||||
@@ -164,7 +164,11 @@ var (
|
|||||||
reArtistBracket = regexp.MustCompile(`^\s*\[([^\]]{1,60})\]\s*`)
|
reArtistBracket = regexp.MustCompile(`^\s*\[([^\]]{1,60})\]\s*`)
|
||||||
reGroupParen = regexp.MustCompile(`\(([^)]{1,60})\)`)
|
reGroupParen = regexp.MustCompile(`\(([^)]{1,60})\)`)
|
||||||
reCatFromDesc = regexp.MustCompile(`(?i)Hentai\s+([A-Za-z]+)\s+Gallery`)
|
reCatFromDesc = regexp.MustCompile(`(?i)Hentai\s+([A-Za-z]+)\s+Gallery`)
|
||||||
reTitleFromDesc = regexp.MustCompile(`Gallery:\s*([^:]+):`)
|
// meta description 形如:
|
||||||
|
// "Free Hentai Misc Gallery: <title> - Tags: a, b, c"
|
||||||
|
// 标题的结束边界优先认 " - Tags:"(标签段);没有 Tags 段时认行尾。
|
||||||
|
// ⚠️ 别用 `Gallery:\s*([^:]+):` —— 那个会把 " - Tags" 一起吞进标题。
|
||||||
|
reTitleFromDesc = regexp.MustCompile(`(?is)Gallery:\s*(.*?)(?:\s*-\s*Tags\s*:|\s*$|[.:]\s+\d+\s+images\b)`)
|
||||||
reRestricted = regexp.MustCompile(`(?i)Login Required|restricted|Sad Panda`)
|
reRestricted = regexp.MustCompile(`(?i)Login Required|restricted|Sad Panda`)
|
||||||
|
|
||||||
// #taglist 的每一行:<td class="tc">artist:</td><td><div class="gtl"><a ...>Noxe</a>...</td>
|
// #taglist 的每一行:<td class="tc">artist:</td><td><div class="gtl"><a ...>Noxe</a>...</td>
|
||||||
@@ -545,7 +549,8 @@ func (c *Client) FetchGalleryPage(ctx context.Context, gid, token string, onProg
|
|||||||
}
|
}
|
||||||
if m := reMetaDesc.FindStringSubmatch(html); m != nil {
|
if m := reMetaDesc.FindStringSubmatch(html); m != nil {
|
||||||
if nm := reTitleFromDesc.FindStringSubmatch(DecodeEntities(m[1])); nm != nil {
|
if nm := reTitleFromDesc.FindStringSubmatch(DecodeEntities(m[1])); nm != nil {
|
||||||
title = strings.TrimSpace(nm[1])
|
// 保险:再剥一次尾部 " - Tags"(有些页面 desc 里 Tags 前无空格)
|
||||||
|
title = strings.TrimSpace(reTitleSuffix.ReplaceAllString(nm[1], ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if m := reTotal.FindStringSubmatch(html); m != nil {
|
if m := reTotal.FindStringSubmatch(html); m != nil {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package eh
|
||||||
|
|
||||||
|
// 回归测试:文件名尾巴多出 " - Tags"(2026-10 线上问题)
|
||||||
|
//
|
||||||
|
// 现象:下载下来的目录/压缩包名全是 "... - Tags",原本标题没有这个。
|
||||||
|
//
|
||||||
|
// 根因:meta description 的真实格式是
|
||||||
|
// "Free Hentai Misc Gallery: <title> - Tags: a, b, c"
|
||||||
|
// 旧正则 `Gallery:\s*([^:]+):` 里的 `[^:]+` 是贪婪的,它一路吃到
|
||||||
|
// 标签段自己的那个冒号为止,于是把 " - Tags" 也当成标题的一部分。
|
||||||
|
//
|
||||||
|
// 正确做法:标题的结束边界就是 " - Tags:",必须显式认这个分隔符。
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestTitleFromDescStripsTags(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
desc string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "带 Tags 段(线上真实样本)",
|
||||||
|
desc: "Free Hentai Misc Gallery: Hinata l Boruto (CriticalHAI) [AI Generated] - Tags: boruto, hinata hyuga, sole female, ai generated",
|
||||||
|
want: "Hinata l Boruto (CriticalHAI) [AI Generated]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "无 Tags 段",
|
||||||
|
desc: "Free Hentai Doujinshi Gallery: [Some Circle] Flagged Gallery",
|
||||||
|
want: "[Some Circle] Flagged Gallery",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Tags 前多空格",
|
||||||
|
desc: "Free Hentai Manga Gallery: Some Title - Tags: a, b",
|
||||||
|
want: "Some Title",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "标题本身含冒号(竖线译名等)",
|
||||||
|
desc: "Free Hentai Misc Gallery: [A] Orig | 译名 - Tags: x",
|
||||||
|
want: "[A] Orig | 译名",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "标题本身以 Tags 结尾也不能被剥",
|
||||||
|
desc: "Free Hentai Misc Gallery: Study About Tags - Tags: a",
|
||||||
|
want: "Study About Tags",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
m := reTitleFromDesc.FindStringSubmatch(tt.desc)
|
||||||
|
if m == nil {
|
||||||
|
t.Fatalf("没匹配到标题: %q", tt.desc)
|
||||||
|
}
|
||||||
|
got := reTitleSuffix.ReplaceAllString(m[1], "")
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("标题不对:\n got %q\nwant %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTitleTagStripsTagsSuffix(t *testing.T) {
|
||||||
|
// <title> 一般是 "... - E-Hentai Galleries",但也可能直接是 "... - Tags"
|
||||||
|
m := reTitleTag.FindStringSubmatch("<title>Some Gallery - Tags</title>")
|
||||||
|
if m == nil {
|
||||||
|
t.Fatal("没取到 <title>")
|
||||||
|
}
|
||||||
|
if got := reTitleSuffix.ReplaceAllString(m[1], ""); got != "Some Gallery" {
|
||||||
|
t.Errorf("title 去 Tags 失败: %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0remove-tags-suffix.ps1"
|
||||||
|
pause
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<#
|
||||||
|
去掉压缩包文件名结尾的 " - Tags"
|
||||||
|
递归遍历「脚本所在目录」下所有层级。
|
||||||
|
支持扩展名: .zip .rar .7z .cbz .cbr .cb7 .tar .gz
|
||||||
|
#>
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
|
$root = $PSScriptRoot
|
||||||
|
$suffix = ' - Tags'
|
||||||
|
$arcExts = @('.zip', '.rar', '.7z', '.cbz', '.cbr', '.cb7', '.tar', '.gz')
|
||||||
|
|
||||||
|
Write-Host "正在递归扫描: $root" -ForegroundColor Cyan
|
||||||
|
Write-Host ''
|
||||||
|
|
||||||
|
$found = 0
|
||||||
|
$done = 0
|
||||||
|
$fail = 0
|
||||||
|
|
||||||
|
$files = Get-ChildItem -LiteralPath $root -Recurse -File -Force -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $arcExts -contains $_.Extension.ToLower() }
|
||||||
|
|
||||||
|
foreach ($f in $files) {
|
||||||
|
$name = $f.Name
|
||||||
|
$base = [System.IO.Path]::GetFileNameWithoutExtension($name)
|
||||||
|
$ext = $f.Extension
|
||||||
|
|
||||||
|
# ⚠️ 必须对「去掉扩展名后的名字」判断。
|
||||||
|
# 直接拿 "xxx - Tags.zip" 去 EndsWith(" - Tags") 永远为 False。
|
||||||
|
if (-not $base.ToLower().EndsWith($suffix.ToLower())) { continue }
|
||||||
|
|
||||||
|
$newBase = $base.Substring(0, $base.Length - $suffix.Length).TrimEnd(' ', '.')
|
||||||
|
|
||||||
|
# 防止改名后变成空名 / 非法名
|
||||||
|
if ([string]::IsNullOrWhiteSpace($newBase)) {
|
||||||
|
Write-Host " [跳过] 去掉后缀后名字为空: $name" -ForegroundColor Yellow
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($newBase -match '[\\/:*?"<>|]') {
|
||||||
|
Write-Host " [跳过] 新名字含非法字符: $newBase" -ForegroundColor Yellow
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$newName = $newBase + $ext
|
||||||
|
if ($newName -eq $name) { continue }
|
||||||
|
|
||||||
|
$found++
|
||||||
|
Write-Host " [找到] $name"
|
||||||
|
Write-Host " -> $newName" -ForegroundColor DarkGray
|
||||||
|
|
||||||
|
$target = Join-Path $f.DirectoryName $newName
|
||||||
|
try {
|
||||||
|
Rename-Item -LiteralPath $f.FullName -NewName $newName -ErrorAction Stop
|
||||||
|
Write-Host " [完成]" -ForegroundColor Green
|
||||||
|
$done++
|
||||||
|
} catch {
|
||||||
|
Write-Host " [失败] $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
$fail++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host '============================================' -ForegroundColor Cyan
|
||||||
|
Write-Host "完成:命中 $found 个,成功 $done 个,失败 $fail 个" -ForegroundColor Cyan
|
||||||
|
Write-Host '============================================' -ForegroundColor Cyan
|
||||||
Reference in New Issue
Block a user