Skip to content

Commit

Permalink
feat: search extract from universal link
Browse files Browse the repository at this point in the history
  • Loading branch information
sjdonado committed Nov 24, 2024
1 parent c8330f6 commit 2508a85
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 35 deletions.
3 changes: 2 additions & 1 deletion src/adapters/apple-music.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function getAppleMusicLink(query: string, metadata: SearchMetadata)
const searchType = APPLE_MUSIC_SEARCH_TYPES[metadata.type];

if (!searchType) {
return;
return null;
}

// apple music does not support x-www-form-urlencoded encoding
Expand Down Expand Up @@ -58,5 +58,6 @@ export async function getAppleMusicLink(query: string, metadata: SearchMetadata)
return searchResultLink;
} catch (err) {
logger.error(`[Apple Music] (${url}) ${err} `);
return null;
}
}
3 changes: 2 additions & 1 deletion src/adapters/deezer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function getDeezerLink(query: string, metadata: SearchMetadata) {
const searchType = DEEZER_SEARCH_TYPES[metadata.type];

if (!searchType) {
return;
return null;
}

const params = new URLSearchParams({
Expand Down Expand Up @@ -68,5 +68,6 @@ export async function getDeezerLink(query: string, metadata: SearchMetadata) {
return searchResultLink;
} catch (error) {
logger.error(`[Deezer] (${url}) ${error}`);
return null;
}
}
5 changes: 3 additions & 2 deletions src/adapters/sound-cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getCheerioDoc } from '~/utils/scraper';

export async function getSoundCloudLink(query: string, metadata: SearchMetadata) {
if (metadata.type === MetadataType.Show) {
return;
return null;
}

const params = new URLSearchParams({
Expand Down Expand Up @@ -39,7 +39,7 @@ export async function getSoundCloudLink(query: string, metadata: SearchMetadata)
const { href, score } = getResultWithBestScore(noscriptDoc, listElements, query);

if (score <= RESPONSE_COMPARE_MIN_SCORE) {
return;
return null;
}

const searchResultLink = {
Expand All @@ -53,5 +53,6 @@ export async function getSoundCloudLink(query: string, metadata: SearchMetadata)
return searchResultLink;
} catch (err) {
logger.error(`[SoundCloud] (${url}) ${err}`);
return null;
}
}
3 changes: 2 additions & 1 deletion src/adapters/spotify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function getSpotifyLink(query: string, metadata: SearchMetadata) {
const searchType = SPOTIFY_SEARCH_TYPES[metadata.type];

if (!searchType) {
return;
return null;
}

const params = new URLSearchParams({
Expand Down Expand Up @@ -89,6 +89,7 @@ export async function getSpotifyLink(query: string, metadata: SearchMetadata) {
return searchResultLink;
} catch (error) {
logger.error(`[Spotify] (${url}) ${error}`);
return null;
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/adapters/tidal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export async function getTidalLink(query: string, metadata: SearchMetadata) {
const searchType = TIDAL_SEARCH_TYPES[metadata.type];

if (!searchType) {
return;
return null;
}

const params = new URLSearchParams({
Expand Down Expand Up @@ -80,6 +80,7 @@ export async function getTidalLink(query: string, metadata: SearchMetadata) {
return searchResultLink;
} catch (error) {
logger.error(`[Tidal] (${url}) ${error}`);
return null;
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/adapters/youtube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const YOUTUBE_SEARCH_TYPES = {
};

export async function getYouTubeLink(query: string, metadata: SearchMetadata) {
return null; // TEMPFIX: youtube blocked the server ip

const params = new URLSearchParams({
q: `${query} ${YOUTUBE_SEARCH_TYPES[metadata.type]}`,
});
Expand Down Expand Up @@ -46,16 +48,14 @@ export async function getYouTubeLink(query: string, metadata: SearchMetadata) {
};
});

return; // TEMPFIX: youtube is blocked

const link = await getLinkWithPuppeteer(
url.toString(),
'ytmusic-card-shelf-renderer a',
cookies
);

if (!link) {
return;
return null;
}

const searchResultLink = {
Expand All @@ -69,6 +69,7 @@ export async function getYouTubeLink(query: string, metadata: SearchMetadata) {
return searchResultLink;
} catch (error) {
logger.error(`[YouTube] (${url}) ${error}`);
return null;
}
}

Expand Down
66 changes: 63 additions & 3 deletions src/parsers/tidal.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { MetadataType, Parser } from '~/config/enum';
import { cacheSearchMetadata, getCachedSearchMetadata } from '~/services/cache';
import { CheerioAPI } from 'cheerio';

import { Adapter, MetadataType, Parser } from '~/config/enum';
import {
cacheSearchMetadata,
cacheTidalUniversalLinkResponse,
getCachedSearchMetadata,
getCachedTidalUniversalLinkResponse,
} from '~/services/cache';
import { fetchMetadata } from '~/services/metadata';
import { SearchMetadata } from '~/services/search';
import { SearchMetadata, SearchResultLink } from '~/services/search';
import { logger } from '~/utils/logger';
import { getCheerioDoc, metaTagContent } from '~/utils/scraper';

Expand Down Expand Up @@ -83,3 +90,56 @@ export const getTidalQueryFromMetadata = (metadata: SearchMetadata) => {

return query;
};

export const getUniversalMetadataFromTidal = async (
link: string
): Promise<Record<Adapter, SearchResultLink | null> | undefined> => {
const cached = await getCachedTidalUniversalLinkResponse(link);
if (cached) {
logger.info(`[Tidal] (${link}) universalLink metadata cache hit`);
return cached;
}

const extractLink = (
doc: CheerioAPI,
selector: string,
type: Adapter
): SearchResultLink | null => {
const url = doc(selector).attr('href');
return url
? {
type,
url,
isVerified: true,
}
: null;
};

try {
const html = await fetchMetadata(link);
const doc = getCheerioDoc(html);

const adapterLinks: Record<Adapter, SearchResultLink | null> = {
[Adapter.Spotify]: extractLink(doc, 'a[href*="spotify.com"]', Adapter.Spotify),
[Adapter.YouTube]: extractLink(
doc,
'a[href*="music.youtube.com"]',
Adapter.YouTube
),
[Adapter.AppleMusic]: extractLink(
doc,
'a[href*="music.apple.com"]',
Adapter.AppleMusic
),
[Adapter.Deezer]: null,
[Adapter.SoundCloud]: null,
[Adapter.Tidal]: null,
};

await cacheTidalUniversalLinkResponse(link, adapterLinks);

return adapterLinks;
} catch (err) {
logger.error(`[${getUniversalMetadataFromTidal.name}] (${link}) ${err}`);
}
};
15 changes: 14 additions & 1 deletion src/services/cache.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { caching } from 'cache-manager';
import bunSqliteStore from 'cache-manager-bun-sqlite3';

import type { Parser } from '~/config/enum';
import type { Adapter, Parser } from '~/config/enum';
import { ENV } from '~/config/env';

import { SearchMetadata, SearchResultLink } from './search';
Expand Down Expand Up @@ -55,6 +55,19 @@ export const getCachedTidalAccessToken = async (): Promise<string | undefined> =
return cacheStore.get('tidal:accessToken');
};

export const cacheTidalUniversalLinkResponse = async (
link: string,
response: Record<Adapter, SearchResultLink | null>
) => {
await cacheStore.set(`tidal:universalLink:${link}`, response);
};

export const getCachedTidalUniversalLinkResponse = async (
link: string
): Promise<Record<Adapter, SearchResultLink | null> | undefined> => {
return cacheStore.get(`tidal:universalLink:${link}`);
};

export const cacheShortenLink = async (link: string, refer: string) => {
await cacheStore.set(`url-shortener:${link}`, refer);
};
Expand Down
73 changes: 52 additions & 21 deletions src/services/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import {
getSoundCloudQueryFromMetadata,
} from '~/parsers/sound-cloud';
import { getSpotifyMetadata, getSpotifyQueryFromMetadata } from '~/parsers/spotify';
import { getTidalMetadata, getTidalQueryFromMetadata } from '~/parsers/tidal';
import {
getTidalMetadata,
getTidalQueryFromMetadata,
getUniversalMetadataFromTidal,
} from '~/parsers/tidal';
import { getYouTubeMetadata, getYouTubeQueryFromMetadata } from '~/parsers/youtube';
import { generateId } from '~/utils/encoding';
import { logger } from '~/utils/logger';
Expand Down Expand Up @@ -131,32 +135,59 @@ export const search = async ({
};
}

const [
spotifyLink,
youtubeLink,
appleMusicLink,
deezerLink,
soundCloudLink,
tidalLink,
shortLink,
] = await Promise.all([
searchParser.type !== Parser.Spotify ? getSpotifyLink(query, metadata) : null,
searchParser.type !== Parser.YouTube && searchAdapters.includes(Adapter.YouTube)
? getYouTubeLink(query, metadata)
: null,
searchParser.type !== Parser.AppleMusic && searchAdapters.includes(Adapter.AppleMusic)
? getAppleMusicLink(query, metadata)
: null,
let spotifyLink: SearchResultLink | null = null;
let youtubeLink: SearchResultLink | null = null;
let appleMusicLink: SearchResultLink | null = null;
let deezerLink: SearchResultLink | null = null;
let soundCloudLink: SearchResultLink | null = null;
let tidalLink: SearchResultLink | null = null;

if (searchParser.type !== Parser.Tidal) {
tidalLink = await getTidalLink(query, metadata);

const fromUniversalLink = await getUniversalMetadataFromTidal(`${tidalLink?.url}/u`);

logger.info(
`[${search.name}] (universalLink results) ${Object.values(fromUniversalLink ?? {})
.map(link => link?.url)
.filter(Boolean)}`
);

if (fromUniversalLink) {
spotifyLink = fromUniversalLink.spotify;
youtubeLink = fromUniversalLink.youTube;
appleMusicLink = fromUniversalLink.appleMusic;
}
}

let shortLink: string | null = null;
[spotifyLink, shortLink] = await Promise.all([
spotifyLink
? spotifyLink
: searchParser.type !== Parser.Spotify
? getSpotifyLink(query, metadata)
: null,
shortenLink(`${ENV.app.url}?id=${id}`),
]);

[youtubeLink, appleMusicLink, deezerLink, soundCloudLink] = await Promise.all([
youtubeLink
? youtubeLink
: searchParser.type !== Parser.YouTube && searchAdapters.includes(Adapter.YouTube)
? getYouTubeLink(query, metadata)
: null,
appleMusicLink
? appleMusicLink
: searchParser.type !== Parser.AppleMusic &&
searchAdapters.includes(Adapter.AppleMusic)
? getAppleMusicLink(query, metadata)
: null,
searchParser.type !== Parser.Deezer && searchAdapters.includes(Adapter.Deezer)
? getDeezerLink(query, metadata)
: null,
searchParser.type !== Parser.SoundCloud && searchAdapters.includes(Adapter.SoundCloud)
? getSoundCloudLink(query, metadata)
: null,
searchParser.type !== Parser.Tidal && searchAdapters.includes(Adapter.Tidal)
? getTidalLink(query, metadata)
: null,
shortenLink(`${ENV.app.url}?id=${id}`),
]);

if (searchParser.type !== Parser.Spotify && spotifyLink) {
Expand Down
1 change: 0 additions & 1 deletion src/utils/url-shortener.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ENV } from '~/config/env';

import { cacheShortenLink, getCachedShortenLink } from '~/services/cache';

import HttpClient from './http-client';
Expand Down

0 comments on commit 2508a85

Please sign in to comment.