Skip to content

Commit

Permalink
Merge branch 'master' into rss-prefilter
Browse files Browse the repository at this point in the history
  • Loading branch information
akesher committed Sep 23, 2023
2 parents 4c4a5a5 + dd947d6 commit cfe05fc
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 24 deletions.
38 changes: 38 additions & 0 deletions CONTRIRUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Writing code

1. Download VS Code. it has typescript support out of the box, but any ide will
do. recommend downloading a typescript plugin if needed
2. clone repo
3. open repo in vs code
4. run npm install
5. start coding :)

the code is in /src cmd.js is the entrypoint pipeline.js is the interesting high
level functions (doRss, etc...)

# Build and Run

`npm run build` will compile the typescript files into javascript files in /dist
`npm run watch` will compile, then incrementally compile when files change the
`cross-seed` command is an alias for `node dist/cmd.js`, as in
`node dist/cmd.js daemon` but it's annoying to use the cross-seed alias while
developing so i use a lot of ctrl-R with fzf

# Testing

I keep a `config.js` that points to
`{ torrentDir: "./input", outputDir: "./output" }` and put some torrent files in
`./input` I usually run in search mode because it's the simplest form of
cross-seed and most work applies to it.

## testing daemon mode

have three terminals open, one for npm run watch, one for keeping the cross-seed
daemon open, and one for curling to localhost:2468

## testing direct injection

mostly i stick with `--save` (the default i think) usually i use docker to run
whichever client. you could install it yourself though I have a gitignored
docker_compose.yml that keeps track of all of the clients but i only run them
one at a time
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cross-seed",
"version": "5.4.3",
"version": "5.4.5",
"description": "Query Jackett for cross-seedable torrents",
"scripts": {
"test": "true",
Expand Down
2 changes: 2 additions & 0 deletions src/config.template.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ 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,

Expand Down
2 changes: 2 additions & 0 deletions src/config.template.docker.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ 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,

Expand Down
6 changes: 3 additions & 3 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ export const PROGRAM_NAME = packageDotJson.name;
export const PROGRAM_VERSION = packageDotJson.version;
export const USER_AGENT = `CrossSeed/${PROGRAM_VERSION}`;

export const EP_REGEX = /^(?<title>.+)[. ](?<season>S\d+)(?<episode>E\d+)/i;
export const EP_REGEX = /^(?<title>.+?)[\s._](?<season>S\d+)?[_.\s]?(?<episode>E\d+(?:[-\s]?E?\d+)?)/i;
export const SEASON_REGEX =
/^(?<title>.+)[. ](?<season>S\d+)(?:\s?-\s?(?<seasonmax>S?\d+))?(?!E\d+)/i;
/^(?<title>.+?)[_.\s](?<season>S\d+)(?:[.\-\s]*?(?<seasonmax>S?\d+))?(?=[_.\s](?!E\d+))/i;
export const MOVIE_REGEX =
/^(?<title>.+)[. ][[(]?(?<year>\d{4})[)\]]?(?![pi])/i;
/^(?<title>.+?)[._\s][[(]?(?<year>\d{4})[)\]]?(?![pi])/i;

export const VIDEO_EXTENSIONS = [".mkv", ".mp4", ".avi"];

Expand Down
53 changes: 44 additions & 9 deletions src/pushNotifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,54 @@ interface PushNotification {

export class PushNotifier {
url: string;
constructor(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 {
this.url = url;
}
}

notify({ title = "cross-seed", body, ...rest }: PushNotification): void {
async notify({ title = "cross-seed", body, ...rest }: PushNotification): Promise<void> {
if (this.url) {
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" });
});
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 });
}
}
}
}
Expand Down
26 changes: 17 additions & 9 deletions src/torznab.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ms from "ms";
import fetch from "node-fetch";
import xml2js from "xml2js";
import { EP_REGEX, SEASON_REGEX } from "./constants.js";
import { EP_REGEX, SEASON_REGEX, USER_AGENT } from "./constants.js";
import { db } from "./db.js";
import { CrossSeedError } from "./errors.js";
import {
Expand All @@ -21,7 +21,6 @@ import {
reformatTitleForSearching,
stripExtension,
} from "./utils.js";
import { USER_AGENT } from "./constants.js";

interface TorznabParams {
t: "caps" | "search" | "tvsearch" | "movie";
Expand Down Expand Up @@ -463,26 +462,35 @@ async function makeRequests(
})
.then((response) => {
if (!response.ok) {
if (response.status === 429) {
const retryAfterSeconds = Number(
response.headers.get("Retry-After")
);

if (!Number.isNaN(retryAfterSeconds)) {
updateIndexerStatus(
IndexerStatus.RATE_LIMITED,
Date.now() + ms("1 hour"),
response.status === 429
? IndexerStatus.RATE_LIMITED
: IndexerStatus.UNKNOWN_ERROR,
Date.now() + ms(`${retryAfterSeconds} seconds`),
[indexers[i].id]
);
} else {
updateIndexerStatus(
IndexerStatus.UNKNOWN_ERROR,
Date.now() + ms("1 hour"),
response.status === 429
? IndexerStatus.RATE_LIMITED
: IndexerStatus.UNKNOWN_ERROR,
response.status === 429
? Date.now() + ms("1 hour")
: Date.now() + ms("10 minutes"),
[indexers[i].id]
);
}
throw new Error(
`request failed with code: ${response.status}`
);
}
return response;
return response.text();
})
.then((r) => r.text())
.then(xml2js.parseStringPromise)
.then(parseTorznabResults)
)
Expand Down

0 comments on commit cfe05fc

Please sign in to comment.