Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 0 additions & 45 deletions deno.lock

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

1 change: 0 additions & 1 deletion import_map.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"@prisma/client/runtime/library": "npm:@prisma/client/runtime/library",
"npm:@prisma/client": "npm:@prisma/client@6.6.0",
"npm:prisma": "npm:prisma@^6.6.0",
"npm:axios": "npm:axios@^1.4.0",
"npm:city-timezones": "npm:city-timezones@^1.2.1",
"npm:moment-timezone": "npm:moment-timezone@^0.5.43",
"npm:parse-url": "npm:parse-url@^8.1.0",
Expand Down
145 changes: 84 additions & 61 deletions src/lib/tvdb.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,39 @@
import axios, { AxiosError, type AxiosRequestConfig } from "npm:axios"
import {
type SearchByRemoteIdResult,
type SearchResult,
type SeriesExtendedRecord,
} from "interfaces/tvdb.generated.ts"
import { getEnv } from "lib/env.ts"

let token: string | undefined
const baseURL = "https://api4.thetvdb.com/v4" as const

async function getToken(): Promise<typeof token> {
if (token != null) return token
let token: string | undefined

try {
const response = await axios.post("https://api4.thetvdb.com/v4/login", {
apikey: getEnv("TVDB_API_KEY"),
pin: getEnv("TVDB_USER_PIN"),
async function getAuthToken(): Promise<{ Authorization: string }> {
Copy link

Copilot AI May 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getAuthToken function does not currently handle the successful response case by extracting the token from the response and returning it. Update the function to parse the JSON response, assign the token, and return { Authorization: Bearer ${token} }.

Copilot uses AI. Check for mistakes.
if (token == null) {
const response = await fetch(`${baseURL}/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
apikey: getEnv("TVDB_API_KEY"),
pin: getEnv("TVDB_USER_PIN"),
}),
})

token = response.data.data.token
return response.data.data.token
} catch (error) {
logPossibleAxiosError(error, "Getting TVDB Token")
}
return undefined
}
if (!response.ok) {
throw new Error("Failed to get TVDB token", {
cause: await response.text(),
})
}

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,
})
} else {
console.error(`Unexpected Error ${errorPrefix}:`, error)
const data = await response.json()
if (!data?.data?.token) {
throw new Error("Invalid API response: Missing token")
}
token = data.data.token
}
}

async function axiosOptions(): Promise<AxiosRequestConfig> {
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",
headers: {
Authorization: `Bearer ${token}`,
},
}
return { Authorization: `Bearer ${token}` }
}

export async function getSeriesByImdbId(
Expand Down Expand Up @@ -109,21 +94,30 @@ export async function getSeries(
tvdbId: number,
): Promise<SeriesExtendedRecord | undefined> {
try {
const options = await axiosOptions()
const response = await axios.get<{
data?: SeriesExtendedRecord
}>(`/series/${tvdbId}/extended`, {
...options,
headers: options.headers,
params: {
short: true,
meta: "episodes,translations",
},
const params = new URLSearchParams({
short: "true",
meta: "episodes,translations",
})
const response = await fetch(
`${baseURL}/series/${tvdbId}/extended?${params}`,
{
headers: await getAuthToken(),
},
)

if (!response.ok) {
console.error(`Error Getting Extended Show Data:`, {
url: response.url,
status: response.status,
data: await response.text(),
})
return undefined
}

return response.data?.data
const data: { data?: SeriesExtendedRecord } = await response.json()
return data.data
} catch (error) {
logPossibleAxiosError(error, "Getting Extended Show Data")
console.error(`Unexpected Error Getting Extended Show Data:`, error)
}
return undefined
}
Expand All @@ -132,14 +126,26 @@ async function searchSeriesByImdbId(
imdbId: string,
): Promise<SearchByRemoteIdResult | undefined> {
try {
const options = await axiosOptions()
const response = await axios.get<{
data?: SearchByRemoteIdResult[]
}>(`/search/remoteid/${imdbId}`, options)
const response = await fetch(
`${baseURL}/search/remoteid/${imdbId}`,
{
headers: await getAuthToken(),
},
)

if (!response.ok) {
console.error(`Error Searching Series by IMDB ID:`, {
url: response.url,
status: response.status,
data: await response.text(),
})
return undefined
}

return response.data.data?.at(0)
const data: { data?: SearchByRemoteIdResult[] } = await response.json()
return data.data?.at(0)
} catch (error) {
logPossibleAxiosError(error, "Searching Series by IMDB ID")
console.error(`Unexpected Error Searching Series by IMDB ID:`, error)
}
return undefined
}
Expand All @@ -148,18 +154,35 @@ async function searchSeriesByName(
query: string,
): Promise<SearchResult[] | undefined> {
try {
const options = await axiosOptions()
const response = await axios.get<{
data: SearchResult[]
}>(`/search?type=series&limit=10&q=${query}`, options)
const params = new URLSearchParams({
type: "series",
limit: "10",
q: query,
})
const response = await fetch(
`${baseURL}/search?${params}`,
{
headers: await getAuthToken(),
},
)

if (!response.ok) {
console.error(`Error Searching Series:`, {
url: response.url,
status: response.status,
data: await response.text(),
})
return undefined
}

const searchResult = response.data?.data
const data: { data: SearchResult[] } = await response.json()
const searchResult = data?.data
if (searchResult == null || searchResult[0].tvdb_id == null) {
return undefined
}
return searchResult
} catch (error) {
logPossibleAxiosError(error, "Searching Series")
console.error(`Unexpected Error Searching Series:`, error)
}
return undefined
}
Expand Down
Loading