diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 671d1504d..9fd0c64f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: with: images: | ghcr.io/${{ github.repository_owner }}/cross-seed - ${{ secrets.DOCKERHUB_USERNAME }}/cross-seed + crossseed/cross-seed tags: | type=semver,pattern=version-{{version}} type=semver,pattern={{version}} diff --git a/.idea/modules.xml b/.idea/modules.xml index e1a04a4f9..c3038509f 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + diff --git a/package-lock.json b/package-lock.json index 5f935310f..5f6482ac9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cross-seed", - "version": "5.4.5", + "version": "5.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cross-seed", - "version": "5.4.5", + "version": "5.5.0", "license": "Apache-2.0", "dependencies": { "bencode": "^2.0.1", diff --git a/package.json b/package.json index ee46be51a..98a36f92f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cross-seed", - "version": "5.4.5", + "version": "5.5.0", "description": "Query Jackett for cross-seedable torrents", "scripts": { "test": "true", diff --git a/src/action.ts b/src/action.ts index 2260d7f53..c77f964ac 100644 --- a/src/action.ts +++ b/src/action.ts @@ -44,7 +44,9 @@ export async function performAction( // Candidate is single, nested file if (candidateParentDir != ".") { if (!existsSync(path.join(linkDir, candidateParentDir))) { - mkdirSync(path.join(linkDir, candidateParentDir), { recursive: true }); + mkdirSync(path.join(linkDir, candidateParentDir), { + recursive: true, + }); } correctedlinkDir = path.join(linkDir, candidateParentDir); } diff --git a/src/cmd.ts b/src/cmd.ts index 3522fcaf8..16bc63bb9 100755 --- a/src/cmd.ts +++ b/src/cmd.ts @@ -309,6 +309,7 @@ createCommandWithSharedOptions("daemon", "Start the cross-seed daemon") setRuntimeConfig(runtimeConfig); initializeLogger(); initializePushNotifier(); + logger.info(`${PROGRAM_NAME} v${PROGRAM_VERSION}`); logger.verbose({ label: Label.CONFIGDUMP, message: inspect(runtimeConfig), diff --git a/src/config.template.cjs b/src/config.template.cjs index 1ff0f09a7..8d1d6183f 100644 --- a/src/config.template.cjs +++ b/src/config.template.cjs @@ -86,8 +86,8 @@ module.exports = { includeEpisodes: false, /** - * Search for single episodes (not from season packs) - * during data based searches. + * Whether to include single episode torrents in the search (not from season packs). + * Like `includeEpisodes` but slightly more restrictive. */ includeSingleEpisodes: false, @@ -166,8 +166,6 @@ module.exports = { * cross-seed will send POST requests to this url * with a JSON payload of { title, body }. * Conforms to the caronc/apprise REST API. - * If necessary, supply your username and password inside the url like so: - * "http://username:password@localhost:8000/notify/cross-seed" */ notificationWebhookUrl: undefined, diff --git a/src/pushNotifier.ts b/src/pushNotifier.ts index 2a3a99c5d..e2df2fbb4 100644 --- a/src/pushNotifier.ts +++ b/src/pushNotifier.ts @@ -20,54 +20,19 @@ interface PushNotification { export class PushNotifier { url: string; - username: string | undefined; - password: string | undefined; - - constructor(url: string) { - const urlObj = new URL(url); - - if (urlObj.username && urlObj.password) { - this.username = urlObj.username; - this.password = urlObj.password; - - // Remove the username and password from the URL - urlObj.username = ""; - urlObj.password = ""; - - this.url = urlObj.toString(); - } else { + constructor(url: string) { this.url = url; - } } - async notify({ title = "cross-seed", body, ...rest }: PushNotification): Promise { + notify({ title = "cross-seed", body, ...rest }: PushNotification): void { if (this.url) { - const headers = new Headers(); - headers.append("Content-Type", "application/json"); - - if (this.username && this.password) { - const credentials = `${this.username}:${this.password}`; - const basicAuth = "Basic " + btoa(credentials); - headers.append("Authorization", basicAuth); - } - - logger.verbose(`Notification request send to ${this.url}`); - - try { - const response = await fetch(this.url, { - method: "POST", - headers, - body: JSON.stringify({ title, body, ...rest }), - }); - - const responseText = await response.text(); - response.ok - ? logger.verbose(`Notifiaction server response:\n${responseText}`) - : logger.error(`Notifiaction server error: ${response.status}, ${response.statusText}`); - - } catch (error) { - logger.error({ message: "Failed to send push notification", error }); - } + fetch(this.url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ title, body, ...rest }), + }).catch(() => { + logger.error({ message: "Failed to send push notification" }); + }); } } } diff --git a/src/torrent.ts b/src/torrent.ts index 36c4419d9..c9caef096 100644 --- a/src/torrent.ts +++ b/src/torrent.ts @@ -1,9 +1,14 @@ -import fs, { promises as fsPromises } from "fs"; import Fuse from "fuse.js"; +import fs, { promises as fsPromises } from "fs"; import fetch, { Response } from "node-fetch"; import path, { join } from "path"; import { inspect } from "util"; -import { USER_AGENT } from "./constants.js"; +import { + USER_AGENT, + EP_REGEX, + SEASON_REGEX, + MOVIE_REGEX, +} from "./constants.js"; import { db } from "./db.js"; import { CrossSeedError } from "./errors.js"; import { logger, logOnce } from "./logger.js"; @@ -11,7 +16,7 @@ import { Metafile } from "./parseTorrent.js"; import { Result, resultOf, resultOfErr } from "./Result.js"; import { getRuntimeConfig } from "./runtimeConfig.js"; import { createSearcheeFromTorrentFile, Searchee } from "./searchee.js"; -import { stripExtension } from "./utils.js"; +import { reformatTitleForSearching, stripExtension } from "./utils.js"; export interface TorrentLocator { infoHash?: string; @@ -201,15 +206,38 @@ export async function getTorrentByFuzzyName( name: string ): Promise { const allNames = await db("torrent").select("name", "file_path"); + const fullMatch = reformatTitleForSearching(name).replace( + /[^a-z0-9]/gi, + "" + ).toLowerCase(); + + // Attempt to filter torrents in DB to match incoming torrent before fuzzy check + let filteredNames = []; + if (fullMatch) { + filteredNames = allNames.filter((dbName) => { + const dbMatch = reformatTitleForSearching(dbName.name).replace( + /[^a-z0-9]/gi, + "" + ).toLowerCase(); + if (!dbMatch) return false; + return fullMatch === dbMatch; + }); + } + + // If none match, proceed with fuzzy name check on all names. + filteredNames = filteredNames.length > 0 ? filteredNames : allNames; + // @ts-expect-error fuse types are confused - const potentialMatches = new Fuse(allNames, { + const potentialMatches = new Fuse(filteredNames, { keys: ["name"], distance: 6, - threshold: 0.25, + threshold: 0.6, }).search(name); + // Valid matches exist if (potentialMatches.length === 0) return null; - const [firstMatch] = potentialMatches; + + const firstMatch = potentialMatches[0]; return parseTorrentFromFilename(firstMatch.item.file_path); }