Skip to content

Commit

Permalink
Merge pull request #20 from sjdonado/6-inverse-search
Browse files Browse the repository at this point in the history
6 inverse search - Youtube Music (beta)
  • Loading branch information
sjdonado authored May 4, 2024
2 parents 004dbca + 9bd9a56 commit db81549
Show file tree
Hide file tree
Showing 50 changed files with 949 additions and 650 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
---
name: Deploy to donado.co

on:
workflow_run:
workflows: ['Run tests']
types:
- completed
push:
branches: [master]

jobs:
deploy:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ on:
branches: [master]

env:
SPOTIFY_AUTH_URL: https://accounts.spotify.com/api/token
SPOTIFY_CLIENT_ID: ${{ secrets.SPOTIFY_CLIENT_ID }}
SPOTIFY_CLIENT_SECRET: ${{ secrets.YOUTUBE_COOKIES }}
SPOTIFY_CLIENT_VERSION: ${{ secrets.SPOTIFY_CLIENT_VERSION }}
SPOTIFY_API_URL: https://api.spotify.com/v1/search
YOUTUBE_MUSIC_URL: https://music.youtube.com/search
YOUTUBE_COOKIES: ${{ secrets.YOUTUBE_COOKIES }}
DEEZER_API_URL: https://api.deezer.com/search
APPLE_MUSIC_API_URL: https://music.apple.com/ca
SOUNDCLOUD_BASE_URL: https://soundcloud.com
TIDAL_BASE_URL: https://listen.tidal.com

jobs:
tests:
runs-on: ubuntu-latest
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# I don't have Spotify

> More links coming soon :)
| Raycast API | Web Page |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![Uptime Badge](https://uptime.donado.co/api/badge/3/uptime/24?labelPrefix=API%20&labelSuffix=h) ![Uptime Badge](https://uptime.donado.co/api/badge/3/ping/24?labelPrefix=API%20) | ![Uptime Badge](https://uptime.donado.co/api/badge/2/uptime/24?labelPrefix=Web%20Page%20&labelSuffix=h) ![Uptime Badge](https://uptime.donado.co/api/badge/2/ping/24?labelPrefix=Web%20Page%20) |

## Web
## Spotify Search

<img width="2032" alt="Screenshot 2024-05-04 at 12 52 36" src="https://github.com/sjdonado/idonthavespotify/assets/27580836/842c5aa5-d0ac-4bb8-938c-ce22d1d522c5">

## Youtube Music Search (beta)

<div align="center">
<img width="1840" alt="Website screenshot" src="https://github.com/sjdonado/idonthavespotify/assets/27580836/4a243d5e-0ab6-4174-9c79-baf5f2cff98a">
</div>
<img width="2032" alt="Screenshot 2024-05-04 at 12 50 22" src="https://github.com/sjdonado/idonthavespotify/assets/27580836/2fce2d17-938a-402e-8b66-47ed6b12f6b0">

## Raycast Extension

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "idonthavespotify",
"version": "1.1.0",
"version": "1.2.0",
"scripts": {
"dev": "concurrently \"bun run build:dev\" \"bun run --watch www/bin.ts\"",
"build:dev": "vite build --mode=development --watch",
"build": "vite build",
"test": "bun test --coverage"
"test": "DATABASE_PATH=:memory: bun test --coverage"
},
"dependencies": {
"@elysiajs/html": "^1.0.2",
Expand Down
49 changes: 29 additions & 20 deletions src/adapters/apple-music.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import * as config from '~/config/default';
import { DEFAULT_TIMEOUT } from '~/config/constants';
import { MetadataType, ServiceType } from '~/config/enum';
import { RESPONSE_COMPARE_MIN_SCORE } from '~/config/constants';

import HttpClient from '~/utils/http-client';
import { logger } from '~/utils/logger';
import { getCheerioDoc } from '~/utils/scraper';

import { SpotifyMetadata, SpotifyMetadataType } from '~/parsers/spotify';
import { SpotifyContentLink, SpotifyContentLinkType } from '~/services/search';
import { getResultWithBestScore } from '~/utils/compare';

import { SearchMetadata, SearchResultLink } from '~/services/search';
import { cacheSearchResultLink, getCachedSearchResultLink } from '~/services/cache';

export const APPLE_MUSIC_LINK_SELECTOR = 'a[href^="https://music.apple.com/"]';

const APPLE_MUSIC_SEARCH_TYPES = {
[SpotifyMetadataType.Song]: 'Songs',
[SpotifyMetadataType.Album]: 'Albums',
[SpotifyMetadataType.Playlist]: 'Playlists',
[SpotifyMetadataType.Artist]: 'Artists',
[SpotifyMetadataType.Podcast]: undefined,
[SpotifyMetadataType.Show]: undefined,
[MetadataType.Song]: 'Songs',
[MetadataType.Album]: 'Albums',
[MetadataType.Playlist]: 'Playlists',
[MetadataType.Artist]: 'Artists',
[MetadataType.Podcast]: undefined,
[MetadataType.Show]: undefined,
};

export async function getAppleMusicLink(query: string, metadata: SpotifyMetadata) {
export async function getAppleMusicLink(query: string, metadata: SearchMetadata) {
const searchType = APPLE_MUSIC_SEARCH_TYPES[metadata.type];

if (!searchType) {
Expand All @@ -32,24 +33,32 @@ export async function getAppleMusicLink(query: string, metadata: SpotifyMetadata

const url = new URL(`${config.services.appleMusic.apiUrl}/search?${params}`);

const cache = await getCachedSearchResultLink(url);
if (cache) {
logger.info(`[Apple Music] (${url}) cache hit`);
return cache;
}

try {
const html = await HttpClient.get<string>(url.toString(), {
timeout: DEFAULT_TIMEOUT * 2,
});
const html = await HttpClient.get<string>(url.toString());
const doc = getCheerioDoc(html);

const listElements = doc(
`${searchType ? 'div[aria-label="' + searchType + '"]' : ''} a[href^="https://music.apple.com/"]:lt(3)`
);

const { href } = getResultWithBestScore(doc, listElements, query);
const { href, score } = getResultWithBestScore(doc, listElements, query);

return {
type: SpotifyContentLinkType.AppleMusic,
const searchResultLink = {
type: ServiceType.AppleMusic,
url: href,
isVerified: true,
} as SpotifyContentLink;
isVerified: score > RESPONSE_COMPARE_MIN_SCORE,
} as SearchResultLink;

await cacheSearchResultLink(url, searchResultLink);

return searchResultLink;
} catch (err) {
logger.error(`[Apple Music](${url}) ${err} `);
logger.error(`[Apple Music] (${url}) ${err} `);
}
}
41 changes: 24 additions & 17 deletions src/adapters/deezer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as config from '~/config/default';
import { MetadataType, ServiceType } from '~/config/enum';
import { ADAPTERS_QUERY_LIMIT } from '~/config/constants';

import HttpClient from '~/utils/http-client';
import { logger } from '~/utils/logger';
import { responseMatchesQuery } from '~/utils/compare';

import { SpotifyMetadata, SpotifyMetadataType } from '~/parsers/spotify';
import { SpotifyContentLink, SpotifyContentLinkType } from '~/services/search';
import { SearchMetadata, SearchResultLink } from '~/services/search';
import { cacheSearchResultLink, getCachedSearchResultLink } from '~/services/cache';

interface DeezerSearchResponse {
total: number;
Expand All @@ -20,15 +21,15 @@ interface DeezerSearchResponse {
}

const DEEZER_SEARCH_TYPES = {
[SpotifyMetadataType.Song]: 'track',
[SpotifyMetadataType.Album]: 'album',
[SpotifyMetadataType.Playlist]: 'playlist',
[SpotifyMetadataType.Artist]: 'artist',
[SpotifyMetadataType.Show]: 'podcast',
[SpotifyMetadataType.Podcast]: undefined,
[MetadataType.Song]: 'track',
[MetadataType.Album]: 'album',
[MetadataType.Playlist]: 'playlist',
[MetadataType.Artist]: 'artist',
[MetadataType.Show]: 'podcast',
[MetadataType.Podcast]: undefined,
};

export async function getDeezerLink(query: string, metadata: SpotifyMetadata) {
export async function getDeezerLink(query: string, metadata: SearchMetadata) {
const searchType = DEEZER_SEARCH_TYPES[metadata.type];

if (!searchType) {
Expand All @@ -43,6 +44,12 @@ export async function getDeezerLink(query: string, metadata: SpotifyMetadata) {
const url = new URL(`${config.services.deezer.apiUrl}/${searchType}`);
url.search = params.toString();

const cache = await getCachedSearchResultLink(url);
if (cache) {
logger.info(`[Deezer] (${url}) cache hit`);
return cache;
}

try {
const response = await HttpClient.get<DeezerSearchResponse>(url.toString());

Expand All @@ -52,15 +59,15 @@ export async function getDeezerLink(query: string, metadata: SpotifyMetadata) {

const [{ title, name, link }] = response.data;

if (!responseMatchesQuery(title ?? name ?? '', query)) {
throw new Error(`Query does not match: ${JSON.stringify({ title, name })}`);
}

return {
type: SpotifyContentLinkType.Deezer,
const searchResultLink = {
type: ServiceType.Deezer,
url: link,
isVerified: true,
} as SpotifyContentLink;
isVerified: responseMatchesQuery(title ?? name ?? '', query),
} as SearchResultLink;

await cacheSearchResultLink(url, searchResultLink);

return searchResultLink;
} catch (error) {
logger.error(`[Deezer] (${url}) ${error}`);
}
Expand Down
34 changes: 25 additions & 9 deletions src/adapters/soundcloud.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import * as config from '~/config/default';
import { MetadataType, ServiceType } from '~/config/enum';
import { RESPONSE_COMPARE_MIN_SCORE } from '~/config/constants';

import HttpClient from '~/utils/http-client';
import { logger } from '~/utils/logger';
import { getCheerioDoc } from '~/utils/scraper';

import { SpotifyContentLink, SpotifyContentLinkType } from '~/services/search';
import { SpotifyMetadata, SpotifyMetadataType } from '~/parsers/spotify';
import { getResultWithBestScore } from '~/utils/compare';

export async function getSoundCloudLink(query: string, metadata: SpotifyMetadata) {
if (metadata.type === SpotifyMetadataType.Show) {
import { SearchMetadata, SearchResultLink } from '~/services/search';
import { cacheSearchResultLink, getCachedSearchResultLink } from '~/services/cache';

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

Expand All @@ -20,6 +22,12 @@ export async function getSoundCloudLink(query: string, metadata: SpotifyMetadata
const url = new URL(`${config.services.soundCloud.baseUrl}/search`);
url.search = params.toString();

const cache = await getCachedSearchResultLink(url);
if (cache) {
logger.info(`[SoundCloud] (${url}) cache hit`);
return cache;
}

try {
const html = await HttpClient.get<string>(url.toString());
const doc = getCheerioDoc(html);
Expand All @@ -30,13 +38,21 @@ export async function getSoundCloudLink(query: string, metadata: SpotifyMetadata

const listElements = noscriptDoc('ul:nth-of-type(2) li:lt(3) h2 a');

const { href } = getResultWithBestScore(noscriptDoc, listElements, query);
const { href, score } = getResultWithBestScore(noscriptDoc, listElements, query);

return {
type: SpotifyContentLinkType.SoundCloud,
if (score <= RESPONSE_COMPARE_MIN_SCORE) {
return;
}

const searchResultLink = {
type: ServiceType.SoundCloud,
url: `${config.services.soundCloud.baseUrl}${href}`,
isVerified: true,
} as SpotifyContentLink;
} as SearchResultLink;

await cacheSearchResultLink(url, searchResultLink);

return searchResultLink;
} catch (err) {
logger.error(`[SoundCloud] (${url}) ${err}`);
}
Expand Down
Loading

0 comments on commit db81549

Please sign in to comment.