Merge pull request #49 from iPromKnight/master
Package updates and start of logging
This commit is contained in:
@@ -1,2 +1,15 @@
|
||||
build.sh
|
||||
node_modules/
|
||||
node_modules
|
||||
Dockerfile*
|
||||
docker-compose*
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
LICENSE
|
||||
.vscode
|
||||
Makefile
|
||||
helm-charts
|
||||
.env
|
||||
.editorconfig
|
||||
.idea
|
||||
coverage*
|
||||
@@ -14,9 +14,6 @@ RUN npm run build
|
||||
# --- Runtime Stage ---
|
||||
FROM node:lts-buster-slim
|
||||
|
||||
# Install pm2
|
||||
RUN npm install pm2 -g
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
@@ -26,4 +23,4 @@ RUN npm prune --omit=dev
|
||||
|
||||
EXPOSE 7001
|
||||
|
||||
ENTRYPOINT [ "pm2-runtime", "start", "ecosystem.config.cjs"]
|
||||
ENTRYPOINT [ "node", "dist/index.cjs" ]
|
||||
@@ -1,14 +0,0 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "consumer",
|
||||
script: "npm start",
|
||||
cwd: "/app",
|
||||
watch: ["./dist/index.cjs"],
|
||||
autorestart: true,
|
||||
env: {
|
||||
...process.env
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
2931
src/node/consumer/package-lock.json
generated
2931
src/node/consumer/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,27 +4,26 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node esbuild.js",
|
||||
"dev": "tsx watch --ignore node_modules src/index.js",
|
||||
"dev": "tsx watch --ignore node_modules src/index.js | pino-pretty",
|
||||
"start": "node dist/index.cjs",
|
||||
"lint": "eslint . --ext .ts,.js"
|
||||
},
|
||||
"author": "A Dude",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tirke/node-cache-manager-mongodb": "^1.6.0",
|
||||
"amqplib": "^0.10.3",
|
||||
"axios": "^1.6.1",
|
||||
"bluebird": "^3.7.2",
|
||||
"bottleneck": "^2.19.5",
|
||||
"cache-manager": "^3.4.4",
|
||||
"cache-manager-mongodb": "^0.3.0",
|
||||
"cache-manager": "^5.4.0",
|
||||
"google-sr": "^3.2.1",
|
||||
"jaro-winkler": "^0.2.8",
|
||||
"magnet-uri": "^6.2.0",
|
||||
"moment": "^2.30.1",
|
||||
"name-to-imdb": "^3.0.4",
|
||||
"parse-torrent-title": "git://github.com/TheBeastLT/parse-torrent-title.git#022408972c2a040f846331a912a6a8487746a654",
|
||||
"parse-torrent-title": "https://github.com/TheBeastLT/parse-torrent-title.git#022408972c2a040f846331a912a6a8487746a654",
|
||||
"pg": "^8.11.3",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"pino": "^8.18.0",
|
||||
"sequelize": "^6.31.1",
|
||||
"torrent-stream": "^1.2.1",
|
||||
"user-agents": "^1.0.1444"
|
||||
@@ -32,10 +31,11 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.6",
|
||||
"@types/stremio-addon-sdk": "^1.6.10",
|
||||
"esbuild": "^0.19.12",
|
||||
"esbuild": "^0.20.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-import-helpers": "^1.3.1",
|
||||
"tsx": "^4.7.0"
|
||||
"tsx": "^4.7.0",
|
||||
"pino-pretty": "^10.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { listenToQueue } from './jobs/processTorrents.js';
|
||||
import { jobConfig } from "./lib/config.js";
|
||||
import { connect } from './lib/repository.js';
|
||||
import { getTrackers } from "./lib/trackerService.js";
|
||||
|
||||
(async () => {
|
||||
await getTrackers();
|
||||
await connect();
|
||||
|
||||
if (jobConfig.JOBS_ENABLED) {
|
||||
await listenToQueue();
|
||||
}
|
||||
await listenToQueue();
|
||||
})();
|
||||
@@ -1,34 +1,37 @@
|
||||
import amqp from 'amqplib'
|
||||
import Promise from 'bluebird'
|
||||
import { rabbitConfig, jobConfig } from '../lib/config.js'
|
||||
import { processTorrentRecord } from "../lib/ingestedTorrent.js";
|
||||
import {logger} from "../lib/logger.js";
|
||||
|
||||
const assertQueueOptions = { durable: true }
|
||||
const consumeQueueOptions = { noAck: false }
|
||||
|
||||
const processMessage = msg =>
|
||||
Promise.resolve(getMessageAsJson(msg))
|
||||
.then(torrent => processTorrentRecord(torrent))
|
||||
.then(() => Promise.resolve(msg));
|
||||
|
||||
const getMessageAsJson = msg => {
|
||||
const torrent = JSON.parse(msg.content.toString());
|
||||
return Promise.resolve(torrent.message);
|
||||
}
|
||||
const processMessage = msg => processTorrentRecord(getMessageAsJson(msg));
|
||||
|
||||
const assertAndConsumeQueue = channel => {
|
||||
console.log('Worker is running! Waiting for new torrents...')
|
||||
const getMessageAsJson = msg =>
|
||||
JSON.parse(msg.content.toString()).message;
|
||||
|
||||
const ackMsg = msg => Promise.resolve(msg)
|
||||
.then(msg => processMessage(msg))
|
||||
.then(msg => channel.ack(msg))
|
||||
.catch(error => console.error('Failed processing torrent', error));
|
||||
const assertAndConsumeQueue = async channel => {
|
||||
logger.info('Worker is running! Waiting for new torrents...')
|
||||
|
||||
return channel.assertQueue(rabbitConfig.QUEUE_NAME, assertQueueOptions)
|
||||
const ackMsg = msg =>
|
||||
processMessage(msg)
|
||||
.then(() => channel.ack(msg))
|
||||
.catch(error => logger.error('Failed processing torrent', error));
|
||||
|
||||
channel.assertQueue(rabbitConfig.QUEUE_NAME, assertQueueOptions)
|
||||
.then(() => channel.prefetch(jobConfig.JOB_CONCURRENCY))
|
||||
.then(() => channel.consume(rabbitConfig.QUEUE_NAME, ackMsg, consumeQueueOptions))
|
||||
.catch(error => logger.error('Failed to setup channel', error));
|
||||
}
|
||||
|
||||
export const listenToQueue = () => amqp.connect(rabbitConfig.URI)
|
||||
.then(connection => connection.createChannel())
|
||||
.then(channel => assertAndConsumeQueue(channel))
|
||||
export const listenToQueue = async () => {
|
||||
if (!jobConfig.JOBS_ENABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
return amqp.connect(rabbitConfig.URI)
|
||||
.then(connection => connection.createChannel())
|
||||
.then(channel => assertAndConsumeQueue(channel))
|
||||
.catch(error => logger.error('Failed to connect and setup channel', error));
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
import cacheManager from 'cache-manager';
|
||||
import mangodbStore from 'cache-manager-mongodb';
|
||||
import { createCache, memoryStore} from 'cache-manager';
|
||||
import { mongoDbStore } from '@tirke/node-cache-manager-mongodb'
|
||||
import { cacheConfig } from './config.js';
|
||||
import { logger } from './logger.js';
|
||||
import { CacheType } from "./types.js";
|
||||
|
||||
const GLOBAL_KEY_PREFIX = 'selfhostio-consumer';
|
||||
const IMDB_ID_PREFIX = `${GLOBAL_KEY_PREFIX}|imdb_id`;
|
||||
@@ -12,61 +14,71 @@ const GLOBAL_TTL = process.env.METADATA_TTL || 7 * 24 * 60 * 60; // 7 days
|
||||
const MEMORY_TTL = process.env.METADATA_TTL || 2 * 60 * 60; // 2 hours
|
||||
const TRACKERS_TTL = 2 * 24 * 60 * 60; // 2 days
|
||||
|
||||
const memoryCache = initiateMemoryCache();
|
||||
const remoteCache = initiateRemoteCache();
|
||||
const initiateMemoryCache = () =>
|
||||
createCache(memoryStore(), {
|
||||
ttl: parseInt(MEMORY_TTL)
|
||||
});
|
||||
|
||||
function initiateRemoteCache() {
|
||||
if (cacheConfig.NO_CACHE) {
|
||||
return null;
|
||||
} else if (cacheConfig.MONGO_URI) {
|
||||
return cacheManager.caching({
|
||||
store: mangodbStore,
|
||||
uri: cacheConfig.MONGO_URI,
|
||||
options: {
|
||||
collection: cacheConfig.COLLECTION_NAME,
|
||||
socketTimeoutMS: 120000,
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: false,
|
||||
ttl: GLOBAL_TTL
|
||||
},
|
||||
ttl: GLOBAL_TTL,
|
||||
ignoreCacheErrors: true
|
||||
});
|
||||
} else {
|
||||
return cacheManager.caching({
|
||||
store: 'memory',
|
||||
ttl: MEMORY_TTL
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initiateMemoryCache() {
|
||||
return cacheManager.caching({
|
||||
store: 'memory',
|
||||
ttl: MEMORY_TTL,
|
||||
max: Infinity // infinite LRU cache size
|
||||
const initiateMongoCache = () => {
|
||||
const store = mongoDbStore({
|
||||
collectionName: cacheConfig.COLLECTION_NAME,
|
||||
ttl: parseInt(GLOBAL_TTL),
|
||||
url: cacheConfig.MONGO_URI,
|
||||
mongoConfig:{
|
||||
socketTimeoutMS: 120000,
|
||||
appName: 'selfhostio-consumer',
|
||||
}
|
||||
});
|
||||
|
||||
return createCache(store, {
|
||||
ttl: parseInt(GLOBAL_TTL),
|
||||
});
|
||||
}
|
||||
|
||||
function cacheWrap(cache, key, method, options) {
|
||||
const initiateRemoteCache = ()=> {
|
||||
if (cacheConfig.NO_CACHE) {
|
||||
logger.debug('Cache is disabled');
|
||||
return null;
|
||||
}
|
||||
return cacheConfig.MONGO_URI ? initiateMongoCache() : initiateMemoryCache();
|
||||
}
|
||||
|
||||
const getCacheType = (cacheType) => {
|
||||
switch (cacheType) {
|
||||
case CacheType.MEMORY:
|
||||
return memoryCache;
|
||||
case CacheType.MONGODB:
|
||||
return remoteCache;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const memoryCache = initiateMemoryCache()
|
||||
const remoteCache = initiateRemoteCache()
|
||||
|
||||
const cacheWrap = async (cacheType, key, method, options) => {
|
||||
const cache = getCacheType(cacheType);
|
||||
|
||||
if (cacheConfig.NO_CACHE || !cache) {
|
||||
return method();
|
||||
}
|
||||
return cache.wrap(key, method, options);
|
||||
|
||||
logger.debug(`Cache type: ${cacheType}`);
|
||||
logger.debug(`Cache key: ${key}`);
|
||||
logger.debug(`Cache options: ${JSON.stringify(options)}`);
|
||||
|
||||
return cache.wrap(key, method, options.ttl);
|
||||
}
|
||||
|
||||
export function cacheWrapImdbId(key, method) {
|
||||
return cacheWrap(remoteCache, `${IMDB_ID_PREFIX}:${key}`, method, { ttl: GLOBAL_TTL });
|
||||
}
|
||||
export const cacheWrapImdbId = (key, method) =>
|
||||
cacheWrap(CacheType.MONGODB, `${IMDB_ID_PREFIX}:${key}`, method, { ttl: parseInt(GLOBAL_TTL) });
|
||||
|
||||
export function cacheWrapKitsuId(key, method) {
|
||||
return cacheWrap(remoteCache, `${KITSU_ID_PREFIX}:${key}`, method, { ttl: GLOBAL_TTL });
|
||||
}
|
||||
export const cacheWrapKitsuId = (key, method) =>
|
||||
cacheWrap(CacheType.MONGODB, `${KITSU_ID_PREFIX}:${key}`, method, { ttl: parseInt(GLOBAL_TTL) });
|
||||
|
||||
export function cacheWrapMetadata(id, method) {
|
||||
return cacheWrap(memoryCache, `${METADATA_PREFIX}:${id}`, method, { ttl: MEMORY_TTL });
|
||||
}
|
||||
export const cacheWrapMetadata = (id, method) =>
|
||||
cacheWrap(CacheType.MEMORY, `${METADATA_PREFIX}:${id}`, method, { ttl: parseInt(MEMORY_TTL) });
|
||||
|
||||
export function cacheTrackers(method) {
|
||||
return cacheWrap(memoryCache, `${TRACKERS_KEY_PREFIX}`, method, { ttl: TRACKERS_TTL });
|
||||
}
|
||||
export const cacheTrackers = (method) =>
|
||||
cacheWrap(CacheType.MEMORY, `${TRACKERS_KEY_PREFIX}`, method, { ttl: parseInt(TRACKERS_TTL) });
|
||||
@@ -1,12 +1,13 @@
|
||||
import { createTorrentEntry, checkAndUpdateTorrent } from './torrentEntries.js';
|
||||
import {getTrackers} from "./trackerService.js";
|
||||
import { Type } from './types.js';
|
||||
import { TorrentType } from './types.js';
|
||||
import {logger} from "./logger.js";
|
||||
|
||||
export async function processTorrentRecord(torrent) {
|
||||
const {category} = torrent;
|
||||
const type = category === 'tv' ? Type.SERIES : Type.MOVIE;
|
||||
const type = category === 'tv' ? TorrentType.SERIES : TorrentType.MOVIE;
|
||||
const torrentInfo = await parseTorrent(torrent, type);
|
||||
console.log(`Processing torrent ${torrentInfo.title} with infoHash ${torrentInfo.infoHash}`)
|
||||
logger.info(`Processing torrent ${torrentInfo.title} with infoHash ${torrentInfo.infoHash}`)
|
||||
|
||||
if (await checkAndUpdateTorrent(torrentInfo)) {
|
||||
return torrentInfo;
|
||||
|
||||
5
src/node/consumer/src/lib/logger.js
Normal file
5
src/node/consumer/src/lib/logger.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import pino from 'pino';
|
||||
|
||||
export const logger = pino({
|
||||
level: process.env.LOG_LEVEL || 'info'
|
||||
});
|
||||
@@ -2,24 +2,24 @@ import axios from 'axios';
|
||||
import { search } from 'google-sr';
|
||||
import nameToImdb from 'name-to-imdb';
|
||||
import { cacheWrapImdbId, cacheWrapKitsuId, cacheWrapMetadata } from './cache.js';
|
||||
import { Type } from './types.js';
|
||||
import { TorrentType } from './types.js';
|
||||
|
||||
const CINEMETA_URL = 'https://v3-cinemeta.strem.io';
|
||||
const KITSU_URL = 'https://anime-kitsu.strem.fun';
|
||||
const TIMEOUT = 20000;
|
||||
|
||||
export function getMetadata(id, type = Type.SERIES) {
|
||||
export function getMetadata(id, type = TorrentType.SERIES) {
|
||||
if (!id) {
|
||||
return Promise.reject("no valid id provided");
|
||||
}
|
||||
|
||||
const key = Number.isInteger(id) || id.match(/^\d+$/) ? `kitsu:${id}` : id;
|
||||
const metaType = type === Type.MOVIE ? Type.MOVIE : Type.SERIES;
|
||||
const metaType = type === TorrentType.MOVIE ? TorrentType.MOVIE : TorrentType.SERIES;
|
||||
return cacheWrapMetadata(key, () => _requestMetadata(`${KITSU_URL}/meta/${metaType}/${key}.json`)
|
||||
.catch(() => _requestMetadata(`${CINEMETA_URL}/meta/${metaType}/${key}.json`))
|
||||
.catch(() => {
|
||||
// try different type in case there was a mismatch
|
||||
const otherType = metaType === Type.MOVIE ? Type.SERIES : Type.MOVIE;
|
||||
const otherType = metaType === TorrentType.MOVIE ? TorrentType.SERIES : TorrentType.MOVIE;
|
||||
return _requestMetadata(`${CINEMETA_URL}/meta/${otherType}/${key}.json`)
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { parse } from 'parse-torrent-title';
|
||||
import { Type } from './types.js';
|
||||
import { TorrentType } from './types.js';
|
||||
|
||||
const MULTIPLE_FILES_SIZE = 4 * 1024 * 1024 * 1024; // 4 GB
|
||||
|
||||
@@ -65,7 +65,7 @@ function isMovieVideo(video, otherVideos, type, hasMovies) {
|
||||
// movie if video explicitly has numbered movie keyword in the name, ie. 1 Movie or Movie 1
|
||||
return true;
|
||||
}
|
||||
if (!hasMovies && type !== Type.ANIME) {
|
||||
if (!hasMovies && type !== TorrentType.ANIME) {
|
||||
// not movie if torrent name does not contain movies keyword or is not a pack torrent and is not anime
|
||||
return false;
|
||||
}
|
||||
@@ -85,7 +85,7 @@ export function isPackTorrent(torrent) {
|
||||
return true;
|
||||
}
|
||||
const parsedInfo = parse(torrent.title);
|
||||
if (torrent.type === Type.MOVIE) {
|
||||
if (torrent.type === TorrentType.MOVIE) {
|
||||
return parsedInfo.complete || typeof parsedInfo.year === 'string' || /movies/i.test(torrent.title);
|
||||
}
|
||||
const hasMultipleEpisodes = parsedInfo.complete ||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import moment from 'moment';
|
||||
import { Sequelize, Op, DataTypes, fn, col, literal } from 'sequelize';
|
||||
import { databaseConfig } from './config.js';
|
||||
import {logger} from "./logger.js";
|
||||
import * as Promises from './promises.js';
|
||||
|
||||
const database = new Sequelize(
|
||||
@@ -185,7 +186,7 @@ export function connect() {
|
||||
if (databaseConfig.ENABLE_SYNC) {
|
||||
return database.sync({ alter: true })
|
||||
.catch(error => {
|
||||
console.error('Failed syncing database: ', error);
|
||||
logger.error('Failed syncing database: ', error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import * as Promises from './promises.js';
|
||||
import * as repository from './repository.js';
|
||||
import { parseTorrentFiles } from './torrentFiles.js';
|
||||
import { assignSubtitles } from './torrentSubtitles.js';
|
||||
import { Type } from './types.js';
|
||||
import { TorrentType } from './types.js';
|
||||
import {logger} from "./logger.js";
|
||||
|
||||
export async function createTorrentEntry(torrent, overwrite = false) {
|
||||
const titleInfo = parse(torrent.title);
|
||||
|
||||
if (!torrent.imdbId && torrent.type !== Type.ANIME) {
|
||||
if (!torrent.imdbId && torrent.type !== TorrentType.ANIME) {
|
||||
torrent.imdbId = await getImdbId(titleInfo, torrent.type)
|
||||
.catch(() => undefined);
|
||||
}
|
||||
@@ -22,13 +23,13 @@ export async function createTorrentEntry(torrent, overwrite = false) {
|
||||
// sanitize imdbId from redundant zeros
|
||||
torrent.imdbId = torrent.imdbId.replace(/tt0+([0-9]{7,})$/, 'tt$1');
|
||||
}
|
||||
if (!torrent.kitsuId && torrent.type === Type.ANIME) {
|
||||
if (!torrent.kitsuId && torrent.type === TorrentType.ANIME) {
|
||||
torrent.kitsuId = await getKitsuId(titleInfo)
|
||||
.catch(() => undefined);
|
||||
}
|
||||
|
||||
if (!torrent.imdbId && !torrent.kitsuId && !isPackTorrent(torrent)) {
|
||||
console.log(`imdbId or kitsuId not found: ${torrent.provider} ${torrent.title}`);
|
||||
logger.warn(`imdbId or kitsuId not found: ${torrent.provider} ${torrent.title}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,17 +37,17 @@ export async function createTorrentEntry(torrent, overwrite = false) {
|
||||
.then(torrentContents => overwrite ? overwriteExistingFiles(torrent, torrentContents) : torrentContents)
|
||||
.then(torrentContents => assignSubtitles(torrentContents))
|
||||
.catch(error => {
|
||||
console.log(`Failed getting files for ${torrent.title}`, error.message);
|
||||
logger.warn(`Failed getting files for ${torrent.title}`, error.message);
|
||||
return {};
|
||||
});
|
||||
if (!videos || !videos.length) {
|
||||
console.log(`no video files found for ${torrent.provider} [${torrent.infoHash}] ${torrent.title}`);
|
||||
logger.warn(`no video files found for ${torrent.provider} [${torrent.infoHash}] ${torrent.title}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return repository.createTorrent({ ...torrent, contents, subtitles })
|
||||
.then(() => Promises.sequence(videos.map(video => () => repository.createFile(video))))
|
||||
.then(() => console.log(`Created ${torrent.provider} entry for [${torrent.infoHash}] ${torrent.title}`));
|
||||
.then(() => logger.info(`Created ${torrent.provider} entry for [${torrent.infoHash}] ${torrent.title}`));
|
||||
}
|
||||
|
||||
async function overwriteExistingFiles(torrent, torrentContents) {
|
||||
@@ -106,7 +107,7 @@ export async function checkAndUpdateTorrent(torrent) {
|
||||
if (!storedTorrent.languages && torrent.languages && storedTorrent.provider !== 'RARBG') {
|
||||
storedTorrent.languages = torrent.languages;
|
||||
await storedTorrent.save();
|
||||
console.log(`Updated [${storedTorrent.infoHash}] ${storedTorrent.title} language to ${torrent.languages}`);
|
||||
logger.debug(`Updated [${storedTorrent.infoHash}] ${storedTorrent.title} language to ${torrent.languages}`);
|
||||
}
|
||||
return createTorrentContents({ ...storedTorrent.get(), torrentLink: torrent.torrentLink })
|
||||
.then(() => updateTorrentSeeders(torrent));
|
||||
@@ -128,7 +129,7 @@ export async function createTorrentContents(torrent) {
|
||||
.then(torrentContents => notOpenedVideo ? torrentContents : { ...torrentContents, videos: storedVideos })
|
||||
.then(torrentContents => assignSubtitles(torrentContents))
|
||||
.catch(error => {
|
||||
console.log(`Failed getting contents for [${torrent.infoHash}] ${torrent.title}`, error.message);
|
||||
logger.warn(`Failed getting contents for [${torrent.infoHash}] ${torrent.title}`, error.message);
|
||||
return {};
|
||||
});
|
||||
|
||||
@@ -149,14 +150,14 @@ export async function createTorrentContents(torrent) {
|
||||
return repository.createTorrent({ ...torrent, contents, subtitles })
|
||||
.then(() => {
|
||||
if (shouldDeleteOld) {
|
||||
console.error(`Deleting old video for [${torrent.infoHash}] ${torrent.title}`)
|
||||
logger.debug(`Deleting old video for [${torrent.infoHash}] ${torrent.title}`)
|
||||
return storedVideos[0].destroy();
|
||||
}
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(() => Promises.sequence(videos.map(video => () => repository.createFile(video))))
|
||||
.then(() => console.log(`Created contents for ${torrent.provider} [${torrent.infoHash}] ${torrent.title}`))
|
||||
.catch(error => console.error(`Failed saving contents for [${torrent.infoHash}] ${torrent.title}`, error));
|
||||
.then(() => logger.info(`Created contents for ${torrent.provider} [${torrent.infoHash}] ${torrent.title}`))
|
||||
.catch(error => logger.error(`Failed saving contents for [${torrent.infoHash}] ${torrent.title}`, error));
|
||||
}
|
||||
|
||||
export async function updateTorrentSeeders(torrent) {
|
||||
@@ -166,7 +167,7 @@ export async function updateTorrentSeeders(torrent) {
|
||||
|
||||
return repository.setTorrentSeeders(torrent, torrent.seeders)
|
||||
.catch(error => {
|
||||
console.warn('Failed updating seeders:', error);
|
||||
logger.warn('Failed updating seeders:', error);
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,26 +8,27 @@ import { getMetadata, getImdbId, getKitsuId } from './metadata.js';
|
||||
import { parseSeriesVideos, isPackTorrent } from './parseHelper.js';
|
||||
import * as Promises from './promises.js';
|
||||
import {torrentFiles} from "./torrent.js";
|
||||
import { Type } from './types.js';
|
||||
import { TorrentType } from './types.js';
|
||||
import {logger} from "./logger.js";
|
||||
|
||||
const MIN_SIZE = 5 * 1024 * 1024; // 5 MB
|
||||
const imdb_limiter = new Bottleneck({ maxConcurrent: metadataConfig.IMDB_CONCURRENT, minTime: metadataConfig.IMDB_INTERVAL_MS });
|
||||
|
||||
export async function parseTorrentFiles(torrent) {
|
||||
const parsedTorrentName = parse(torrent.title);
|
||||
const metadata = await getMetadata(torrent.kitsuId || torrent.imdbId, torrent.type || Type.MOVIE)
|
||||
const metadata = await getMetadata(torrent.kitsuId || torrent.imdbId, torrent.type || TorrentType.MOVIE)
|
||||
.then(meta => Object.assign({}, meta))
|
||||
.catch(() => undefined);
|
||||
|
||||
// if (metadata && metadata.type !== torrent.type && torrent.type !== Type.ANIME) {
|
||||
// throw new Error(`Mismatching entry type for ${torrent.name}: ${torrent.type}!=${metadata.type}`);
|
||||
// }
|
||||
if (torrent.type !== Type.ANIME && metadata && metadata.type && metadata.type !== torrent.type) {
|
||||
if (torrent.type !== TorrentType.ANIME && metadata && metadata.type && metadata.type !== torrent.type) {
|
||||
// it's actually a movie/series
|
||||
torrent.type = metadata.type;
|
||||
}
|
||||
|
||||
if (torrent.type === Type.MOVIE && (!parsedTorrentName.seasons ||
|
||||
if (torrent.type === TorrentType.MOVIE && (!parsedTorrentName.seasons ||
|
||||
parsedTorrentName.season === 5 && [1, 5].includes(parsedTorrentName.episode))) {
|
||||
return parseMovieFiles(torrent, parsedTorrentName, metadata);
|
||||
}
|
||||
@@ -133,9 +134,9 @@ async function mapSeriesEpisode(file, torrent, files) {
|
||||
}
|
||||
|
||||
async function mapSeriesMovie(file, torrent) {
|
||||
const kitsuId = torrent.type === Type.ANIME ? await findMovieKitsuId(file) : undefined;
|
||||
const kitsuId = torrent.type === TorrentType.ANIME ? await findMovieKitsuId(file) : undefined;
|
||||
const imdbId = !kitsuId ? await findMovieImdbId(file) : undefined;
|
||||
const metadata = await getMetadata(kitsuId || imdbId, Type.MOVIE).catch(() => ({}));
|
||||
const metadata = await getMetadata(kitsuId || imdbId, TorrentType.MOVIE).catch(() => ({}));
|
||||
const hasEpisode = metadata.videos && metadata.videos.length && (file.episode || metadata.videos.length === 1);
|
||||
const episodeVideo = hasEpisode && metadata.videos[(file.episode || 1) - 1];
|
||||
return [{
|
||||
@@ -158,7 +159,7 @@ async function decomposeEpisodes(torrent, files, metadata = { episodeCount: [] }
|
||||
|
||||
preprocessEpisodes(files);
|
||||
|
||||
if (torrent.type === Type.ANIME && torrent.kitsuId) {
|
||||
if (torrent.type === TorrentType.ANIME && torrent.kitsuId) {
|
||||
if (needsCinemetaMetadataForAnime(files, metadata)) {
|
||||
// In some cases anime could be resolved to wrong kitsuId
|
||||
// because of imdb season naming/absolute per series naming/multiple seasons
|
||||
@@ -240,7 +241,7 @@ function isDateEpisodeFiles(files, metadata) {
|
||||
|
||||
function isAbsoluteEpisodeFiles(torrent, files, metadata) {
|
||||
const threshold = Math.ceil(files.length / 5);
|
||||
const isAnime = torrent.type === Type.ANIME && torrent.kitsuId;
|
||||
const isAnime = torrent.type === TorrentType.ANIME && torrent.kitsuId;
|
||||
const nonMovieEpisodes = files
|
||||
.filter(file => !file.isMovie && file.episodes);
|
||||
const absoluteEpisodes = files
|
||||
@@ -255,7 +256,7 @@ function isNewEpisodeNotInMetadata(torrent, file, metadata) {
|
||||
// new episode might not yet been indexed by cinemeta.
|
||||
// detect this if episode number is larger than the last episode or season is larger than the last one
|
||||
// only for non anime metas
|
||||
const isAnime = torrent.type === Type.ANIME && torrent.kitsuId;
|
||||
const isAnime = torrent.type === TorrentType.ANIME && torrent.kitsuId;
|
||||
return !isAnime && !file.isMovie && file.episodes && file.season !== 1
|
||||
&& /continuing|current/i.test(metadata.status)
|
||||
&& file.season >= metadata.episodeCount.length
|
||||
@@ -355,7 +356,7 @@ function getTimeZoneOffset(country) {
|
||||
|
||||
function assignKitsuOrImdbEpisodes(torrent, files, metadata) {
|
||||
if (!metadata || !metadata.videos || !metadata.videos.length) {
|
||||
if (torrent.type === Type.ANIME) {
|
||||
if (torrent.type === TorrentType.ANIME) {
|
||||
// assign episodes as kitsu episodes for anime when no metadata available for imdb mapping
|
||||
files
|
||||
.filter(file => file.season && file.episodes)
|
||||
@@ -364,7 +365,7 @@ function assignKitsuOrImdbEpisodes(torrent, files, metadata) {
|
||||
file.season = undefined;
|
||||
file.episodes = undefined;
|
||||
})
|
||||
if (metadata.type === Type.MOVIE && files.every(file => !file.imdbId)) {
|
||||
if (metadata.type === TorrentType.MOVIE && files.every(file => !file.imdbId)) {
|
||||
// sometimes a movie has episode naming, thus not recognized as a movie and imdbId not assigned
|
||||
files.forEach(file => file.imdbId = metadata.imdbId);
|
||||
}
|
||||
@@ -465,18 +466,18 @@ async function updateToCinemetaMetadata(metadata) {
|
||||
metadata.totalCount = newMetadata.totalCount;
|
||||
return metadata;
|
||||
})
|
||||
.catch(error => console.warn(`Failed ${metadata.imdbId} metadata cinemeta update due: ${error.message}`));
|
||||
.catch(error => logger.warn(`Failed ${metadata.imdbId} metadata cinemeta update due: ${error.message}`));
|
||||
}
|
||||
|
||||
function findMovieImdbId(title) {
|
||||
const parsedTitle = typeof title === 'string' ? parse(title) : title;
|
||||
console.log(`Finding movie imdbId for ${title}`);
|
||||
return imdb_limiter.schedule(() => getImdbId(parsedTitle, Type.MOVIE).catch(() => undefined));
|
||||
logger.debug(`Finding movie imdbId for ${title}`);
|
||||
return imdb_limiter.schedule(() => getImdbId(parsedTitle, TorrentType.MOVIE).catch(() => undefined));
|
||||
}
|
||||
|
||||
function findMovieKitsuId(title) {
|
||||
const parsedTitle = typeof title === 'string' ? parse(title) : title;
|
||||
return getKitsuId(parsedTitle, Type.MOVIE).catch(() => undefined);
|
||||
return getKitsuId(parsedTitle, TorrentType.MOVIE).catch(() => undefined);
|
||||
}
|
||||
|
||||
function isDiskTorrent(contents) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import {cacheTrackers} from "./cache.js";
|
||||
import { trackerConfig } from './config.js';
|
||||
import {logger} from "./logger.js";
|
||||
|
||||
const downloadTrackers = async () => {
|
||||
const response = await axios.get(trackerConfig.TRACKERS_URL);
|
||||
@@ -15,7 +16,7 @@ const downloadTrackers = async () => {
|
||||
urlTrackers = urlTrackers.filter(line => !line.startsWith('udp://'));
|
||||
}
|
||||
|
||||
console.log(`Trackers updated at ${Date.now()}: ${urlTrackers.length} trackers`);
|
||||
logger.info(`Trackers updated at ${Date.now()}: ${urlTrackers.length} trackers`);
|
||||
|
||||
return urlTrackers;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
export const Type = {
|
||||
export const TorrentType = {
|
||||
MOVIE: 'movie',
|
||||
SERIES: 'series',
|
||||
ANIME: 'anime',
|
||||
PORN: 'xxx',
|
||||
};
|
||||
|
||||
export const CacheType = {
|
||||
MEMORY: 'memory',
|
||||
MONGODB: 'mongodb',
|
||||
};
|
||||
Reference in New Issue
Block a user