mirror of
https://github.com/knightcrawler-stremio/knightcrawler.git
synced 2024-12-20 03:29:51 +00:00
few more metadata tests and a single torrent entry service test for now
This commit is contained in:
@@ -15,7 +15,7 @@ import nameToImdb from 'name-to-imdb';
|
|||||||
|
|
||||||
const CINEMETA_URL = 'https://v3-cinemeta.strem.io';
|
const CINEMETA_URL = 'https://v3-cinemeta.strem.io';
|
||||||
const KITSU_URL = 'https://anime-kitsu.strem.fun';
|
const KITSU_URL = 'https://anime-kitsu.strem.fun';
|
||||||
const TIMEOUT = 20000;
|
const TIMEOUT = 60000;
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class MetadataService implements IMetadataService {
|
export class MetadataService implements IMetadataService {
|
||||||
@@ -33,7 +33,7 @@ export class MetadataService implements IMetadataService {
|
|||||||
const query = encodeURIComponent(key);
|
const query = encodeURIComponent(key);
|
||||||
|
|
||||||
return this.cacheService.cacheWrapKitsuId(key,
|
return this.cacheService.cacheWrapKitsuId(key,
|
||||||
() => axios.get(`${KITSU_URL}/catalog/series/kitsu-anime-list/search=${query}.json`, {timeout: 60000})
|
() => axios.get(`${KITSU_URL}/catalog/series/kitsu-anime-list/search=${query}.json`, {timeout: TIMEOUT})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const body = response.data as IKitsuCatalogJsonResponse;
|
const body = response.data as IKitsuCatalogJsonResponse;
|
||||||
if (body && body.metas && body.metas.length) {
|
if (body && body.metas && body.metas.length) {
|
||||||
@@ -75,14 +75,14 @@ export class MetadataService implements IMetadataService {
|
|||||||
return this.cacheService.cacheWrapMetadata(key.toString(), () => {
|
return this.cacheService.cacheWrapMetadata(key.toString(), () => {
|
||||||
switch (isImdbId) {
|
switch (isImdbId) {
|
||||||
case true:
|
case true:
|
||||||
return this.requestCinemetaMetadata(`${CINEMETA_URL}/meta/imdb/${key}.json`);
|
return this.requestMetadata(`${CINEMETA_URL}/meta/imdb/${key}.json`, this.handleCinemetaResponse);
|
||||||
default:
|
default:
|
||||||
return this.requestKitsuMetadata(`${KITSU_URL}/meta/${metaType}/${key}.json`)
|
return this.requestMetadata(`${KITSU_URL}/meta/${metaType}/${key}.json`, this.handleKitsuResponse)
|
||||||
}})
|
}})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// try different type in case there was a mismatch
|
// try different type in case there was a mismatch
|
||||||
const otherType = metaType === TorrentType.Movie ? TorrentType.Series : TorrentType.Movie;
|
const otherType = metaType === TorrentType.Movie ? TorrentType.Series : TorrentType.Movie;
|
||||||
return this.requestCinemetaMetadata(`${CINEMETA_URL}/meta/${otherType}/${key}.json`)
|
return this.requestMetadata(`${CINEMETA_URL}/meta/${otherType}/${key}.json`, this.handleCinemetaResponse)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw new Error(`failed metadata query ${key} due: ${error.message}`);
|
throw new Error(`failed metadata query ${key} due: ${error.message}`);
|
||||||
@@ -90,12 +90,16 @@ export class MetadataService implements IMetadataService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public isEpisodeImdbId = async (imdbId: string | undefined): Promise<boolean> => {
|
public isEpisodeImdbId = async (imdbId: string | undefined): Promise<boolean> => {
|
||||||
if (!imdbId) {
|
if (!imdbId || !imdbId.toString().match(/^tt\d+$/)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`https://www.imdb.com/title/${imdbId}/`, {timeout: TIMEOUT});
|
||||||
|
return response.data.includes('video.episode');
|
||||||
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return axios.get(`https://www.imdb.com/title/${imdbId}/`, {timeout: 10000})
|
|
||||||
.then(response => !!(response.data && response.data.includes('video.episode')))
|
|
||||||
.catch(() => false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public escapeTitle = (title: string): string => title.toLowerCase()
|
public escapeTitle = (title: string): string => title.toLowerCase()
|
||||||
@@ -108,72 +112,78 @@ export class MetadataService implements IMetadataService {
|
|||||||
.replace(/\s{2,}/, ' ') // replace multiple spaces
|
.replace(/\s{2,}/, ' ') // replace multiple spaces
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
private requestKitsuMetadata = async (url: string): Promise<IMetadataResponse> => {
|
private requestMetadata = async (url: string, handler: (body: unknown) => IMetadataResponse): Promise<IMetadataResponse> => {
|
||||||
const response = await axios.get(url, {timeout: TIMEOUT});
|
try {
|
||||||
const body = response.data;
|
const response = await axios.get(url, { timeout: TIMEOUT });
|
||||||
return this.handleKitsuResponse(body as IKitsuJsonResponse);
|
const body = response.data;
|
||||||
|
return handler(body);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`HTTP error! status: ${error.response?.status}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private requestCinemetaMetadata = async (url: string): Promise<IMetadataResponse> => {
|
private handleCinemetaResponse = (response: unknown): IMetadataResponse => {
|
||||||
const response = await axios.get(url, {timeout: TIMEOUT});
|
const body = response as ICinemetaJsonResponse
|
||||||
const body = response.data;
|
|
||||||
return this.handleCinemetaResponse(body as ICinemetaJsonResponse);
|
return ({
|
||||||
|
imdbId: parseInt(body.meta?.id || '0'),
|
||||||
|
type: body.meta?.type,
|
||||||
|
title: body.meta?.name,
|
||||||
|
year: parseInt(body.meta?.year || '0'),
|
||||||
|
country: body.meta?.country,
|
||||||
|
genres: body.meta?.genres,
|
||||||
|
status: body.meta?.status,
|
||||||
|
videos: body.meta?.videos
|
||||||
|
? body.meta.videos.map(video => ({
|
||||||
|
name: video.name,
|
||||||
|
season: video.season,
|
||||||
|
episode: video.episode,
|
||||||
|
imdbSeason: video.season,
|
||||||
|
imdbEpisode: video.episode,
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
episodeCount: body.meta?.videos
|
||||||
|
? this.getEpisodeCount(body.meta.videos)
|
||||||
|
: [],
|
||||||
|
totalCount: body.meta?.videos
|
||||||
|
? body.meta.videos.filter(
|
||||||
|
entry => entry.season !== 0 && entry.episode !== 0
|
||||||
|
).length
|
||||||
|
: 0,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleCinemetaResponse = (body: ICinemetaJsonResponse): IMetadataResponse => ({
|
private handleKitsuResponse = (response: unknown): IMetadataResponse => {
|
||||||
imdbId: parseInt(body.meta?.id || '0'),
|
const body = response as IKitsuJsonResponse;
|
||||||
type: body.meta?.type,
|
|
||||||
title: body.meta?.name,
|
return ({
|
||||||
year: parseInt(body.meta?.year || '0'),
|
kitsuId: parseInt(body.meta?.kitsu_id || '0'),
|
||||||
country: body.meta?.country,
|
type: body.meta?.type,
|
||||||
genres: body.meta?.genres,
|
title: body.meta?.name,
|
||||||
status: body.meta?.status,
|
year: parseInt(body.meta?.year || '0'),
|
||||||
videos: body.meta?.videos
|
country: body.meta?.country,
|
||||||
? body.meta.videos.map(video => ({
|
genres: body.meta?.genres,
|
||||||
name: video.name,
|
status: body.meta?.status,
|
||||||
season: video.season,
|
videos: body.meta?.videos
|
||||||
episode: video.episode,
|
? body.meta?.videos.map(video => ({
|
||||||
imdbSeason: video.season,
|
name: video.title,
|
||||||
imdbEpisode: video.episode,
|
season: video.season,
|
||||||
}))
|
episode: video.episode,
|
||||||
: [],
|
kitsuId: video.id,
|
||||||
episodeCount: body.meta?.videos
|
kitsuEpisode: video.episode,
|
||||||
? this.getEpisodeCount(body.meta.videos)
|
released: video.released,
|
||||||
: [],
|
}))
|
||||||
totalCount: body.meta?.videos
|
: [],
|
||||||
? body.meta.videos.filter(
|
episodeCount: body.meta?.videos
|
||||||
entry => entry.season !== 0 && entry.episode !== 0
|
? this.getEpisodeCount(body.meta.videos)
|
||||||
).length
|
: [],
|
||||||
: 0,
|
totalCount: body.meta?.videos
|
||||||
});
|
? body.meta.videos.filter(
|
||||||
|
entry => entry.season !== 0 && entry.episode !== 0
|
||||||
private handleKitsuResponse = (body: IKitsuJsonResponse): IMetadataResponse => ({
|
).length
|
||||||
kitsuId: parseInt(body.meta?.kitsu_id || '0'),
|
: 0,
|
||||||
type: body.meta?.type,
|
});
|
||||||
title: body.meta?.name,
|
};
|
||||||
year: parseInt(body.meta?.year || '0'),
|
|
||||||
country: body.meta?.country,
|
|
||||||
genres: body.meta?.genres,
|
|
||||||
status: body.meta?.status,
|
|
||||||
videos: body.meta?.videos
|
|
||||||
? body.meta?.videos.map(video => ({
|
|
||||||
name: video.title,
|
|
||||||
season: video.season,
|
|
||||||
episode: video.episode,
|
|
||||||
kitsuId: video.id,
|
|
||||||
kitsuEpisode: video.episode,
|
|
||||||
released: video.released,
|
|
||||||
}))
|
|
||||||
: [],
|
|
||||||
episodeCount: body.meta?.videos
|
|
||||||
? this.getEpisodeCount(body.meta.videos)
|
|
||||||
: [],
|
|
||||||
totalCount: body.meta?.videos
|
|
||||||
? body.meta.videos.filter(
|
|
||||||
entry => entry.season !== 0 && entry.episode !== 0
|
|
||||||
).length
|
|
||||||
: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
private getEpisodeCount = (videos: ICommonVideoMetadata[]): number[] =>
|
private getEpisodeCount = (videos: ICommonVideoMetadata[]): number[] =>
|
||||||
Object.values(
|
Object.values(
|
||||||
|
|||||||
@@ -79,12 +79,38 @@ describe('MetadataService Tests', () => {
|
|||||||
expect(body.videos.length).toBe(22);
|
expect(body.videos.length).toBe(22);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should check if imdb id is an episode", async () => {
|
it("should get imdb id the flash 2014", async () => {
|
||||||
|
const result = await metadataService.getImdbId({
|
||||||
|
title: 'The Flash',
|
||||||
|
year: 2014,
|
||||||
|
type: 'series'
|
||||||
|
});
|
||||||
|
expect(mockCacheService.cacheWrapImdbId).toHaveBeenCalledWith('the flash_2014_series', expect.any(Function));
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
expect(result).toEqual('tt3107288');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if imdb id is not provided", async () => {
|
||||||
|
const result = await metadataService.isEpisodeImdbId(undefined);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if kitsu id is provided", async () => {
|
||||||
|
const result = await metadataService.isEpisodeImdbId("kitsu:11");
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should escape title naruto, with year", () => {
|
||||||
|
const result = metadataService.escapeTitle('Naruto: Shippuden | 2002');
|
||||||
|
expect(result).toEqual('naruto shippuden 2002');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should check if imdb id is an episode: the flash 1990", async () => {
|
||||||
const result = await metadataService.isEpisodeImdbId('tt0579968');
|
const result = await metadataService.isEpisodeImdbId('tt0579968');
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should escape title", () => {
|
it("should escape title naruto, no year", () => {
|
||||||
const result = metadataService.escapeTitle('Naruto: Shippuden');
|
const result = metadataService.escapeTitle('Naruto: Shippuden');
|
||||||
expect(result).toEqual('naruto shippuden');
|
expect(result).toEqual('naruto shippuden');
|
||||||
});
|
});
|
||||||
|
|||||||
109
src/node/consumer/test/torrent_entries_service.test.ts
Normal file
109
src/node/consumer/test/torrent_entries_service.test.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import "reflect-metadata"; // required
|
||||||
|
import {TorrentType} from "@enums/torrent_types";
|
||||||
|
import {ILoggingService} from "@interfaces/logging_service";
|
||||||
|
import {IMetadataService} from "@interfaces/metadata_service";
|
||||||
|
import {IParsedTorrent} from "@interfaces/parsed_torrent";
|
||||||
|
import {ITorrentFileCollection} from "@interfaces/torrent_file_collection";
|
||||||
|
import {ITorrentFileService} from "@interfaces/torrent_file_service";
|
||||||
|
import {ITorrentSubtitleService} from "@interfaces/torrent_subtitle_service";
|
||||||
|
import {IDatabaseRepository} from "@repository/interfaces/database_repository";
|
||||||
|
import {TorrentEntriesService} from "@services/torrent_entries_service";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jest.mock('@services/logging_service', () => {
|
||||||
|
return {
|
||||||
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
debug: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.mock('@services/torrent_file_service', () => {
|
||||||
|
return {
|
||||||
|
parseTorrentFiles: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.mock('@services/metadata_service', () => {
|
||||||
|
return {
|
||||||
|
getImdbId: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.mock('@services/torrent_subtitle_service', () => {
|
||||||
|
return {
|
||||||
|
assignSubtitles: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.mock('@repository/database_repository', () => {
|
||||||
|
return {
|
||||||
|
createTorrent: jest.fn().mockResolvedValue(undefined),
|
||||||
|
createFile: jest.fn().mockResolvedValue(undefined),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('TorrentEntriesService Tests', () => {
|
||||||
|
let torrentEntriesService: TorrentEntriesService,
|
||||||
|
mockLoggingService: ILoggingService,
|
||||||
|
mockFileService: ITorrentFileService,
|
||||||
|
mockMetadataService: IMetadataService,
|
||||||
|
mockSubtitleService: ITorrentSubtitleService,
|
||||||
|
mockDatabaseRepository: IDatabaseRepository;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockFileService = jest.requireMock<ITorrentFileService>('@services/torrent_file_service');
|
||||||
|
mockMetadataService = jest.requireMock<IMetadataService>('@services/metadata_service');
|
||||||
|
mockSubtitleService = jest.requireMock<ITorrentSubtitleService>('@services/torrent_subtitle_service');
|
||||||
|
mockLoggingService = jest.requireMock<ILoggingService>('@services/logging_service');
|
||||||
|
mockDatabaseRepository = jest.requireMock<IDatabaseRepository>('@repository/database_repository');
|
||||||
|
torrentEntriesService = new TorrentEntriesService(mockMetadataService, mockLoggingService, mockFileService , mockSubtitleService, mockDatabaseRepository);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a torrent entry', async () => {
|
||||||
|
const torrent : IParsedTorrent = {
|
||||||
|
title: 'Test title',
|
||||||
|
provider: 'Test provider',
|
||||||
|
infoHash: 'Test infoHash',
|
||||||
|
type: TorrentType.Movie,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileCollection : ITorrentFileCollection = {
|
||||||
|
videos: [{
|
||||||
|
fileIndex: 0,
|
||||||
|
title: 'Test video',
|
||||||
|
size: 123456,
|
||||||
|
imdbId: 'tt1234567',
|
||||||
|
}],
|
||||||
|
contents: [],
|
||||||
|
subtitles: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileCollectionWithSubtitles : ITorrentFileCollection = {
|
||||||
|
...fileCollection,
|
||||||
|
subtitles: [ {
|
||||||
|
fileId: 0,
|
||||||
|
title: 'Test subtitle',
|
||||||
|
fileIndex: 0,
|
||||||
|
path: 'Test path',
|
||||||
|
infoHash: 'Test infoHash',
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockMetadataService.getImdbId as jest.Mock).mockResolvedValue('tt1234567');
|
||||||
|
(mockFileService.parseTorrentFiles as jest.Mock).mockResolvedValue(fileCollection);
|
||||||
|
(mockSubtitleService.assignSubtitles as jest.Mock).mockResolvedValue(fileCollectionWithSubtitles);
|
||||||
|
(mockDatabaseRepository.createTorrent as jest.Mock).mockResolvedValue(torrent);
|
||||||
|
|
||||||
|
await torrentEntriesService.createTorrentEntry(torrent);
|
||||||
|
|
||||||
|
expect(mockMetadataService.getImdbId).toHaveBeenCalledWith({ title: 'Test title', year: undefined, type: TorrentType.Movie });
|
||||||
|
expect(mockFileService.parseTorrentFiles).toHaveBeenCalledWith(torrent);
|
||||||
|
expect(mockFileService.parseTorrentFiles).toHaveReturnedWith(Promise.resolve(fileCollection));
|
||||||
|
expect(mockSubtitleService.assignSubtitles).toHaveBeenCalledWith(fileCollection);
|
||||||
|
expect(mockSubtitleService.assignSubtitles).toHaveReturnedWith(Promise.resolve(fileCollectionWithSubtitles));
|
||||||
|
expect(mockDatabaseRepository.createTorrent).toHaveBeenCalledWith(expect.objectContaining(torrent));
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user