From e701f26caa2632d3fdcb3b320c22b922b72a2ed4 Mon Sep 17 00:00:00 2001 From: Cyberistic Date: Tue, 24 Feb 2026 04:33:35 +0300 Subject: [PATCH] fix(audio): verse recitation URLs should return absolute paths, not relative - Added audioUrl field to VerseRecitation type for absolute URLs - Added normalizeVerseRecitations() helper to convert relative paths to absolute - Updated mocks to include relative URL examples - Added tests verifying URL normalization Verse recitation methods now return both url (relative) and audioUrl (absolute) for consistency with chapter recitations and immediate usability. --- packages/api/mocks/handlers.ts | 2 ++ packages/api/src/sdk/audio.ts | 37 +++++++++++++++++++++++-- packages/api/src/types/api/AudioData.ts | 3 ++ packages/api/test/audio.test.ts | 30 ++++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/packages/api/mocks/handlers.ts b/packages/api/mocks/handlers.ts index 8b99088..da04a28 100644 --- a/packages/api/mocks/handlers.ts +++ b/packages/api/mocks/handlers.ts @@ -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, @@ -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, diff --git a/packages/api/src/sdk/audio.ts b/packages/api/src/sdk/audio.ts index f499d94..23063e8 100644 --- a/packages/api/src/sdk/audio.ts +++ b/packages/api/src/sdk/audio.ts @@ -17,6 +17,33 @@ type GetVerseRecitationOptions = BaseApiParams & { fields?: Partial>; }; +/** + * 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}`; + + return { + ...file, + audioUrl: absoluteUrl, + }; + }); +} + /** * Audio API methods */ @@ -86,7 +113,10 @@ export class QuranAudio { options, ); - return data; + return { + ...data, + audioFiles: normalizeVerseRecitations(data.audioFiles), + }; } /** @@ -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), + }; } } diff --git a/packages/api/src/types/api/AudioData.ts b/packages/api/src/types/api/AudioData.ts index 988a3b5..6eba63d 100644 --- a/packages/api/src/types/api/AudioData.ts +++ b/packages/api/src/types/api/AudioData.ts @@ -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") */ + audioUrl: string; id?: number; chapterId?: number; diff --git a/packages/api/test/audio.test.ts b/packages/api/test/audio.test.ts index a303947..7c745ce 100644 --- a/packages/api/test/audio.test.ts +++ b/packages/api/test/audio.test.ts @@ -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 () => { @@ -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 () => {