mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-08 14:26:37 +08:00
fix nexusphp search and library labels
This commit is contained in:
@@ -217,14 +217,14 @@ func nexusPHPTorrentRows(pageHTML string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func firstNexusPHPLink(row, path string) *nexusPHPLink {
|
func firstNexusPHPLink(row, path string) *nexusPHPLink {
|
||||||
pattern := regexp.MustCompile(`(?is)<a\b([^>]*href\s*=\s*["']([^"']*` + regexp.QuoteMeta(path) + `[^"']*)["'][^>]*)>(.*?)</a>`)
|
pattern := regexp.MustCompile(`(?is)<a\b([^>]*href\s*=\s*["']([^"']*)["'][^>]*)>(.*?)</a>`)
|
||||||
for _, match := range pattern.FindAllStringSubmatch(row, -1) {
|
for _, match := range pattern.FindAllStringSubmatch(row, -1) {
|
||||||
if len(match) < 4 {
|
if len(match) < 4 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
href := html.UnescapeString(strings.TrimSpace(match[2]))
|
href := html.UnescapeString(strings.TrimSpace(match[2]))
|
||||||
parsed, err := url.Parse(href)
|
parsed, err := url.Parse(href)
|
||||||
if err != nil {
|
if err != nil || !nexusPHPLinkPathMatches(parsed, path) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return &nexusPHPLink{
|
return &nexusPHPLink{
|
||||||
@@ -237,6 +237,22 @@ func firstNexusPHPLink(row, path string) *nexusPHPLink {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nexusPHPLinkPathMatches(parsed *url.URL, want string) bool {
|
||||||
|
if parsed == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
path := strings.TrimSpace(parsed.Path)
|
||||||
|
if path == "" {
|
||||||
|
path = strings.TrimSpace(parsed.Opaque)
|
||||||
|
}
|
||||||
|
path = strings.Trim(strings.ToLower(path), "/")
|
||||||
|
want = strings.Trim(strings.ToLower(strings.TrimSpace(want)), "/")
|
||||||
|
if path == "" || want == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return path == want || strings.HasSuffix(path, "/"+want)
|
||||||
|
}
|
||||||
|
|
||||||
func nexusPHPTitleFromLink(link nexusPHPLink) string {
|
func nexusPHPTitleFromLink(link nexusPHPLink) string {
|
||||||
for _, attr := range []string{"title", "data-title"} {
|
for _, attr := range []string{"title", "data-title"} {
|
||||||
if value := htmlAttr(link.attrs, attr); value != "" {
|
if value := htmlAttr(link.attrs, attr); value != "" {
|
||||||
|
|||||||
@@ -247,6 +247,24 @@ func TestParseNexusPHPHTMLModernRows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseNexusPHPHTMLIgnoresUserDetailsLinks(t *testing.T) {
|
||||||
|
page := `
|
||||||
|
<table>
|
||||||
|
<tr><td><a href="userdetails.php?id=31044">shukBeta</a></td><td>15.5 GiB</td></tr>
|
||||||
|
<tr><td><a href="/details.php?id=789" title="问心 S01 1080p">问心</a><a href="/download.php?id=789">下载</a></td><td>1.5 GiB</td></tr>
|
||||||
|
</table>`
|
||||||
|
result, err := parseNexusPHPHTML(page, "Nexus", "https://pt.example")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(result.Items) != 1 {
|
||||||
|
t.Fatalf("items = %#v, want only real torrent details row", result.Items)
|
||||||
|
}
|
||||||
|
if result.Items[0].ID != "789" || result.Items[0].Title != "问心 S01 1080p" {
|
||||||
|
t.Fatalf("parsed item = %#v", result.Items[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMTeamAPIRateLimits(t *testing.T) {
|
func TestMTeamAPIRateLimits(t *testing.T) {
|
||||||
search := mteamAPIRateLimits(mteamAPIEndpointSearch)
|
search := mteamAPIRateLimits(mteamAPIEndpointSearch)
|
||||||
if len(search) != 1 || search[0].Limit != 1500 || search[0].Window != 24*time.Hour {
|
if len(search) != 1 || search[0].Limit != 1500 || search[0].Window != 24*time.Hour {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { imageURL } from '../api/client'
|
|||||||
import { EpisodeArtworkToggle } from '../components/EpisodeArtworkToggle'
|
import { EpisodeArtworkToggle } from '../components/EpisodeArtworkToggle'
|
||||||
import { MediaCard } from '../components/MediaCard'
|
import { MediaCard } from '../components/MediaCard'
|
||||||
import { artworkScore, seriesCardLink, type SeriesCard } from '../utils/groupSeries'
|
import { artworkScore, seriesCardLink, type SeriesCard } from '../utils/groupSeries'
|
||||||
|
import { libraryDisplayPath } from './libraryDisplayModel'
|
||||||
import { mediaTime, type LibraryPreview } from './librariesPageModel'
|
import { mediaTime, type LibraryPreview } from './librariesPageModel'
|
||||||
|
|
||||||
const TYPE_ICONS: Record<string, ReactNode> = {
|
const TYPE_ICONS: Record<string, ReactNode> = {
|
||||||
@@ -127,6 +128,7 @@ export function LibrariesContent({ previews }: { previews: LibraryPreview[] }) {
|
|||||||
function LibraryEntryCard({ preview }: { preview: LibraryPreview }) {
|
function LibraryEntryCard({ preview }: { preview: LibraryPreview }) {
|
||||||
const library = preview.library
|
const library = preview.library
|
||||||
const artwork = libraryArtworkItems(preview.cards)
|
const artwork = libraryArtworkItems(preview.cards)
|
||||||
|
const displayPath = libraryDisplayPath(library.path)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
@@ -160,7 +162,9 @@ function LibraryEntryCard({ preview }: { preview: LibraryPreview }) {
|
|||||||
<h2 className="truncate font-display text-xl font-black text-ink-600 group-hover:text-brand-600">
|
<h2 className="truncate font-display text-xl font-black text-ink-600 group-hover:text-brand-600">
|
||||||
{library.name}
|
{library.name}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-1 line-clamp-1 break-all text-xs text-ink-50">{library.path}</p>
|
<p className="mt-1 line-clamp-1 break-all text-xs text-ink-50" title={library.path}>
|
||||||
|
{displayPath}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-xs font-bold">
|
<div className="flex items-center justify-between text-xs font-bold">
|
||||||
<span className="text-sand-600">{preview.total.toLocaleString()} 个条目</span>
|
<span className="text-sand-600">{preview.total.toLocaleString()} 个条目</span>
|
||||||
@@ -174,6 +178,7 @@ function LibraryEntryCard({ preview }: { preview: LibraryPreview }) {
|
|||||||
function LibraryShelf({ preview }: { preview: LibraryPreview }) {
|
function LibraryShelf({ preview }: { preview: LibraryPreview }) {
|
||||||
const library = preview.library
|
const library = preview.library
|
||||||
const cards = preview.cards.slice(0, 10)
|
const cards = preview.cards.slice(0, 10)
|
||||||
|
const displayPath = libraryDisplayPath(library.path)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="rounded-[1.7rem] border border-sand-200 bg-white/75 p-4 shadow-card">
|
<section className="rounded-[1.7rem] border border-sand-200 bg-white/75 p-4 shadow-card">
|
||||||
@@ -185,7 +190,7 @@ function LibraryShelf({ preview }: { preview: LibraryPreview }) {
|
|||||||
</div>
|
</div>
|
||||||
<h2 className="truncate font-display text-2xl font-black text-ink-600">{library.name}</h2>
|
<h2 className="truncate font-display text-2xl font-black text-ink-600">{library.name}</h2>
|
||||||
<p className="mt-1 line-clamp-1 break-all text-xs text-ink-50">
|
<p className="mt-1 line-clamp-1 break-all text-xs text-ink-50">
|
||||||
{library.path} · {preview.total.toLocaleString()} 个条目 · 最新 {cards.length} 部
|
<span title={library.path}>{displayPath}</span> · {preview.total.toLocaleString()} 个条目 · 最新 {cards.length} 部
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link to={`/library/${library.id}`} className="btn-outline shrink-0">
|
<Link to={`/library/${library.id}`} className="btn-outline shrink-0">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Library } from '../types'
|
import type { Library } from '../types'
|
||||||
import { EpisodeArtworkToggle } from '../components/EpisodeArtworkToggle'
|
import { EpisodeArtworkToggle } from '../components/EpisodeArtworkToggle'
|
||||||
|
import { libraryDisplayPath } from './libraryDisplayModel'
|
||||||
|
|
||||||
type LibraryPageHeaderProps = {
|
type LibraryPageHeaderProps = {
|
||||||
library: Library | null
|
library: Library | null
|
||||||
@@ -32,6 +33,8 @@ export function LibraryPageHeader({
|
|||||||
onScrape,
|
onScrape,
|
||||||
onRepairRescrape,
|
onRepairRescrape,
|
||||||
}: LibraryPageHeaderProps) {
|
}: LibraryPageHeaderProps) {
|
||||||
|
const displayPath = library ? libraryDisplayPath(library.path) : ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
@@ -39,7 +42,7 @@ export function LibraryPageHeader({
|
|||||||
{library?.name ?? '媒体库'}
|
{library?.name ?? '媒体库'}
|
||||||
<span className="text-sand-500"> ({itemCount})</span>
|
<span className="text-sand-500"> ({itemCount})</span>
|
||||||
</h1>
|
</h1>
|
||||||
{library && <p className="text-sm text-ink-50">{library.type} · {library.path}</p>}
|
{library && <p className="text-sm text-ink-50" title={library.path}>{library.type} · {displayPath}</p>}
|
||||||
{loadingAllText && <p className="mt-1 text-xs text-sand-500">{loadingAllText}</p>}
|
{loadingAllText && <p className="mt-1 text-xs text-sand-500">{loadingAllText}</p>}
|
||||||
{scanProgress && <p className="mt-1 text-xs text-brand-500">{scanProgress}</p>}
|
{scanProgress && <p className="mt-1 text-xs text-brand-500">{scanProgress}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { cloudLibraryLabel, cloudLibraryProvider, TYPE_LABEL } from './storageConfigModel'
|
||||||
|
|
||||||
|
export function libraryDisplayPath(path: string): string {
|
||||||
|
const provider = cloudLibraryProvider(path)
|
||||||
|
if (!provider) return path
|
||||||
|
const providerLabel = TYPE_LABEL[provider] || provider
|
||||||
|
return `${providerLabel} / ${cloudLibraryLabel(path)}`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user