diff --git a/Dockerfile b/Dockerfile index ec6e5b275..a16b6d4eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,23 @@ -FROM node:16 +# Build Stage +FROM node:18-alpine AS build-stage WORKDIR /usr/src/cross-seed -RUN npm install -g npm@9 COPY package*.json ./ -RUN npm ci -ENV CONFIG_DIR=/config -ENV DOCKER_ENV=true +RUN npm install -g npm@9 \ + && npm ci COPY tsconfig.json tsconfig.json COPY src src -RUN npm run build +RUN npm run build \ + && npm prune --production \ + && rm -rf src tsconfig.json + +# Production Stage +FROM node:18-alpine +WORKDIR /usr/src/cross-seed +COPY --from=build-stage /usr/src/cross-seed . RUN npm link +RUN apk add --no-cache curl +ENV CONFIG_DIR=/config +ENV DOCKER_ENV=true EXPOSE 2468 WORKDIR /config ENTRYPOINT ["cross-seed"] diff --git a/package-lock.json b/package-lock.json index 9730228c0..d47fb75b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cross-seed", - "version": "5.3.2", + "version": "5.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cross-seed", - "version": "5.3.2", + "version": "5.4.0", "license": "Apache-2.0", "dependencies": { "bencode": "^2.0.1", diff --git a/package.json b/package.json index c6289e64a..a3fe2a01a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cross-seed", - "version": "5.3.2", + "version": "5.4.0", "description": "Query Jackett for cross-seedable torrents", "scripts": { "test": "true", diff --git a/src/cmd.ts b/src/cmd.ts index 2e0badf19..3c1b3999b 100755 --- a/src/cmd.ts +++ b/src/cmd.ts @@ -241,6 +241,13 @@ program .description("Clear the cache of downloaded-and-rejected torrents") .action(async () => { await db("decision").del(); + await db("indexer").update({ + status: null, + retry_after: null, + search_cap: null, + tv_search_cap: null, + movie_search_cap: null, + }); await db.destroy(); }); diff --git a/src/torznab.ts b/src/torznab.ts index 2afeb44b8..27475f3dc 100644 --- a/src/torznab.ts +++ b/src/torznab.ts @@ -294,15 +294,55 @@ function assembleUrl( return url.toString(); } -function fetchCaps(indexer: { +async function fetchCaps(indexer: { id: number; url: string; apikey: string; }): Promise { - return fetch(assembleUrl(indexer.url, indexer.apikey, { t: "caps" })) - .then((r) => r.text()) - .then(xml2js.parseStringPromise) - .then(parseTorznabCaps); + let response; + try { + response = await fetch( + assembleUrl(indexer.url, indexer.apikey, { t: "caps" }) + ); + } catch (e) { + const error = new Error( + `Indexer ${indexer.url} failed to respond, check verbose logs` + ); + logger.error(error); + logger.debug(e); + throw error; + } + + const responseText = await response.text(); + if (!response.ok) { + const error = new Error( + `Indexer ${indexer.url} responded with code ${response.status} when fetching caps, check verbose logs` + ); + logger.error(error); + logger.debug( + `Response body first 1000 characters: ${responseText.substring( + 0, + 1000 + )}` + ); + throw error; + } + try { + const parsedXml = await xml2js.parseStringPromise(responseText); + return parseTorznabCaps(parsedXml); + } catch (_) { + const error = new Error( + `Indexer ${indexer.url} responded with invalid XML when fetching caps, check verbose logs` + ); + logger.error(error); + logger.debug( + `Response body first 1000 characters: ${responseText.substring( + 0, + 1000 + )}` + ); + throw error; + } } function collateOutcomes( @@ -334,16 +374,10 @@ async function updateCaps( const outcomes = await Promise.allSettled( indexers.map((indexer) => fetchCaps(indexer)) ); - const { fulfilled, rejected } = collateOutcomes( + const { fulfilled } = collateOutcomes( indexers.map((i) => i.id), outcomes ); - for (const [indexerId, reason] of rejected) { - logger.warn( - `Failed to reach ${indexers.find((i) => i.id === indexerId).url}` - ); - logger.debug(reason); - } for (const [indexerId, caps] of fulfilled) { await db("indexer").where({ id: indexerId }).update({ @@ -379,6 +413,7 @@ export async function validateTorznabUrls() { tv_search_cap: null, movie_search_cap: null, }) + .orWhere({ search_cap: false, active: true }) .select({ id: "id", url: "url", apikey: "apikey" }); await updateCaps(enabledIndexersWithoutCaps);