Skip to content

Commit

Permalink
mediafetcher-simplify (#38)
Browse files Browse the repository at this point in the history
simplify mediafetcher
  • Loading branch information
wydengyre authored Jan 29, 2024
1 parent 5242f20 commit e829c81
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 53 deletions.
17 changes: 7 additions & 10 deletions lib/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PromisePool } from "@supercharge/promise-pool";
import { z } from "zod";
import { Podcast } from "../build/podcast/index.js";
import { NotFoundError } from "./error.js";
import { Fetcher as MediaFetcher } from "./media.js";
import { fetchInfo } from "./media.js";

const cardSchema = z.object({
episode_title: z.string(),
Expand Down Expand Up @@ -38,11 +38,10 @@ export async function convertFeed(
c: ConvertConf,
relUrl: string,
): Promise<string> {
const mediaFetcher = new MediaFetcher(c.fetch);
const convertor = new Convertor({
raiBaseUrl: c.raiBaseUrl,
poolSize: c.poolSize,
fetcher: mediaFetcher,
fetch: c.fetch,
});
const feedJson = await fetchFeed(c, relUrl);
return convertor.convert(feedJson);
Expand Down Expand Up @@ -73,18 +72,18 @@ async function fetchFeed(
type ConvertorConf = {
raiBaseUrl: URL;
poolSize: number;
fetcher: MediaFetcher;
fetch: typeof fetch;
};

class Convertor {
readonly #raiBaseUrl: URL;
readonly #poolSize: number;
readonly #fetcher: MediaFetcher;
readonly #fetch: typeof fetch;

constructor({ raiBaseUrl, poolSize, fetcher }: ConvertorConf) {
constructor({ raiBaseUrl, poolSize, fetch }: ConvertorConf) {
this.#raiBaseUrl = raiBaseUrl;
this.#poolSize = poolSize;
this.#fetcher = fetcher;
this.#fetch = fetch;
}

// TODO: feedUrl, siteUrl
Expand Down Expand Up @@ -126,9 +125,7 @@ class Convertor {
async convertCard(card: Card) {
const imageUrl = new URL(card.image, this.#raiBaseUrl).toString();
const date = new Date(card.track_info.date);
const mediaInfo = await this.#fetcher.fetchInfo(
card.downloadable_audio.url,
);
const mediaInfo = await fetchInfo(this.#fetch, card.downloadable_audio.url);
const url = mediaInfo.url.toString();
return {
title: card.episode_title,
Expand Down
10 changes: 4 additions & 6 deletions lib/media.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { strict as assert } from "node:assert";
import test, { before } from "node:test";
import * as logger from "./logger.js";
import { Fetcher } from "./media.js";
import test from "node:test";
import { fetchInfo } from "./media.js";

test("media", (t) => {
return t.test(fetchInfoSuccess);
Expand All @@ -11,7 +10,7 @@ async function fetchInfoSuccess() {
const url =
"https://mediapolisvod.rai.it/relinker/relinkerServlet.htm?cont=PE3wc6etKfssSlashNKfaoXssSlashpWcgeeqqEEqualeeqqEEqual";
const mediaUrl = new URL("https://test.dev/foo.mp3");
const f: typeof fetch = async () =>
const fetch: typeof globalThis.fetch = async () =>
({
url: mediaUrl.toString(),
status: 200,
Expand All @@ -20,8 +19,7 @@ async function fetchInfoSuccess() {
"content-length": "123456789",
}),
}) as Response;
const fetcher = new Fetcher(f);
const info = await fetcher.fetchInfo(url);
const info = await fetchInfo(fetch, url);
assert.deepStrictEqual(info, {
url: mediaUrl,
size: 123456789,
Expand Down
72 changes: 35 additions & 37 deletions lib/media.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,49 @@
export type MediaUrl = URL;
export { MediaInfo, MediaUrl, fetchInfo };

export type MediaInfo = {
const chromeAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/[latest_version] Safari/537.36";

type MediaUrl = URL;

type MediaInfo = {
url: URL;
size: number;
};

const chromeAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/[latest_version] Safari/537.36";
const relinkerRe = /^\?cont=[a-zA-Z0-9]+$/;
const expectedContentType = "audio/mpeg";
export class Fetcher {
readonly #fetch: typeof fetch;

constructor(f: typeof fetch = fetch.bind(globalThis)) {
this.#fetch = f;
async function fetchInfo(
fetch: typeof globalThis.fetch,
url: string,
): Promise<MediaInfo> {
const mediaUrl = mkMediaUrl(url);
if (typeof mediaUrl === "string") {
const err = `Invalid URL (${url}): ${mediaUrl}`;
throw new Error(err);
}

async fetchInfo(url: string): Promise<MediaInfo> {
const mediaUrl = mkMediaUrl(url);
if (typeof mediaUrl === "string") {
const err = `Invalid URL (${url}): ${mediaUrl}`;
throw new Error(err);
}

const chromeHeadInit: RequestInit = {
method: "HEAD",
headers: {
"User-Agent": chromeAgent,
},
};
const resp = await this.#fetch(url, chromeHeadInit);

const contentType = resp.headers.get("content-type");
if (contentType !== expectedContentType) {
throw new Error(
`Invalid content type: ${contentType}, wanted ${expectedContentType}`,
);
}
const chromeHeadInit: RequestInit = {
method: "HEAD",
headers: {
"User-Agent": chromeAgent,
},
};
const resp = await fetch(url, chromeHeadInit);

const contentLength = resp.headers.get("content-length");
const length = Number(contentLength);
if (Number.isNaN(length)) {
throw new Error(`Invalid content length: ${contentLength}`);
}
const expectedContentType = "audio/mpeg";
const contentType = resp.headers.get("content-type");
if (contentType !== expectedContentType) {
throw new Error(
`Invalid content type: ${contentType}, wanted ${expectedContentType}`,
);
}

return { url: new URL(resp.url), size: length };
const contentLength = resp.headers.get("content-length");
const length = Number(contentLength);
if (Number.isNaN(length)) {
throw new Error(`Invalid content length: ${contentLength}`);
}

return { url: new URL(resp.url), size: length };
}

function mkMediaUrl(urlStr: string): MediaUrl | string {
Expand Down

0 comments on commit e829c81

Please sign in to comment.