Skip to content
Open
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
2 changes: 2 additions & 0 deletions packages/api/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ export const handlers = [
id: 1,
chapter_id: params.chapterId,
verse_key: "1:1",
url: "AbdulBaset/Murattal/mp3/001001.mp3",
file_size: 123456,
format: "mp3",
recitation_id: params.recitationId,
Expand Down Expand Up @@ -238,6 +239,7 @@ export const handlers = [
{
id: 1,
verse_key: params.key,
url: "AbdulBaset/Murattal/mp3/002255.mp3",
file_size: 123456,
format: "mp3",
recitation_id: params.recitationId,
Expand Down
37 changes: 35 additions & 2 deletions packages/api/src/sdk/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,33 @@ type GetVerseRecitationOptions = BaseApiParams & {
fields?: Partial<Record<VerseRecitationField, boolean>>;
};

/**
* Base URL for verse audio files from Quran.com
* Used to convert relative paths to absolute URLs
*/
const AUDIO_BASE_URL = "https://verses.quran.com";

/**
* Normalize verse recitation data by adding absolute audioUrl
* The API returns relative paths in the 'url' field, but we want
* to provide absolute URLs for consistency with chapter recitations
*/
function normalizeVerseRecitations(
audioFiles: VerseRecitation[],
): VerseRecitation[] {
return audioFiles.map((file) => {
// If url is already absolute, use it; otherwise prepend the base URL
const absoluteUrl = file.url.startsWith("http")
? file.url
: `${AUDIO_BASE_URL}/${file.url}`;
Comment on lines +35 to +38
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The normalization logic has an untested branch when file.url is already absolute (startsWith("http")). Adding a test case for an already-absolute url would help prevent regressions and clarify expected behavior (e.g., whether audioUrl should equal url in that case).

Copilot uses AI. Check for mistakes.

Comment on lines +31 to +39
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

normalizeVerseRecitations is typed as taking/returning VerseRecitation[], but VerseRecitation now requires audioUrl even though the upstream API payload only provides the relative url. This makes the intermediate data.audioFiles type inaccurate and can mislead future changes. Consider introducing a separate "raw" type (e.g., type RawVerseRecitation = Omit<VerseRecitation, "audioUrl">) for the fetch response and keep VerseRecitation for the normalized return type (or make audioUrl optional on the interface if it can be absent).

Copilot uses AI. Check for mistakes.
return {
...file,
audioUrl: absoluteUrl,
};
});
}

/**
* Audio API methods
*/
Expand Down Expand Up @@ -86,7 +113,10 @@ export class QuranAudio {
options,
);

return data;
return {
...data,
audioFiles: normalizeVerseRecitations(data.audioFiles),
};
}

/**
Expand All @@ -109,6 +139,9 @@ export class QuranAudio {
pagination: Pagination;
}>(`/content/api/v4/recitations/${recitationId}/by_ayah/${key}`, options);

return data;
return {
...data,
audioFiles: normalizeVerseRecitations(data.audioFiles),
};
}
}
3 changes: 3 additions & 0 deletions packages/api/src/types/api/AudioData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export interface ChapterRecitation {

export interface VerseRecitation {
verseKey: VerseKey;
/** Relative URL path (e.g., "AbdulBaset/Murattal/mp3/002255.mp3") */
url: string;
/** Absolute URL (e.g., "https://download.quranicaudio.com/qdc/AbdulBaset/Murattal/mp3/002255.mp3") */
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

audioUrl JSDoc example uses the chapter-recitation host (download.quranicaudio.com/qdc/...), but this PR normalizes verse URLs to https://verses.quran.com/.... Update the example (and any wording) so it matches the actual audioUrl being returned for verse recitations.

Suggested change
/** Absolute URL (e.g., "https://download.quranicaudio.com/qdc/AbdulBaset/Murattal/mp3/002255.mp3") */
/** Absolute URL (e.g., "https://verses.quran.com/AbdulBaset/Murattal/mp3/002255.mp3") */

Copilot uses AI. Check for mistakes.
audioUrl: string;

id?: number;
chapterId?: number;
Expand Down
30 changes: 30 additions & 0 deletions packages/api/test/audio.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,21 @@ describe("Audio API", () => {
VALID_RECITATION_ID,
);
expect(response).toBeDefined();
expect(response.audioFiles).toBeInstanceOf(Array);
});

it("should normalize relative URLs to absolute audioUrl", async () => {
const response = await testClient.audio.findVerseRecitationsByChapter(
VALID_CHAPTER_ID,
VALID_RECITATION_ID,
);
expect(response.audioFiles[0]).toBeDefined();
// The relative URL from the mock
expect(response.audioFiles[0].url).toBe("AbdulBaset/Murattal/mp3/001001.mp3");
// The normalized absolute URL
expect(response.audioFiles[0].audioUrl).toBe(
"https://verses.quran.com/AbdulBaset/Murattal/mp3/001001.mp3",
);
});

it("should throw error for invalid chapter ID", async () => {
Expand All @@ -108,6 +123,21 @@ describe("Audio API", () => {
VALID_RECITATION_ID,
);
expect(response).toBeDefined();
expect(response.audioFiles).toBeInstanceOf(Array);
});

it("should normalize relative URLs to absolute audioUrl", async () => {
const response = await testClient.audio.findVerseRecitationsByKey(
VALID_VERSE_KEY,
VALID_RECITATION_ID,
);
expect(response.audioFiles[0]).toBeDefined();
// The relative URL from the mock (002255.mp3 for verse 2:255)
expect(response.audioFiles[0].url).toBe("AbdulBaset/Murattal/mp3/002255.mp3");
// The normalized absolute URL
expect(response.audioFiles[0].audioUrl).toBe(
"https://verses.quran.com/AbdulBaset/Murattal/mp3/002255.mp3",
);
});

it("should throw error for invalid verse key", async () => {
Expand Down
Loading