Skip to content

Commit 2c83882

Browse files
authored
feat: enhance AddDialog with poster search improvements (#671)
Signed-off-by: Pavel Pikta <devops@pavelpikta.com>
1 parent 61dd468 commit 2c83882

4 files changed

Lines changed: 92 additions & 10 deletions

File tree

web/src/components/Add/AddDialog.jsx

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useMemo, useState } from 'react'
1+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
import Button from '@material-ui/core/Button'
33
import { torrentsHost, torrentUploadHost } from 'utils/Hosts'
44
import axios from 'axios'
@@ -11,11 +11,18 @@ import usePreviousState from 'utils/usePreviousState'
1111
import { useQuery } from 'react-query'
1212
import { getTorrents } from 'utils/Utils'
1313
import parseTorrent from 'parse-torrent'
14+
import ptt from 'parse-torrent-title'
1415
import { ButtonWrapper } from 'style/DialogStyles'
1516
import { StyledDialog, StyledHeader } from 'style/CustomMaterialUiStyles'
1617
import useOnStandaloneAppOutsideClick from 'utils/useOnStandaloneAppOutsideClick'
1718

18-
import { checkImageURL, getMoviePosters, checkTorrentSource, parseTorrentTitle } from './helpers'
19+
import {
20+
checkImageURL,
21+
getMoviePosters,
22+
checkTorrentSource,
23+
parseTorrentTitle,
24+
shortenTitleForPosterSearch,
25+
} from './helpers'
1926
import { Content } from './style'
2027
import RightSideComponent from './RightSideComponent'
2128
import LeftSideComponent from './LeftSideComponent'
@@ -48,6 +55,7 @@ export default function AddDialog({
4855
const [skipDebounce, setSkipDebounce] = useState(false)
4956
const [isCustomTitleEnabled, setIsCustomTitleEnabled] = useState(false)
5057
const [currentSourceHash, setCurrentSourceHash] = useState()
58+
const editModePosterSearchedRef = useRef(false)
5159

5260
const ref = useOnStandaloneAppOutsideClick(handleClose)
5361

@@ -108,6 +116,20 @@ export default function AddDialog({
108116
setPosterUrl('')
109117
}
110118

119+
// Edit mode: init original/parsed title from name so poster can be searched
120+
useEffect(() => {
121+
if (!originalHash || (!originalName && !originalTitle)) return
122+
const source = originalName || originalTitle
123+
setOriginalTorrentTitle(source)
124+
try {
125+
const parsed = ptt.parse(source)
126+
setParsedTitle(parsed?.title || '')
127+
} catch (_) {
128+
setParsedTitle('')
129+
}
130+
editModePosterSearchedRef.current = false
131+
}, [originalHash, originalName, originalTitle])
132+
111133
useEffect(() => {
112134
if (originalHash) {
113135
checkImageURL(posterUrl).then(correctImage => {
@@ -126,8 +148,9 @@ export default function AddDialog({
126148
removePoster()
127149
return
128150
}
151+
const query = shortenTitleForPosterSearch(String(movieName).trim())
129152

130-
getMoviePosters(movieName, language).then(urlList => {
153+
getMoviePosters(query || movieName, language).then(urlList => {
131154
if (urlList) {
132155
setPosterList(urlList)
133156
if (!shouldRefreshMainPoster && isUserInteractedWithPoster) return
@@ -167,6 +190,22 @@ export default function AddDialog({
167190
updateTitleFromSource()
168191
}, [prevTorrentSourceState, selectedFile, torrentSource, updateTitleFromSource])
169192

193+
// Edit mode: auto-search poster once when we have title and no poster
194+
useEffect(() => {
195+
if (
196+
!originalHash ||
197+
editModePosterSearchedRef.current ||
198+
originalPoster ||
199+
!(parsedTitle || originalTitle || title)
200+
) {
201+
return
202+
}
203+
const searchTitle = parsedTitle || title || originalTitle
204+
if (!shortenTitleForPosterSearch(searchTitle)) return
205+
editModePosterSearchedRef.current = true
206+
posterSearch(searchTitle, posterSearchLanguage, { shouldRefreshMainPoster: true })
207+
}, [originalHash, originalPoster, parsedTitle, originalTitle, title, posterSearchLanguage, posterSearch])
208+
170209
const prevTitleState = usePreviousState(title)
171210

172211
useEffect(() => {

web/src/components/Add/RightSideComponent.jsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,7 @@ export default function RightSideComponent({
237237

238238
<UpdatePosterButton
239239
onClick={() => {
240-
let fixedTitle = isCustomTitleEnabled ? title : originalTorrentTitle ? parsedTitle : title
241-
const titleFixedMatch = fixedTitle.replaceAll(/\./g, ' ').match(/^([\w -]+)/)
242-
if (titleFixedMatch?.length && titleFixedMatch[0].length > 0) {
243-
;[fixedTitle] = titleFixedMatch
244-
}
245-
240+
const fixedTitle = isCustomTitleEnabled ? title : originalTorrentTitle ? parsedTitle : title
246241
posterSearch(fixedTitle, posterSearchLanguage)
247242
}}
248243
color='primary'

web/src/components/Add/helpers.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,44 @@ export const checkTorrentSource = source =>
9191
source.match(linkRegex) !== null ||
9292
source.match(torrsRegex) !== null
9393

94+
/** Max length for TMDB/search API query; long torrent names exceed this. */
95+
const POSTER_SEARCH_MAX_LEN = 50
96+
/** Max words to use from title for poster search. */
97+
const POSTER_SEARCH_MAX_WORDS = 4
98+
99+
/**
100+
* Shortens a long torrent title for poster search (TMDB).
101+
* Uses part before " [", " (", " / " and limits by words/length so the API gets a valid query.
102+
* @param {string} fullTitle - Raw torrent title
103+
* @param {{ maxWords?: number, maxLen?: number }} opts - Optional limits
104+
* @returns {string} Short title suitable for getMoviePosters()
105+
*/
106+
export const shortenTitleForPosterSearch = (fullTitle, opts = {}) => {
107+
const maxWords = opts.maxWords ?? POSTER_SEARCH_MAX_WORDS
108+
const maxLen = opts.maxLen ?? POSTER_SEARCH_MAX_LEN
109+
if (!fullTitle || typeof fullTitle !== 'string') return ''
110+
const trimmed = fullTitle.trim()
111+
if (!trimmed) return ''
112+
let base = trimmed
113+
for (const sep of [' [', ' (', ' / ']) {
114+
const i = base.indexOf(sep)
115+
if (i > 0) base = base.slice(0, i).trim()
116+
}
117+
try {
118+
const parsed = ptt.parse(base)
119+
if (parsed?.title && parsed.title.length <= maxLen + 15) base = parsed.title
120+
} catch (_) {
121+
// ignore
122+
}
123+
const words = base.split(/\s+/).filter(Boolean)
124+
const byWords = words.slice(0, maxWords).join(' ')
125+
if (byWords.length <= maxLen) return byWords.trim()
126+
const cut = byWords.slice(0, maxLen)
127+
const lastSpace = cut.lastIndexOf(' ')
128+
const result = lastSpace > 0 ? cut.slice(0, lastSpace) : cut
129+
return result.trim() || trimmed.slice(0, maxLen).trim()
130+
}
131+
94132
export const parseTorrentTitle = (parsingSource, callback) => {
95133
parseTorrent.remote(parsingSource, (err, { name, files } = {}) => {
96134
if (!name || err) return callback({ parsedTitle: null, originalName: null })

web/src/components/Search/SearchDialog.jsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { torznabSearchHost, torrentsHost, settingsHost, searchHost } from 'utils
2424
import useOnStandaloneAppOutsideClick from 'utils/useOnStandaloneAppOutsideClick'
2525
import { StyledDialog, StyledHeader } from 'style/CustomMaterialUiStyles'
2626
import { parseSizeToBytes, formatSizeToClassicUnits } from 'utils/Utils'
27+
import { getMoviePosters, shortenTitleForPosterSearch } from 'components/Add/helpers'
2728

2829
import { Content } from './style'
2930

@@ -97,12 +98,21 @@ export default function SearchDialog({ handleClose }) {
9798
setErrorMsg(t('Torznab.NoLinkFound'))
9899
return
99100
}
101+
let poster = item.Poster
102+
if (!poster && item.Title) {
103+
const query = shortenTitleForPosterSearch(item.Title)
104+
if (query) {
105+
const urlList = await getMoviePosters(query, 'en')
106+
const [firstPosterUrl] = urlList || []
107+
if (firstPosterUrl) poster = firstPosterUrl
108+
}
109+
}
100110
await axios.post(torrentsHost(), {
101111
action: 'add',
102112
link,
103113
title: item.Title,
104114
save_to_db: true,
105-
poster: item.Poster,
115+
poster: poster || '',
106116
})
107117
setSuccessMsg(t('Torznab.TorrentAddedSuccessfully'))
108118
} catch (error) {

0 commit comments

Comments
 (0)