Skip to content

Commit

Permalink
Add TMDB as info provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Eltik committed Sep 20, 2023
1 parent 42d47f3 commit d7fbe38
Show file tree
Hide file tree
Showing 10 changed files with 418 additions and 282 deletions.
6 changes: 4 additions & 2 deletions anify-backend/src/database/impl/misc/stats.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { db } from "../..";
import { Anime, Manga } from "../../../types/types";

export const stats = async (): Promise<{ anime: number; manga: number; skipTimes: number; apiKeys: number } | undefined> => {
export const stats = async (): Promise<{ anime: number; manga: number; novels: number; skipTimes: number; apiKeys: number } | undefined> => {
const anime = await db.query("SELECT COUNT(*) FROM anime").get();
const manga = await db.query("SELECT COUNT(*) FROM manga").get();
const manga = await db.query(`SELECT COUNT(*) FROM manga WHERE "format" IN ('MANGA', 'ONE_SHOT')`).get();
const novels = await db.query(`SELECT COUNT(*) FROM manga WHERE "format" IN ('NOVEL')`).get();
const skipTimes = await db.query("SELECT COUNT(*) FROM skipTimes").get();
const apiKeys = await db.query("SELECT COUNT(*) FROM apiKey").get();

return {
anime: Object.values(anime ?? {})[0],
manga: Object.values(manga ?? {})[0],
novels: Object.values(novels ?? {})[0],
skipTimes: Object.values(skipTimes ?? {})[0],
apiKeys: Object.values(apiKeys ?? {})[0],
};
Expand Down
6 changes: 5 additions & 1 deletion anify-backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ import emitter, { Events } from "./lib";
import { get } from "./database/impl/modify/get";
import queues from "./worker";
import { start } from "./server";
import { infoProviders } from "./mappings";

before().then(async (_) => {
await start();
//await start();
await get("147103").then(async (data) => {
await infoProviders.tmdb.info(data!).then(console.log);
});
});

async function before() {
Expand Down
216 changes: 0 additions & 216 deletions anify-backend/src/mappings/impl/information/anilist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,222 +18,6 @@ export default class AniList extends InformationProvider<Anime | Manga, AnimeInf
return ["synonyms", "genres", "tags", "artwork", "characters"];
}

override async search(query: string, type: Type, formats: Format[], page?: number, perPage?: number): Promise<AnimeInfo[] | MangaInfo[] | undefined> {
const aniListArgs = {
query: `
query($page: Int, $perPage: Int, $search: String, $type: MediaType, $format: [MediaFormat]) {
Page(page: $page, perPage: $perPage) {
pageInfo {
total
currentPage
lastPage
hasNextPage
perPage
}
media(type: $type, format_in: $format, search: $search) {
${this.query}
}
}
}
`,
variables: {
search: query,
type: type,
format: formats,
page: page ? page : 0,
perPage: perPage ? perPage : 15,
},
};
const req = await this.request(
this.api,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
origin: "graphql.anilist.co",
},
body: JSON.stringify(aniListArgs),
},
true,
);
const json = await req?.json();
const media = json.data.Page.media;

if (type === Type.ANIME) {
return media.map((data: Media) => {
const artwork: Artwork[] = [];

if (data.coverImage.large)
artwork.push({
type: "poster",
img: data.coverImage.large,
providerId: this.id,
});
if (data.coverImage.extraLarge)
artwork.push({
type: "poster",
img: data.coverImage.extraLarge,
providerId: this.id,
});
if (data.bannerImage)
artwork.push({
type: "banner",
img: data.bannerImage,
providerId: this.id,
});

const characters: Character[] = [];
const relations: Relations[] = [];

for (const character of data.characters.edges) {
if (characters.length > 10) break;
const aliases: string[] = [];

for (const alias of character.node.name.alternative) {
aliases.push(alias);
}
aliases.push(character.node.name.full);

characters.push({
voiceActor: {
name: character.voiceActors[0]?.name?.full ?? null,
image: character.voiceActors[0]?.image?.large ?? null,
},
image: character.node.image.large,
name: character.node.name.full,
});
}

for (const relation of data.relations.edges) {
relations.push({
id: String(relation.node.id),
format: relation.node.format,
relationType: relation.relationType,
title: relation.node.title,
type: relation.node.type,
});
}

return {
id: String(data.id),
title: {
english: data.title.english ?? null,
romaji: data.title.romaji ?? null,
native: data.title.native ?? null,
},
trailer: data.trailer?.id ? `https://www.youtube.com/watch?v=${data.trailer.id}` : null,
currentEpisode: data.status === MediaStatus.FINISHED || data.status === MediaStatus.CANCELLED ? data.episodes ?? 0 : 0,
duration: data.duration ?? null,
coverImage: data.coverImage.extraLarge ?? null,
bannerImage: data.bannerImage ?? null,
popularity: Number(data.popularity),
synonyms: data.synonyms ?? [],
totalEpisodes: data.episodes ?? 0,
color: null,
status: data.status,
season: data.season as Season,
genres: (data.genres as Genres[]) ?? [],
rating: data.meanScore ? data.meanScore / 10 : null,
description: data.description ?? null,
format: data.format,
year: data.seasonYear ?? data.startDate?.year ?? null,
type: data.type,
countryOfOrigin: data.countryOfOrigin ?? null,
tags: data.tags.map((tag) => {
return tag.name;
}),
artwork: artwork,
relations: relations,
characters: characters,
} as AnimeInfo;
});
} else {
return media.map((data: Media) => {
const artwork: Artwork[] = [];

if (data.coverImage.large)
artwork.push({
type: "poster",
img: data.coverImage.large,
providerId: this.id,
});
if (data.coverImage.extraLarge)
artwork.push({
type: "poster",
img: data.coverImage.extraLarge,
providerId: this.id,
});
if (data.bannerImage)
artwork.push({
type: "banner",
img: data.bannerImage,
providerId: this.id,
});

const characters: Character[] = [];
const relations: Relations[] = [];

for (const character of data.characters.edges) {
if (characters.length > 10) break;
const aliases: string[] = [];

for (const alias of character.node.name.alternative) {
aliases.push(alias);
}
aliases.push(character.node.name.full);

characters.push({
voiceActor: {
name: character.voiceActors[0]?.name?.full ?? null,
image: character.voiceActors[0]?.image?.large ?? null,
},
image: character.node.image.large,
name: character.node.name.full,
});
}

for (const relation of data.relations.edges) {
relations.push({
id: String(relation.node.id),
format: relation.node.format,
relationType: relation.relationType,
title: relation.node.title,
type: relation.node.type,
});
}

return {
id: String(data.id),
title: {
english: data.title.english ?? null,
romaji: data.title.romaji ?? null,
native: data.title.native ?? null,
},
coverImage: data.coverImage.extraLarge ?? null,
bannerImage: data.bannerImage ?? null,
popularity: Number(data.popularity),
synonyms: data.synonyms ?? [],
totalChapters: data.chapters ?? 0,
totalVolumes: data.volumes ?? 0,
color: null,
status: data.status,
genres: (data.genres as Genres[]) ?? [],
rating: data.meanScore ? data.meanScore / 10 : null,
description: data.description ?? null,
format: data.format,
year: data.seasonYear ?? data.startDate?.year ?? null,
type: data.type,
countryOfOrigin: data.countryOfOrigin ?? null,
tags: data.tags.map((tag) => tag.name),
artwork: artwork,
characters: characters,
relations: relations,
} as MangaInfo;
});
}
}

override async info(media: Anime | Manga): Promise<AnimeInfo | MangaInfo | undefined> {
const anilistId = media.mappings.find((data) => {
return data.providerId === "anilist";
Expand Down
8 changes: 4 additions & 4 deletions anify-backend/src/mappings/impl/information/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Http from "../../../helper/request";
import { Format, ProviderType, Type } from "../../../types/enums";
import { Anime, AnimeInfo, Manga, MangaInfo, MediaInfoKeys } from "../../../types/types";
import { Anime, AnimeInfo, Chapter, Episode, Manga, MangaInfo, MediaInfoKeys } from "../../../types/types";

export default abstract class InformationProvider<T extends Anime | Manga, U extends AnimeInfo | MangaInfo> {
abstract id: string;
Expand All @@ -10,11 +10,11 @@ export default abstract class InformationProvider<T extends Anime | Manga, U ext
public customProxy: string | undefined;
public preferredTitle: "english" | "romaji" | "native" = "english";

async search(query: string, type: Type, formats: Format[]): Promise<U[] | undefined> {
return [];
async info(media: T): Promise<U | undefined> {
return undefined;
}

async info(media: T): Promise<U | undefined> {
async fetchContentData(media: T): Promise<Episode[] | Chapter[] | undefined> {
return undefined;
}

Expand Down
72 changes: 72 additions & 0 deletions anify-backend/src/mappings/impl/information/tmdb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import InformationProvider from ".";
import { Format, Season } from "../../../types/enums";
import { Anime, AnimeInfo, Character, Manga, MangaInfo, MediaInfoKeys } from "../../../types/types";

export default class TMDB extends InformationProvider<Anime | Manga, AnimeInfo | MangaInfo> {
override id = "tmdb";
override url = "https://themoviedb.org";

private api = "https://api.themoviedb.org/3";
private apiKey = "5201b54eb0968700e693a30576d7d4dc";

override get priorityArea(): MediaInfoKeys[] {
return ["description"];
}

override get sharedArea(): MediaInfoKeys[] {
return ["genres"];
}

override async info(media: Anime | Manga): Promise<AnimeInfo | MangaInfo | undefined> {
const tmdbId = media.mappings.find((data) => {
return data.providerId === "tmdb";
})?.id;

if (!tmdbId) return undefined;

const data: Response | undefined = await this.request(`${this.api}${tmdbId}?api_key=${this.apiKey}`).catch(() => {
return undefined;
});

if (!data) return undefined;

if (data.ok) {
const info = await data.json();

return {
id: tmdbId,
title: {
english: info.name,
romaji: null,
native: info.original_name,
},
currentEpisode: info.last_episode_to_air?.episode_number,
trailer: null,
duration: info.episode_run_time[0] ?? null,
color: null,
bannerImage: info.backdrop_path ? `https://image.tmdb.org/t/p/w500${info.backdrop_path}` : null,
coverImage: info.poster_path ? `https://image.tmdb.org/t/p/w500${info.poster_path}` : null,
status: null,
format: Format.UNKNOWN,
season: Season.UNKNOWN,
synonyms: [],
description: info.overview,
year: info.first_air_date ? new Date(info.first_air_date).getFullYear() : 0,
totalEpisodes: info.number_of_episodes,
genres: info.genres?.map((genre: { id: number; name: string }) => genre.name),
rating: info.vote_average,
popularity: info.popularity,
countryOfOrigin: info.origin_country[0] ?? null,
tags: [],
relations: [],
artwork: [],
characters: [],
totalChapters: null,
totalVolumes: null,
type: media.type,
};
}

return undefined;
}
}
2 changes: 1 addition & 1 deletion anify-backend/src/mappings/impl/information/tvdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default class TVDB extends InformationProvider<Anime | Manga, AnimeInfo |
const coverImage = !hasPrequelRelation ? coverImages[0]?.image ?? media.coverImage ?? null : media.coverImage ?? null;

return {
id: media.id,
id: tvdbId,
title: {
english: null,
romaji: null,
Expand Down
Loading

0 comments on commit d7fbe38

Please sign in to comment.