Skip to content

Commit

Permalink
Fix: use the translations to convert show name/overview to english (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbackas committed Nov 18, 2024
1 parent c3da678 commit f56b03a
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 83 deletions.
40 changes: 20 additions & 20 deletions release.config.cjs
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
/* eslint-disable no-template-curly-in-string */
/**
* @typedef {import('semantic-release').Options} Options
*/
*/

/** @type {Options} */
module.exports = {
branches: ['main'],
tagFormat: '${version}',
repositoryUrl: 'https://github.com/cbackas/TVBot',
branches: ["main"],
tagFormat: "${version}",
repositoryUrl: "https://github.com/cbackas/TVBot",
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
'@semantic-release/github',
"@semantic-release/github",
{
failTitle: false,
failComment: false,
labels: false,
releasedLabels: false
}
releasedLabels: false,
},
],
[
'@codedependant/semantic-release-docker',
"@codedependant/semantic-release-docker",
{
dockerTags: ['{{version}}', 'latest'],
dockerFile: 'Dockerfile',
dockerPlatform: ['linux/amd64', 'linux/arm64'],
dockerRegistry: 'ghcr.io',
dockerProject: 'cbackas',
dockerImage: 'tvbot'
}
]
dockerTags: ["{{version}}", "latest"],
dockerFile: "Dockerfile",
dockerPlatform: ["linux/amd64", "linux/arm64"],
dockerRegistry: "ghcr.io",
dockerProject: "cbackas",
dockerImage: "tvbot",
},
],
],
preset: 'eslint'
}
preset: "eslint",
};
7 changes: 5 additions & 2 deletions src/interfaces/tvdb.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -844,15 +844,18 @@ export interface Translation {
tagline?: string;
}

export type NameTranslation = Omit<Translation, "overview">
export type OverviewTranslation = Omit<Translation, "name">

/** translation simple record */
export interface TranslationSimple {
language?: string;
}

/** translation extended record */
export interface TranslationExtended {
nameTranslations?: Translation[];
overviewTranslations?: Translation[];
nameTranslations?: NameTranslation[];
overviewTranslations?: OverviewTranslation[];
alias?: string[];
}

Expand Down
185 changes: 124 additions & 61 deletions src/lib/tvdb.ts
Original file line number Diff line number Diff line change
@@ -1,117 +1,180 @@
import axios, { AxiosError, type AxiosRequestConfig } from 'axios'
import { type SearchByRemoteIdResult, type SearchResult, type SeriesExtendedRecord } from '../interfaces/tvdb.generated'
import axios, { AxiosError, type AxiosRequestConfig } from "axios";
import {
type SearchByRemoteIdResult,
type SearchResult,
type SeriesExtendedRecord,
} from "../interfaces/tvdb.generated";

if (process.env.TVDB_API_KEY === undefined) throw new Error('TVDB_API_KEY is not defined')
if (process.env.TVDB_USER_PIN === undefined) throw new Error('TVDB_USER_PIN is not defined')
if (process.env.TVDB_API_KEY === undefined) {
throw new Error("TVDB_API_KEY is not defined");
}
if (process.env.TVDB_USER_PIN === undefined) {
throw new Error("TVDB_USER_PIN is not defined");
}

let token: string | undefined
let token: string | undefined;

async function getToken (): Promise<typeof token> {
if (token != null) return token
async function getToken(): Promise<typeof token> {
if (token != null) return token;

try {
const response = await axios.post('https://api4.thetvdb.com/v4/login', {
const response = await axios.post("https://api4.thetvdb.com/v4/login", {
apikey: process.env.TVDB_API_KEY,
pin: process.env.TVDB_USER_PIN
})
pin: process.env.TVDB_USER_PIN,
});

token = response.data.data.token
return response.data.data.token
token = response.data.data.token;
return response.data.data.token;
} catch (error) {
logPossibleAxiosError(error, 'Getting TVDB Token')
logPossibleAxiosError(error, "Getting TVDB Token");
}
return undefined
return undefined;
}

function logPossibleAxiosError (error: unknown, errorPrefix: string): void {
function logPossibleAxiosError(error: unknown, errorPrefix: string): void {
if (error instanceof AxiosError) {
console.error(`Error ${errorPrefix}:`, {
url: error.config?.url,
code: error.code,
data: error.response?.data,
status: error.response?.status
})
status: error.response?.status,
});
} else {
console.error(`Unexpected Error ${errorPrefix}:`, error)
console.error(`Unexpected Error ${errorPrefix}:`, error);
}
}

async function axiosOptions (): Promise<AxiosRequestConfig<any>> {
const token = await getToken()
if (token == null) throw new Error("Failed to get TVDB token, couldn't build axios options")
async function axiosOptions(): Promise<AxiosRequestConfig<any>> {
const token = await getToken();
if (token == null) {
throw new Error("Failed to get TVDB token, couldn't build axios options");
}
return {
baseURL: 'https://api4.thetvdb.com/v4',
baseURL: "https://api4.thetvdb.com/v4",
headers: {
Authorization: `Bearer ${token}`
}
}
Authorization: `Bearer ${token}`,
},
};
}

export async function getSeriesByImdbId (imdbId: string): Promise<SeriesExtendedRecord | undefined> {
const data = await searchSeriesByImdbId(imdbId)
export async function getSeriesByImdbId(
imdbId: string,
): Promise<SeriesExtendedRecord | undefined> {
const data = await searchSeriesByImdbId(imdbId);

if (data == null) {
return undefined
} else if (data?.series?.id != null) {
return await getSeries(data?.series.id)
return undefined;
}

let series: SeriesExtendedRecord | undefined;
if (data?.series?.id != null) {
series = await getSeries(data?.series.id);
} else if (data?.season?.seriesId != null) {
return await getSeries(data?.season?.seriesId)
series = await getSeries(data?.season?.seriesId);
}

if (
series?.originalLanguage == null || series.originalLanguage === "eng"
) return series;

return translateSeries(series);
}

function translateSeries(series: SeriesExtendedRecord): SeriesExtendedRecord {
if (series.translations == null) {
console.warn(
`Series ${series.id} has a non-english original language but no translations`,
);
return series;
}

const { nameTranslations, overviewTranslations } = series.translations;
const englishName = nameTranslations?.find((t) => t.language === "eng")?.name;
if (englishName != null) {
series.name = englishName;
} else {
console.warn(
`Series ${series.id} has a non-english original language but no english name translation`,
);
}
const englishOverview = overviewTranslations?.find((t) =>
t.language === "eng"
)?.overview;
if (englishOverview != null) {
series.overview = englishOverview;
} else {
console.warn(
`Series ${series.id} has a non-english original language but no english overview translation`,
);
}

return series;
}

export async function getSeries (tvdbId: number): Promise<SeriesExtendedRecord | undefined> {
export async function getSeries(
tvdbId: number,
): Promise<SeriesExtendedRecord | undefined> {
try {
const options = await axiosOptions()
const options = await axiosOptions();
const response = await axios.get<{
data?: SeriesExtendedRecord
data?: SeriesExtendedRecord;
}>(`/series/${tvdbId}/extended`, {
...options,
headers: options.headers,
params: {
short: true,
meta: 'episodes'
}
})
meta: "episodes,translations",
},
});

return response.data?.data
return response.data?.data;
} catch (error) {
logPossibleAxiosError(error, 'Getting Extended Show Data')
logPossibleAxiosError(error, "Getting Extended Show Data");
}
return undefined
return undefined;
}

async function searchSeriesByImdbId (imdbId: string): Promise<SearchByRemoteIdResult | undefined> {
async function searchSeriesByImdbId(
imdbId: string,
): Promise<SearchByRemoteIdResult | undefined> {
try {
const options = await axiosOptions()
const options = await axiosOptions();
const response = await axios.get<{
data?: SearchByRemoteIdResult[]
}>(`/search/remoteid/${imdbId}`, options)
data?: SearchByRemoteIdResult[];
}>(`/search/remoteid/${imdbId}`, options);

return response.data.data?.at(0)
return response.data.data?.at(0);
} catch (error) {
logPossibleAxiosError(error, 'Searching Series by IMDB ID')
logPossibleAxiosError(error, "Searching Series by IMDB ID");
}
return undefined
return undefined;
}

async function searchSeriesByName (query: string): Promise<SearchResult[] | undefined> {
async function searchSeriesByName(
query: string,
): Promise<SearchResult[] | undefined> {
try {
const options = await axiosOptions()
const options = await axiosOptions();
const response = await axios.get<{
data: SearchResult[]
}>(`/search?type=series&limit=10&q=${query}`, options)
data: SearchResult[];
}>(`/search?type=series&limit=10&q=${query}`, options);

const searchResult = response.data?.data
if (searchResult == null || searchResult[0].tvdb_id == null) return undefined
return searchResult
const searchResult = response.data?.data;
if (searchResult == null || searchResult[0].tvdb_id == null) {
return undefined;
}
return searchResult;
} catch (error) {
logPossibleAxiosError(error, 'Searching Series')
logPossibleAxiosError(error, "Searching Series");
}
return undefined
return undefined;
}

export async function getSeriesByName (query: string): Promise<SeriesExtendedRecord | undefined> {
const searchResult = await searchSeriesByName(query)
if (searchResult == null || searchResult[0].tvdb_id == null) return undefined
const series = await getSeries(parseInt(searchResult[0].tvdb_id))
return series
export async function getSeriesByName(
query: string,
): Promise<SeriesExtendedRecord | undefined> {
const searchResult = await searchSeriesByName(query);
if (searchResult == null || searchResult[0].tvdb_id == null) return undefined;
const series = await getSeries(parseInt(searchResult[0].tvdb_id));
return series;
}

0 comments on commit f56b03a

Please sign in to comment.