@saavn-labs/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +339 -0
  3. package/dist/core/models/album.d.ts +32 -0
  4. package/dist/core/models/album.js +1 -0
  5. package/dist/core/models/artist.d.ts +61 -0
  6. package/dist/core/models/artist.js +1 -0
  7. package/dist/core/models/index.d.ts +4 -0
  8. package/dist/core/models/index.js +4 -0
  9. package/dist/core/models/playlist.d.ts +35 -0
  10. package/dist/core/models/playlist.js +1 -0
  11. package/dist/core/models/song.d.ts +52 -0
  12. package/dist/core/models/song.js +1 -0
  13. package/dist/core/modules/album.module.d.ts +82 -0
  14. package/dist/core/modules/album.module.js +74 -0
  15. package/dist/core/modules/artist.module.d.ts +97 -0
  16. package/dist/core/modules/artist.module.js +49 -0
  17. package/dist/core/modules/extras.module.d.ts +385 -0
  18. package/dist/core/modules/extras.module.js +64 -0
  19. package/dist/core/modules/playlist.module.d.ts +79 -0
  20. package/dist/core/modules/playlist.module.js +74 -0
  21. package/dist/core/modules/song.module.d.ts +91 -0
  22. package/dist/core/modules/song.module.js +97 -0
  23. package/dist/helpers/errors.d.ts +25 -0
  24. package/dist/helpers/errors.js +84 -0
  25. package/dist/helpers/experimental/stream-urls/index.d.ts +20 -0
  26. package/dist/helpers/experimental/stream-urls/index.js +33 -0
  27. package/dist/helpers/experimental/stream-urls/stream-urls.edge.d.ts +4 -0
  28. package/dist/helpers/experimental/stream-urls/stream-urls.edge.js +38 -0
  29. package/dist/helpers/experimental/stream-urls/stream-urls.node.d.ts +4 -0
  30. package/dist/helpers/experimental/stream-urls/stream-urls.node.js +27 -0
  31. package/dist/helpers/fetch.d.ts +38 -0
  32. package/dist/helpers/fetch.js +38 -0
  33. package/dist/helpers/utils.d.ts +13 -0
  34. package/dist/helpers/utils.js +58 -0
  35. package/dist/index.d.ts +10 -0
  36. package/dist/index.js +8 -0
  37. package/dist/saavn/common-mapper.d.ts +77 -0
  38. package/dist/saavn/common-mapper.js +348 -0
  39. package/dist/saavn/entities/album.entity.d.ts +151 -0
  40. package/dist/saavn/entities/album.entity.js +25 -0
  41. package/dist/saavn/entities/artist.entity.d.ts +274 -0
  42. package/dist/saavn/entities/artist.entity.js +49 -0
  43. package/dist/saavn/entities/base.d.ts +117 -0
  44. package/dist/saavn/entities/base.js +71 -0
  45. package/dist/saavn/entities/extras.d.ts +102 -0
  46. package/dist/saavn/entities/extras.js +43 -0
  47. package/dist/saavn/entities/index.d.ts +6 -0
  48. package/dist/saavn/entities/index.js +6 -0
  49. package/dist/saavn/entities/playlist.entity.d.ts +143 -0
  50. package/dist/saavn/entities/playlist.entity.js +55 -0
  51. package/dist/saavn/entities/show.entity.d.ts +14 -0
  52. package/dist/saavn/entities/show.entity.js +14 -0
  53. package/dist/saavn/entities/song.entity.d.ts +142 -0
  54. package/dist/saavn/entities/song.entity.js +48 -0
  55. package/dist/saavn/operations/get-details/index.d.ts +755 -0
  56. package/dist/saavn/operations/get-details/index.js +34 -0
  57. package/dist/saavn/operations/get-details/mapper.ops.d.ts +35 -0
  58. package/dist/saavn/operations/get-details/mapper.ops.js +55 -0
  59. package/dist/saavn/operations/get-details/schema.ops.d.ts +706 -0
  60. package/dist/saavn/operations/get-details/schema.ops.js +49 -0
  61. package/dist/saavn/operations/get-reco/index.d.ts +144 -0
  62. package/dist/saavn/operations/get-reco/index.js +19 -0
  63. package/dist/saavn/operations/get-reco/mapper.ops.d.ts +36 -0
  64. package/dist/saavn/operations/get-reco/mapper.ops.js +43 -0
  65. package/dist/saavn/operations/get-reco/schema.ops.d.ts +103 -0
  66. package/dist/saavn/operations/get-reco/schema.ops.js +28 -0
  67. package/dist/saavn/operations/get-trending/index.d.ts +288 -0
  68. package/dist/saavn/operations/get-trending/index.js +24 -0
  69. package/dist/saavn/operations/get-trending/mapper.ops.d.ts +13 -0
  70. package/dist/saavn/operations/get-trending/mapper.ops.js +134 -0
  71. package/dist/saavn/operations/get-trending/schema.ops.d.ts +272 -0
  72. package/dist/saavn/operations/get-trending/schema.ops.js +34 -0
  73. package/dist/saavn/operations/index.d.ts +6 -0
  74. package/dist/saavn/operations/index.js +6 -0
  75. package/dist/saavn/operations/search-results/index.d.ts +790 -0
  76. package/dist/saavn/operations/search-results/index.js +29 -0
  77. package/dist/saavn/operations/search-results/mapper.ops.d.ts +413 -0
  78. package/dist/saavn/operations/search-results/mapper.ops.js +80 -0
  79. package/dist/saavn/operations/search-results/schema.ops.d.ts +366 -0
  80. package/dist/saavn/operations/search-results/schema.ops.js +101 -0
  81. package/dist/saavn/operations/web-api/index.d.ts +604 -0
  82. package/dist/saavn/operations/web-api/index.js +29 -0
  83. package/dist/saavn/operations/web-api/mapper.ops.d.ts +11 -0
  84. package/dist/saavn/operations/web-api/mapper.ops.js +23 -0
  85. package/dist/saavn/operations/web-api/schema.ops.d.ts +582 -0
  86. package/dist/saavn/operations/web-api/schema.ops.js +50 -0
  87. package/dist/saavn/operations/web-radio/index.d.ts +181 -0
  88. package/dist/saavn/operations/web-radio/index.js +19 -0
  89. package/dist/saavn/operations/web-radio/mapper.ops.d.ts +14 -0
  90. package/dist/saavn/operations/web-radio/mapper.ops.js +35 -0
  91. package/dist/saavn/operations/web-radio/schema.ops.d.ts +162 -0
  92. package/dist/saavn/operations/web-radio/schema.ops.js +56 -0
  93. package/dist/saavn/primitives/enums.d.ts +17 -0
  94. package/dist/saavn/primitives/enums.js +4 -0
  95. package/dist/saavn/primitives/string.d.ts +5 -0
  96. package/dist/saavn/primitives/string.js +22 -0
  97. package/dist/saavn/run-operation.d.ts +12 -0
  98. package/dist/saavn/run-operation.js +39 -0
  99. package/dist/schemas/index.d.ts +2188 -0
  100. package/dist/schemas/index.js +14 -0
  101. package/dist/types.d.ts +24 -0
  102. package/dist/types.js +1 -0
  103. package/package.json +82 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Fetch a playlist by Saavn ID.
3
+ *
4
+ * @param params - Parameters object
5
+ * @param params.playlistId - Saavn playlist ID
6
+ * @returns Normalized playlist object
7
+ */
8
+ declare function getById({ playlistId }: {
9
+ playlistId: string;
10
+ }): Promise<import("../models").Playlist>;
11
+ /**
12
+ * Fetch a playlist by permalink.
13
+ *
14
+ * @param params - Parameters object
15
+ * @param params.permalink - Playlist permalink string
16
+ * @returns Normalized playlist object
17
+ */
18
+ declare function getByPermalink({ permalink }: {
19
+ permalink: string;
20
+ }): Promise<import("../models").Playlist>;
21
+ /**
22
+ * Fetch playlist recommendations by Saavn ID.
23
+ *
24
+ * @param params - Parameters object
25
+ * @param params.playlistId - Saavn playlist ID
26
+ * @returns List of recommended playlists
27
+ */
28
+ declare function getRecommendations({ playlistId }: {
29
+ playlistId: string;
30
+ }): Promise<{
31
+ id: string;
32
+ type: "playlist";
33
+ title: string;
34
+ subtitle: string;
35
+ url: string;
36
+ images: import("../..").Image[];
37
+ flags: {
38
+ isExplicit: boolean;
39
+ };
40
+ owner: {
41
+ name: string;
42
+ };
43
+ }[]>;
44
+ /**
45
+ * Get trending playlists by language.
46
+ *
47
+ * @param params - Parameters object
48
+ * @param params.language - Language code
49
+ * @returns List of trending playlists
50
+ */
51
+ declare function getTrending({ language }: {
52
+ language: string;
53
+ }): Promise<import("../models").Playlist[]>;
54
+ /**
55
+ * Search for playlists by query.
56
+ *
57
+ * @param params - Parameters object
58
+ * @param params.query - Search query string
59
+ * @param params.limit - Number of results to return (default: 10)
60
+ * @param params.offset - Offset for pagination (default: 1)
61
+ * @returns List of matching playlists
62
+ */
63
+ declare function search({ query, limit, offset, }: {
64
+ query: string;
65
+ limit?: number;
66
+ offset?: number;
67
+ }): Promise<{
68
+ total: number;
69
+ start: number;
70
+ results: import("../models").Playlist[];
71
+ }>;
72
+ export declare const PlaylistModule: {
73
+ getById: typeof getById;
74
+ getByPermalink: typeof getByPermalink;
75
+ getRecommendations: typeof getRecommendations;
76
+ getTrending: typeof getTrending;
77
+ search: typeof search;
78
+ };
79
+ export {};
@@ -0,0 +1,74 @@
1
+ import { SaavnGetDetails, SaavnGetReco, SaavnGetTrending, SaavnSearchResults, SaavnWebAPI, } from '../../saavn/operations';
2
+ import { runOperation } from '../../saavn/run-operation';
3
+ import { extractPermalinkToken } from '../../helpers/utils';
4
+ import { SDKError } from '../../helpers/errors';
5
+ /**
6
+ * Fetch a playlist by Saavn ID.
7
+ *
8
+ * @param params - Parameters object
9
+ * @param params.playlistId - Saavn playlist ID
10
+ * @returns Normalized playlist object
11
+ */
12
+ function getById({ playlistId }) {
13
+ return runOperation(SaavnGetDetails.playlist, { listid: playlistId });
14
+ }
15
+ /**
16
+ * Fetch a playlist by permalink.
17
+ *
18
+ * @param params - Parameters object
19
+ * @param params.permalink - Playlist permalink string
20
+ * @returns Normalized playlist object
21
+ */
22
+ function getByPermalink({ permalink }) {
23
+ const { token, type } = extractPermalinkToken(permalink) ?? {};
24
+ if (!token || type !== 'playlist') {
25
+ throw new SDKError('INVALID_PARAMS', 'Invalid permalink provided');
26
+ }
27
+ return runOperation(SaavnWebAPI.playlist, { token, type: 'playlist' });
28
+ }
29
+ /**
30
+ * Fetch playlist recommendations by Saavn ID.
31
+ *
32
+ * @param params - Parameters object
33
+ * @param params.playlistId - Saavn playlist ID
34
+ * @returns List of recommended playlists
35
+ */
36
+ function getRecommendations({ playlistId }) {
37
+ return runOperation(SaavnGetReco.playlists, { listid: playlistId });
38
+ }
39
+ /**
40
+ * Get trending playlists by language.
41
+ *
42
+ * @param params - Parameters object
43
+ * @param params.language - Language code
44
+ * @returns List of trending playlists
45
+ */
46
+ function getTrending({ language }) {
47
+ return runOperation(SaavnGetTrending.playlists, {
48
+ entity_language: language,
49
+ entity_type: 'playlist',
50
+ });
51
+ }
52
+ /**
53
+ * Search for playlists by query.
54
+ *
55
+ * @param params - Parameters object
56
+ * @param params.query - Search query string
57
+ * @param params.limit - Number of results to return (default: 10)
58
+ * @param params.offset - Offset for pagination (default: 1)
59
+ * @returns List of matching playlists
60
+ */
61
+ function search({ query, limit = 20, offset = 1, }) {
62
+ return runOperation(SaavnSearchResults.playlists, {
63
+ q: query,
64
+ p: String(offset),
65
+ n: String(limit),
66
+ });
67
+ }
68
+ export const PlaylistModule = {
69
+ getById,
70
+ getByPermalink,
71
+ getRecommendations,
72
+ getTrending,
73
+ search,
74
+ };
@@ -0,0 +1,91 @@
1
+ import { fetchStreamUrls } from '../../helpers/experimental/stream-urls';
2
+ /**
3
+ * Fetch a song or songs by Saavn ID.
4
+ *
5
+ * @param params - Parameters object
6
+ * @param params.songIds - Saavn song ID or array of IDs
7
+ * @returns Normalized song object or list
8
+ */
9
+ declare function getById({ songIds }: {
10
+ songIds: string[] | string;
11
+ }): Promise<{
12
+ songs: import("../models").Song[];
13
+ }>;
14
+ /**
15
+ * Fetch a song by permalink.
16
+ *
17
+ * @param params - Parameters object
18
+ * @param params.permalink - Song permalink string
19
+ * @returns Normalized song object
20
+ */
21
+ declare function getByPermalink({ permalink }: {
22
+ permalink: string;
23
+ }): Promise<{
24
+ songs: import("../models").Song[];
25
+ }>;
26
+ /**
27
+ * Fetch songs by station ID.
28
+ *
29
+ * @param params - Parameters object
30
+ * @param params.stationId - Station ID
31
+ * @param params.limit - Number of songs to fetch (default: 10)
32
+ * @param params.next - Whether to get next songs (optional)
33
+ * @returns List of songs from station
34
+ */
35
+ declare function getByStationId({ stationId, limit, next, }: {
36
+ stationId: string;
37
+ limit?: number;
38
+ next?: boolean;
39
+ }): Promise<{
40
+ stationId: string | undefined;
41
+ songs: import("../models").Song[];
42
+ }>;
43
+ /**
44
+ * Fetch song recommendations by Saavn ID.
45
+ *
46
+ * @param params - Parameters object
47
+ * @param params.songId - Saavn song ID
48
+ * @returns List of recommended songs
49
+ */
50
+ declare function getRecommendations({ songId }: {
51
+ songId: string;
52
+ }): Promise<import("../models").Song[]>;
53
+ /**
54
+ * Get trending songs by language.
55
+ * @param params - Parameters object
56
+ * @param params.language - Language code
57
+ * @returns List of trending songs
58
+ */
59
+ declare function getTrending({ language }: {
60
+ language: string;
61
+ }): Promise<import("../models").Song[]>;
62
+ /**
63
+ * Search for songs by query.
64
+ *
65
+ * @param params - Parameters object
66
+ * @param params.query - Search query string
67
+ * @param params.limit - Number of results to return (default: 10)
68
+ * @param params.offset - Offset for pagination (default: 1)
69
+ * @returns List of matching songs
70
+ */
71
+ declare function search({ query, limit, offset, }: {
72
+ query: string;
73
+ limit?: number;
74
+ offset?: number;
75
+ }): Promise<{
76
+ total: number;
77
+ start: number;
78
+ results: import("../models").Song[];
79
+ }>;
80
+ export declare const SongModule: {
81
+ getById: typeof getById;
82
+ getByPermalink: typeof getByPermalink;
83
+ getByStationId: typeof getByStationId;
84
+ getRecommendations: typeof getRecommendations;
85
+ getTrending: typeof getTrending;
86
+ search: typeof search;
87
+ experimental: {
88
+ fetchStreamUrls: typeof fetchStreamUrls;
89
+ };
90
+ };
91
+ export {};
@@ -0,0 +1,97 @@
1
+ import { SaavnGetDetails, SaavnGetReco, SaavnGetTrending, SaavnSearchResults, SaavnWebAPI, SaavnWebRadio, } from '../../saavn/operations';
2
+ import { runOperation } from '../../saavn/run-operation';
3
+ import { extractPermalinkToken } from '../../helpers/utils';
4
+ import { fetchStreamUrls } from '../../helpers/experimental/stream-urls';
5
+ import { SDKError } from '../../helpers/errors';
6
+ /**
7
+ * Fetch a song or songs by Saavn ID.
8
+ *
9
+ * @param params - Parameters object
10
+ * @param params.songIds - Saavn song ID or array of IDs
11
+ * @returns Normalized song object or list
12
+ */
13
+ function getById({ songIds }) {
14
+ return runOperation(SaavnGetDetails.songs, {
15
+ pids: songIds instanceof Array ? songIds.join(',') : songIds,
16
+ });
17
+ }
18
+ /**
19
+ * Fetch a song by permalink.
20
+ *
21
+ * @param params - Parameters object
22
+ * @param params.permalink - Song permalink string
23
+ * @returns Normalized song object
24
+ */
25
+ function getByPermalink({ permalink }) {
26
+ const { token, type } = extractPermalinkToken(permalink) ?? {};
27
+ if (!token || type !== 'song') {
28
+ throw new SDKError('INVALID_PARAMS', 'Invalid permalink provided');
29
+ }
30
+ return runOperation(SaavnWebAPI.songs, { token, type: 'song' });
31
+ }
32
+ /**
33
+ * Fetch songs by station ID.
34
+ *
35
+ * @param params - Parameters object
36
+ * @param params.stationId - Station ID
37
+ * @param params.limit - Number of songs to fetch (default: 10)
38
+ * @param params.next - Whether to get next songs (optional)
39
+ * @returns List of songs from station
40
+ */
41
+ function getByStationId({ stationId, limit = 20, next, }) {
42
+ return runOperation(SaavnWebRadio.songs, {
43
+ ctx: 'android',
44
+ stationid: stationId,
45
+ k: String(limit),
46
+ next: next ? '1' : '0',
47
+ });
48
+ }
49
+ /**
50
+ * Fetch song recommendations by Saavn ID.
51
+ *
52
+ * @param params - Parameters object
53
+ * @param params.songId - Saavn song ID
54
+ * @returns List of recommended songs
55
+ */
56
+ function getRecommendations({ songId }) {
57
+ return runOperation(SaavnGetReco.songs, { pid: songId });
58
+ }
59
+ /**
60
+ * Get trending songs by language.
61
+ * @param params - Parameters object
62
+ * @param params.language - Language code
63
+ * @returns List of trending songs
64
+ */
65
+ function getTrending({ language }) {
66
+ return runOperation(SaavnGetTrending.songs, {
67
+ entity_language: language,
68
+ entity_type: 'song',
69
+ });
70
+ }
71
+ /**
72
+ * Search for songs by query.
73
+ *
74
+ * @param params - Parameters object
75
+ * @param params.query - Search query string
76
+ * @param params.limit - Number of results to return (default: 10)
77
+ * @param params.offset - Offset for pagination (default: 1)
78
+ * @returns List of matching songs
79
+ */
80
+ function search({ query, limit = 20, offset = 1, }) {
81
+ return runOperation(SaavnSearchResults.songs, {
82
+ q: query,
83
+ p: String(offset),
84
+ n: String(limit),
85
+ });
86
+ }
87
+ export const SongModule = {
88
+ getById,
89
+ getByPermalink,
90
+ getByStationId,
91
+ getRecommendations,
92
+ getTrending,
93
+ search,
94
+ experimental: {
95
+ fetchStreamUrls,
96
+ },
97
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Error utilities for @saavn-labs/sdk
3
+ * @module errors
4
+ * @internal
5
+ */
6
+ import { ZodError } from 'zod';
7
+ export type SDKErrorCode = 'INVALID_PARAMS' | 'INVALID_RESPONSE' | 'API_ERROR' | 'NETWORK_ERROR' | 'UNSUPPORTED_RUNTIME' | 'EXPERIMENTAL_FEATURE' | 'INTERNAL_ERROR';
8
+ export declare class SDKError extends Error {
9
+ readonly code: SDKErrorCode;
10
+ readonly details?: unknown;
11
+ cause?: unknown;
12
+ constructor(code: SDKErrorCode, message: string, options?: {
13
+ details?: unknown;
14
+ cause?: unknown;
15
+ });
16
+ }
17
+ export declare function fromZodError(error: ZodError, kind: 'params' | 'response', context?: string): SDKError;
18
+ export declare function networkError(context?: string, status?: number): SDKError;
19
+ export declare function experimentalError(message?: string): SDKError;
20
+ export declare function internalError(message?: string, cause?: unknown): SDKError;
21
+ /**
22
+ * Detect unusable API responses before schema validation.
23
+ * Returns a user-friendly message or null if response looks valid.
24
+ */
25
+ export declare function detectSaavnApiError(data: unknown): string | null;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Error utilities for @saavn-labs/sdk
3
+ * @module errors
4
+ * @internal
5
+ */
6
+ import { ZodError } from 'zod';
7
+ export class SDKError extends Error {
8
+ code;
9
+ details;
10
+ cause;
11
+ constructor(code, message, options) {
12
+ super(message);
13
+ this.name = 'SDKError';
14
+ this.code = code;
15
+ this.details = options?.details;
16
+ this.cause = options?.cause;
17
+ }
18
+ }
19
+ export function fromZodError(error, kind, context) {
20
+ return new SDKError(kind === 'params' ? 'INVALID_PARAMS' : 'INVALID_RESPONSE', `Invalid ${kind}${context ? ` for ${context}` : ''}.`, {
21
+ details: error.issues.map((issue) => ({
22
+ path: issue.path.join('.'),
23
+ message: issue.message,
24
+ })),
25
+ cause: error,
26
+ });
27
+ }
28
+ export function networkError(context, status) {
29
+ return new SDKError('NETWORK_ERROR', `Request failed${context ? ` for ${context}` : ''}.`, {
30
+ details: status ? { status } : undefined,
31
+ });
32
+ }
33
+ export function experimentalError(message) {
34
+ return new SDKError('EXPERIMENTAL_FEATURE', message ??
35
+ 'This feature is experimental and requires explicit user acknowledgment.');
36
+ }
37
+ export function internalError(message = 'An internal error occurred.', cause) {
38
+ return new SDKError('INTERNAL_ERROR', message, { cause });
39
+ }
40
+ /**
41
+ * Detect unusable API responses before schema validation.
42
+ * Returns a user-friendly message or null if response looks valid.
43
+ */
44
+ export function detectSaavnApiError(data) {
45
+ if (data == null) {
46
+ return 'Empty response from Saavn API.';
47
+ }
48
+ if (data === '' || data === false) {
49
+ return 'Invalid response from Saavn API.';
50
+ }
51
+ if (Array.isArray(data) && data.length === 0) {
52
+ return 'No data returned from Saavn API.';
53
+ }
54
+ if (typeof data !== 'object') {
55
+ return null;
56
+ }
57
+ const obj = data;
58
+ if (typeof obj.error === 'string') {
59
+ return obj.error;
60
+ }
61
+ if (typeof obj.error === 'object' && obj.error !== null) {
62
+ const err = obj.error;
63
+ if (typeof err.msg === 'string') {
64
+ return err.msg;
65
+ }
66
+ if (typeof err.code === 'string') {
67
+ switch (err.code) {
68
+ case 'INPUT_MISSING':
69
+ return 'One or more required parameters are missing.';
70
+ default:
71
+ return `Saavn API error (${err.code}).`;
72
+ }
73
+ }
74
+ return 'Saavn API returned an error.';
75
+ }
76
+ if (Object.prototype.hasOwnProperty.call(obj, 'stationid') &&
77
+ obj.stationid === '') {
78
+ return 'Invalid or expired station ID.';
79
+ }
80
+ if (typeof obj.status === 'string' && obj.status !== 'success') {
81
+ return `Saavn API returned status: ${obj.status}.`;
82
+ }
83
+ return null;
84
+ }
@@ -0,0 +1,20 @@
1
+ export type Runtime = 'node' | 'edge';
2
+ /**
3
+ * ⚠️ EXPERIMENTAL
4
+ *
5
+ * Generates direct download links for a song.
6
+ *
7
+ * This API behaves differently depending on runtime:
8
+ * - `node`: uses local DES-ECB decryption via Node crypto
9
+ * - `edge`: uses Saavn's undocumented internal API
10
+ *
11
+ * Runtime must be specified explicitly.
12
+ *
13
+ * @param encryptedUrl - Encrypted media URL
14
+ * @param options - { runtime: 'node' | 'edge', acknowledge: boolean } to acknowledge experimental nature
15
+ * @returns Stream URL
16
+ */
17
+ export declare function fetchStreamUrls(encryptedUrl: string, runtime: Runtime, acknowledge: boolean): Promise<Array<{
18
+ bitrate: string;
19
+ url: string;
20
+ }>>;
@@ -0,0 +1,33 @@
1
+ import { SDKError, experimentalError } from '../../../helpers/errors';
2
+ import { generateStreamUrlsNode } from './stream-urls.node';
3
+ import { generateStreamUrlsEdge } from './stream-urls.edge';
4
+ /**
5
+ * ⚠️ EXPERIMENTAL
6
+ *
7
+ * Generates direct download links for a song.
8
+ *
9
+ * This API behaves differently depending on runtime:
10
+ * - `node`: uses local DES-ECB decryption via Node crypto
11
+ * - `edge`: uses Saavn's undocumented internal API
12
+ *
13
+ * Runtime must be specified explicitly.
14
+ *
15
+ * @param encryptedUrl - Encrypted media URL
16
+ * @param options - { runtime: 'node' | 'edge', acknowledge: boolean } to acknowledge experimental nature
17
+ * @returns Stream URL
18
+ */
19
+ export async function fetchStreamUrls(encryptedUrl, runtime, acknowledge) {
20
+ if (!encryptedUrl)
21
+ return [];
22
+ if (acknowledge !== true) {
23
+ throw experimentalError('Stream URL generation is experimental and requires explicit acknowledgment.');
24
+ }
25
+ switch (runtime) {
26
+ case 'node':
27
+ return generateStreamUrlsNode(encryptedUrl);
28
+ case 'edge':
29
+ return generateStreamUrlsEdge(encryptedUrl);
30
+ default:
31
+ throw new SDKError('UNSUPPORTED_RUNTIME', `Unsupported runtime: ${String(runtime)}`);
32
+ }
33
+ }
@@ -0,0 +1,4 @@
1
+ export declare function generateStreamUrlsEdge(encryptedUrl: string): Promise<{
2
+ bitrate: "12kbps" | "48kbps" | "96kbps" | "160kbps" | "320kbps";
3
+ url: string;
4
+ }[]>;
@@ -0,0 +1,38 @@
1
+ import { fetchFromSaavn } from '../../../helpers/fetch';
2
+ const AUDIO_QUALITIES = [
3
+ { id: '_12', bitrate: '12kbps' },
4
+ { id: '_48', bitrate: '48kbps' },
5
+ { id: '_96', bitrate: '96kbps' },
6
+ { id: '_160', bitrate: '160kbps' },
7
+ { id: '_320', bitrate: '320kbps' },
8
+ ];
9
+ function normalizeAacUrl(rawUrl) {
10
+ try {
11
+ const url = new URL(rawUrl);
12
+ url.hostname = url.hostname.replace(/^web\./, 'aac.');
13
+ return url.toString();
14
+ }
15
+ catch {
16
+ return rawUrl;
17
+ }
18
+ }
19
+ export async function generateStreamUrlsEdge(encryptedUrl) {
20
+ const res = await fetchFromSaavn({
21
+ call: 'song.generateAuthToken',
22
+ params: {
23
+ url: encryptedUrl,
24
+ bitrate: '96',
25
+ },
26
+ });
27
+ if (!res.ok)
28
+ return [];
29
+ const data = res.data;
30
+ if (typeof data.auth_url === 'string') {
31
+ const baseUrl = normalizeAacUrl(data.auth_url.split('?')[0]);
32
+ return AUDIO_QUALITIES.map((q) => ({
33
+ bitrate: q.bitrate,
34
+ url: baseUrl.replace('_96', q.id),
35
+ }));
36
+ }
37
+ return [];
38
+ }
@@ -0,0 +1,4 @@
1
+ export declare function generateStreamUrlsNode(encryptedUrl: string): {
2
+ bitrate: "12kbps" | "48kbps" | "96kbps" | "160kbps" | "320kbps";
3
+ url: string;
4
+ }[];
@@ -0,0 +1,27 @@
1
+ import crypto from 'node:crypto';
2
+ const AUDIO_QUALITIES = [
3
+ { id: '_12', bitrate: '12kbps' },
4
+ { id: '_48', bitrate: '48kbps' },
5
+ { id: '_96', bitrate: '96kbps' },
6
+ { id: '_160', bitrate: '160kbps' },
7
+ { id: '_320', bitrate: '320kbps' },
8
+ ];
9
+ export function generateStreamUrlsNode(encryptedUrl) {
10
+ if (!encryptedUrl)
11
+ return [];
12
+ try {
13
+ const key = Buffer.from('38346591', 'utf8');
14
+ const decipher = crypto.createDecipheriv('des-ecb', key, null);
15
+ decipher.setAutoPadding(true);
16
+ const decrypted = decipher.update(encryptedUrl, 'base64', 'utf8') + decipher.final('utf8');
17
+ if (!decrypted)
18
+ return [];
19
+ return AUDIO_QUALITIES.map((q) => ({
20
+ bitrate: q.bitrate,
21
+ url: decrypted.replace('_96', q.id),
22
+ }));
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Fetch utility for @saavn-labs/sdk
3
+ * @module fetch
4
+ * @internal
5
+ */
6
+ /**
7
+ * API context/environment to use for requests
8
+ * - 'web6dot0': Web context
9
+ * - 'android': Android context
10
+ */
11
+ export type ClientContext = 'web6dot0' | 'android';
12
+ /**
13
+ * Parameters for making an API request to JioSaavn
14
+ */
15
+ export interface FetchParams {
16
+ /** API call to op (e.g., 'song.getDetails') */
17
+ call: string;
18
+ /** Query parameters for the API call */
19
+ params?: Record<string, string | number | boolean>;
20
+ /** API context to use (default: 'web6dot0') */
21
+ context?: ClientContext;
22
+ /** Base URL for the JioSaavn API (e.g. your own proxy) */
23
+ baseUrl?: string;
24
+ /** Custom HTTP headers to include in request */
25
+ headers?: Record<string, string>;
26
+ /** Custom User-Agent strings to rotate from (optional) */
27
+ userAgents?: string[];
28
+ /** Custom fetch implementation (e.g., for environments without global fetch) */
29
+ fetch?: typeof fetch;
30
+ /** Optional timeout in milliseconds */
31
+ timeoutMs?: number;
32
+ }
33
+ export interface FetchResponse {
34
+ data: unknown;
35
+ ok: boolean;
36
+ status: number;
37
+ }
38
+ export declare const fetchFromSaavn: ({ call, params, context, baseUrl, headers, userAgents, timeoutMs, fetch: fetchImpl, }: FetchParams) => Promise<FetchResponse>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Fetch utility for @saavn-labs/sdk
3
+ * @module fetch
4
+ * @internal
5
+ */
6
+ import { pickUserAgent } from './utils';
7
+ const BASE_URL = 'https://www.jiosaavn.com';
8
+ export const fetchFromSaavn = async ({ call, params = {}, context = 'web6dot0', baseUrl = BASE_URL, headers = {}, userAgents, timeoutMs, fetch: fetchImpl = globalThis.fetch, }) => {
9
+ const url = new URL(`${baseUrl}/api.php`);
10
+ url.searchParams.append('__call', call);
11
+ url.searchParams.append('_format', 'json');
12
+ url.searchParams.append('_marker', '0');
13
+ url.searchParams.append('api_version', '4');
14
+ url.searchParams.append('ctx', context);
15
+ for (const [k, v] of Object.entries(params)) {
16
+ url.searchParams.append(k, String(v));
17
+ }
18
+ const controller = timeoutMs ? new AbortController() : undefined;
19
+ const timer = timeoutMs
20
+ ? setTimeout(() => controller?.abort(), timeoutMs)
21
+ : undefined;
22
+ try {
23
+ const res = await fetchImpl(url.toString(), {
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ 'User-Agent': pickUserAgent(userAgents),
27
+ ...headers,
28
+ },
29
+ signal: controller?.signal,
30
+ });
31
+ const data = await res.json();
32
+ return { data, ok: res.ok, status: res.status };
33
+ }
34
+ finally {
35
+ if (timer)
36
+ clearTimeout(timer);
37
+ }
38
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * General utilities for @saavn-labs/sdk
3
+ * @module utils
4
+ * @internal
5
+ */
6
+ import type { SaavnPermalinkToken } from '../types';
7
+ export declare function pickUserAgent(custom?: readonly string[]): string;
8
+ /**
9
+ * Extract entity type and token from a JioSaavn share URL
10
+ * @param permaLink - JioSaavn share URL or token
11
+ * @returns Extracted token object or undefined if invalid
12
+ */
13
+ export declare function extractPermalinkToken(permaLink: string): SaavnPermalinkToken | undefined;