You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
3.9 KiB
153 lines
3.9 KiB
import { ScrapeMedia } from "@movie-web/providers"; |
|
import { nanoid } from "nanoid"; |
|
import { ofetch } from "ofetch"; |
|
import { useCallback } from "react"; |
|
|
|
import { ScrapingItems, ScrapingSegment } from "@/hooks/useProviderScrape"; |
|
import { PlayerMeta } from "@/stores/player/slices/source"; |
|
|
|
// for anybody who cares - these are anonymous metrics. |
|
// They are just used for figuring out if providers are broken or not |
|
const metricsEndpoint = "https://backend.movie-web.app/metrics/providers"; |
|
const captchaMetricsEndpoint = "https://backend.movie-web.app/metrics/captcha"; |
|
const batchId = () => nanoid(32); |
|
|
|
export type ProviderMetric = { |
|
tmdbId: string; |
|
type: string; |
|
title: string; |
|
seasonId?: string; |
|
episodeId?: string; |
|
status: "failed" | "notfound" | "success"; |
|
providerId: string; |
|
embedId?: string; |
|
errorMessage?: string; |
|
fullError?: string; |
|
}; |
|
|
|
function getStackTrace(error: Error, lines: number) { |
|
const topMessage = error.toString(); |
|
const stackTraceLines = (error.stack ?? "").split("\n", lines + 1); |
|
stackTraceLines.pop(); |
|
return `${topMessage}\n\n${stackTraceLines.join("\n")}`; |
|
} |
|
|
|
export async function reportProviders(items: ProviderMetric[]): Promise<void> { |
|
return ofetch(metricsEndpoint, { |
|
method: "POST", |
|
body: { |
|
items, |
|
batchId: batchId(), |
|
}, |
|
}); |
|
} |
|
|
|
const segmentStatusMap: Record< |
|
ScrapingSegment["status"], |
|
ProviderMetric["status"] | null |
|
> = { |
|
success: "success", |
|
notfound: "notfound", |
|
failure: "failed", |
|
pending: null, |
|
waiting: null, |
|
}; |
|
|
|
export function scrapeSourceOutputToProviderMetric( |
|
media: PlayerMeta, |
|
providerId: string, |
|
embedId: string | null, |
|
status: ProviderMetric["status"], |
|
err: unknown | null |
|
): ProviderMetric { |
|
const episodeId = media.episode?.tmdbId; |
|
const seasonId = media.season?.tmdbId; |
|
let error: undefined | Error; |
|
if (err instanceof Error) error = err; |
|
|
|
return { |
|
status, |
|
providerId, |
|
title: media.title, |
|
tmdbId: media.tmdbId, |
|
type: media.type, |
|
embedId: embedId ?? undefined, |
|
episodeId, |
|
seasonId, |
|
errorMessage: error?.message, |
|
fullError: error ? getStackTrace(error, 5) : undefined, |
|
}; |
|
} |
|
|
|
export function scrapeSegmentToProviderMetric( |
|
media: ScrapeMedia, |
|
providerId: string, |
|
segment: ScrapingSegment |
|
): ProviderMetric | null { |
|
const status = segmentStatusMap[segment.status]; |
|
if (!status) return null; |
|
let episodeId: string | undefined; |
|
let seasonId: string | undefined; |
|
if (media.type === "show") { |
|
episodeId = media.episode.tmdbId; |
|
seasonId = media.season.tmdbId; |
|
} |
|
let error: undefined | Error; |
|
if (segment.error instanceof Error) error = segment.error; |
|
|
|
return { |
|
status, |
|
providerId, |
|
title: media.title, |
|
tmdbId: media.tmdbId, |
|
type: media.type, |
|
embedId: segment.embedId, |
|
episodeId, |
|
seasonId, |
|
errorMessage: segment.reason ?? error?.message, |
|
fullError: error ? getStackTrace(error, 5) : undefined, |
|
}; |
|
} |
|
|
|
export function scrapePartsToProviderMetric( |
|
media: ScrapeMedia, |
|
order: ScrapingItems[], |
|
sources: Record<string, ScrapingSegment> |
|
): ProviderMetric[] { |
|
const output: ProviderMetric[] = []; |
|
|
|
order.forEach((orderItem) => { |
|
const source = sources[orderItem.id]; |
|
orderItem.children.forEach((embedId) => { |
|
const embed = sources[embedId]; |
|
if (!embed.embedId) return; |
|
const metric = scrapeSegmentToProviderMetric(media, source.id, embed); |
|
if (!metric) return; |
|
output.push(metric); |
|
}); |
|
|
|
const metric = scrapeSegmentToProviderMetric(media, source.id, source); |
|
if (!metric) return; |
|
output.push(metric); |
|
}); |
|
|
|
return output; |
|
} |
|
|
|
export function useReportProviders() { |
|
const report = useCallback((items: ProviderMetric[]) => { |
|
if (items.length === 0) return; |
|
reportProviders(items).catch(() => {}); |
|
}, []); |
|
|
|
return { report }; |
|
} |
|
|
|
export function reportCaptchaSolve(success: boolean) { |
|
ofetch(captchaMetricsEndpoint, { |
|
method: "POST", |
|
body: { |
|
success, |
|
}, |
|
}).catch(() => {}); |
|
}
|
|
|