mirror of
https://git.gay/lucida/lucida
synced 2025-12-11 20:15:14 +01:00
add spotify support
This commit is contained in:
parent
8af3b9f926
commit
c321cfab05
14
README.md
14
README.md
@ -10,6 +10,7 @@ Lucida is made to use few NodeJS dependencies and no system dependencies (...bes
|
||||
import Lucida from 'lucida'
|
||||
import Tidal from 'lucida/streamers/tidal/main.js'
|
||||
import Qobuz from 'lucida/streamers/qobuz/main.js'
|
||||
import Spotify from 'lucida/streamers/spotify/main.js'
|
||||
|
||||
const lucida = new Lucida({
|
||||
modules: {
|
||||
@ -18,6 +19,9 @@ const lucida = new Lucida({
|
||||
}),
|
||||
qobuz: new Qobuz({
|
||||
// tokens
|
||||
}),
|
||||
spotify: new Spotify({
|
||||
// options
|
||||
})
|
||||
// Any other modules
|
||||
},
|
||||
@ -25,13 +29,23 @@ const lucida = new Lucida({
|
||||
qobuz: {
|
||||
username: '',
|
||||
password: ''
|
||||
},
|
||||
spotify: {
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// only needed if using modules which use the logins configuration rather than tokens
|
||||
await lucida.login()
|
||||
|
||||
const track = await lucida.getByUrl('https://tidal.com/browse/track/255207223')
|
||||
|
||||
await fs.promises.writeFile('test.flac', (await track.getStream()).stream)
|
||||
|
||||
// only needed for modules which create persistent connections (of the built-in modules, this is just Spotify)
|
||||
await lucida.disconnect()
|
||||
```
|
||||
|
||||
For using a specific module, you can just use the functions built into the `Streamer` interface.
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
"dependencies": {
|
||||
"dotenv": "^16.1.4",
|
||||
"image-size": "^1.0.2",
|
||||
"librespot": "^0.1.3",
|
||||
"node-fetch": "^3.3.1",
|
||||
"xmldom-qsa": "^1.1.3"
|
||||
},
|
||||
|
||||
130
pnpm-lock.yaml
generated
130
pnpm-lock.yaml
generated
@ -11,6 +11,9 @@ dependencies:
|
||||
image-size:
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2
|
||||
librespot:
|
||||
specifier: ^0.1.3
|
||||
version: 0.1.3
|
||||
node-fetch:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
@ -126,13 +129,55 @@ packages:
|
||||
fastq: 1.15.0
|
||||
dev: true
|
||||
|
||||
/@protobufjs/aspromise@1.1.2:
|
||||
resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
|
||||
dev: false
|
||||
|
||||
/@protobufjs/base64@1.1.2:
|
||||
resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
|
||||
dev: false
|
||||
|
||||
/@protobufjs/codegen@2.0.4:
|
||||
resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
|
||||
dev: false
|
||||
|
||||
/@protobufjs/eventemitter@1.1.0:
|
||||
resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
|
||||
dev: false
|
||||
|
||||
/@protobufjs/fetch@1.1.0:
|
||||
resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
|
||||
dependencies:
|
||||
'@protobufjs/aspromise': 1.1.2
|
||||
'@protobufjs/inquire': 1.1.0
|
||||
dev: false
|
||||
|
||||
/@protobufjs/float@1.0.2:
|
||||
resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
|
||||
dev: false
|
||||
|
||||
/@protobufjs/inquire@1.1.0:
|
||||
resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
|
||||
dev: false
|
||||
|
||||
/@protobufjs/path@1.1.2:
|
||||
resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
|
||||
dev: false
|
||||
|
||||
/@protobufjs/pool@1.1.0:
|
||||
resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
|
||||
dev: false
|
||||
|
||||
/@protobufjs/utf8@1.1.0:
|
||||
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
|
||||
dev: false
|
||||
|
||||
/@types/json-schema@7.0.13:
|
||||
resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==}
|
||||
dev: true
|
||||
|
||||
/@types/node@20.2.5:
|
||||
resolution: {integrity: sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==}
|
||||
dev: true
|
||||
|
||||
/@types/semver@7.5.2:
|
||||
resolution: {integrity: sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==}
|
||||
@ -551,6 +596,13 @@ packages:
|
||||
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
||||
dev: true
|
||||
|
||||
/fast-xml-parser@4.2.7:
|
||||
resolution: {integrity: sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
strnum: 1.0.5
|
||||
dev: false
|
||||
|
||||
/fastq@1.15.0:
|
||||
resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
|
||||
dependencies:
|
||||
@ -763,6 +815,17 @@ packages:
|
||||
type-check: 0.4.0
|
||||
dev: true
|
||||
|
||||
/librespot@0.1.3:
|
||||
resolution: {integrity: sha512-G9AGVUunh6zK8tTJE1kwod4gYge2nFJw8yyhCWBzK36HkZBXiHLMEKU1Yq8ANmXMPPPV7ffUFNOJFzJWqWFMwA==}
|
||||
dependencies:
|
||||
fast-xml-parser: 4.2.7
|
||||
node-fetch: 2.7.0
|
||||
protobufjs: 7.2.5
|
||||
shannon-bindings: 0.1.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/locate-path@6.0.0:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
@ -774,6 +837,10 @@ packages:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
|
||||
/long@5.2.3:
|
||||
resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
|
||||
dev: false
|
||||
|
||||
/lru-cache@6.0.0:
|
||||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||
engines: {node: '>=10'}
|
||||
@ -812,11 +879,27 @@ packages:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
dev: true
|
||||
|
||||
/node-addon-api@1.7.2:
|
||||
resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==}
|
||||
dev: false
|
||||
|
||||
/node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
dev: false
|
||||
|
||||
/node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
dev: false
|
||||
|
||||
/node-fetch@3.3.1:
|
||||
resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
@ -901,6 +984,25 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/protobufjs@7.2.5:
|
||||
resolution: {integrity: sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@protobufjs/aspromise': 1.1.2
|
||||
'@protobufjs/base64': 1.1.2
|
||||
'@protobufjs/codegen': 2.0.4
|
||||
'@protobufjs/eventemitter': 1.1.0
|
||||
'@protobufjs/fetch': 1.1.0
|
||||
'@protobufjs/float': 1.0.2
|
||||
'@protobufjs/inquire': 1.1.0
|
||||
'@protobufjs/path': 1.1.2
|
||||
'@protobufjs/pool': 1.1.0
|
||||
'@protobufjs/utf8': 1.1.0
|
||||
'@types/node': 20.2.5
|
||||
long: 5.2.3
|
||||
dev: false
|
||||
|
||||
/punycode@2.3.0:
|
||||
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
||||
engines: {node: '>=6'}
|
||||
@ -947,6 +1049,13 @@ packages:
|
||||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/shannon-bindings@0.1.0:
|
||||
resolution: {integrity: sha512-svz5ewuAGfrxibOe5GVfPfvjs8FwHEixTW2K3cUEDDt6tCnxTsFzqi0JoQ//45EGYoxxjX2uES3msWE41XVaIQ==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
node-addon-api: 1.7.2
|
||||
dev: false
|
||||
|
||||
/shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
@ -976,6 +1085,10 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/strnum@1.0.5:
|
||||
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
|
||||
dev: false
|
||||
|
||||
/supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
@ -994,6 +1107,10 @@ packages:
|
||||
is-number: 7.0.0
|
||||
dev: true
|
||||
|
||||
/tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
dev: false
|
||||
|
||||
/tslib@1.14.1:
|
||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
dev: true
|
||||
@ -1037,6 +1154,17 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
dev: false
|
||||
|
||||
/webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
dev: false
|
||||
|
||||
/whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
dev: false
|
||||
|
||||
/which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
@ -25,7 +25,7 @@ class Lucida {
|
||||
if (!this.logins) throw new Error('No logins specified')
|
||||
for (const i in this.logins) {
|
||||
const credentials = this.logins[i]
|
||||
await this.modules[i].login?.(credentials.username, credentials.password)
|
||||
await this.modules[i]?.login?.(credentials.username, credentials.password)
|
||||
}
|
||||
}
|
||||
async search(query: string, limit: number): Promise<{ [key: string]: SearchResults }> {
|
||||
@ -53,6 +53,13 @@ class Lucida {
|
||||
}
|
||||
throw new Error(`Couldn't find module for hostname ${urlObj.hostname}`)
|
||||
}
|
||||
disconnect() {
|
||||
return Promise.all(
|
||||
Object.values(this.modules).map((e) => {
|
||||
return e.disconnect?.()
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Lucida
|
||||
|
||||
84
src/streamers/spotify/main.ts
Normal file
84
src/streamers/spotify/main.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import Librespot from 'librespot'
|
||||
import { parseArtist, parseAlbum, parseTrack } from './parse.js'
|
||||
import { GetByUrlResponse, SearchResults, StreamerWithLogin } from '../../types.js'
|
||||
import { SpotifyAlbum, SpotifyArtist } from 'librespot/build/utils/types'
|
||||
|
||||
class Spotify implements StreamerWithLogin {
|
||||
client: Librespot
|
||||
hostnames = ['open.spotify.com']
|
||||
constructor(options: never) {
|
||||
this.client = new Librespot(options)
|
||||
}
|
||||
login(username: string, password: string) {
|
||||
return this.client.login(username, password)
|
||||
}
|
||||
#getUrlParts(url: string): ['artist' | 'album' | 'track', string] {
|
||||
const urlObj = new URL(url)
|
||||
const parts = urlObj.pathname.slice(1).split('/')
|
||||
if (parts.length > 2) throw new Error('Unknown Spotify URL')
|
||||
if (parts[0] != 'artist' && parts[0] != 'track' && parts[0] != 'album') {
|
||||
throw new Error(`Spotify type "${parts[0]}" unsupported`)
|
||||
}
|
||||
if (!parts[1]) throw new Error('Unknown Spotify URL')
|
||||
return [parts[0], parts[1]]
|
||||
}
|
||||
getTypeFromUrl(url: string) {
|
||||
return this.#getUrlParts(url)[0]
|
||||
}
|
||||
async getByUrl(url: string): Promise<GetByUrlResponse> {
|
||||
const [type, id] = this.#getUrlParts(url)
|
||||
if (type == 'track') {
|
||||
const metadata = await this.client.get.trackMetadata(id)
|
||||
return {
|
||||
type,
|
||||
getStream: async () => {
|
||||
const streamData = await this.client.get.trackStream(id)
|
||||
return {
|
||||
mimeType: 'audio/ogg',
|
||||
sizeBytes: streamData.sizeBytes,
|
||||
stream: streamData.stream
|
||||
}
|
||||
},
|
||||
metadata: parseTrack(metadata)
|
||||
}
|
||||
}
|
||||
const data = await this.client.get.byUrl(url)
|
||||
let metadata
|
||||
switch (type) {
|
||||
case 'artist':
|
||||
metadata = parseArtist(<SpotifyArtist>data)
|
||||
return {
|
||||
type,
|
||||
metadata
|
||||
}
|
||||
case 'album':
|
||||
metadata = parseAlbum(<SpotifyAlbum>data)
|
||||
if ((<SpotifyAlbum>data).tracks) {
|
||||
return {
|
||||
type,
|
||||
metadata,
|
||||
tracks: (<SpotifyAlbum>data).tracks?.map((e) => parseTrack(e)) ?? []
|
||||
}
|
||||
}
|
||||
return {
|
||||
type,
|
||||
metadata,
|
||||
tracks: []
|
||||
}
|
||||
}
|
||||
}
|
||||
async search(query: string): Promise<SearchResults> {
|
||||
const results = await this.client.browse.search(query)
|
||||
return {
|
||||
query,
|
||||
albums: results.albums?.map((e) => parseAlbum(e)) ?? [],
|
||||
artists: results.artists?.map((e) => parseArtist(e)) ?? [],
|
||||
tracks: results.tracks?.map((e) => parseTrack(e)) ?? []
|
||||
}
|
||||
}
|
||||
disconnect() {
|
||||
return this.client.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
export default Spotify
|
||||
57
src/streamers/spotify/parse.ts
Normal file
57
src/streamers/spotify/parse.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import type {
|
||||
SpotifyAlbum,
|
||||
SpotifyArtist,
|
||||
SpotifyThumbnail,
|
||||
SpotifyTrack
|
||||
} from 'librespot/build/utils/types'
|
||||
import { Album, Artist, Track } from '../../types'
|
||||
|
||||
function parseThumbnails(raw: SpotifyThumbnail[]) {
|
||||
return raw
|
||||
.sort((a, b) => (a.width ?? 0) - (b.width ?? 0))
|
||||
.map((e) => {
|
||||
return {
|
||||
width: e.width ?? 0,
|
||||
height: e.height ?? 0,
|
||||
url: e.url
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function parseArtist(raw: SpotifyArtist) {
|
||||
const artist: Artist = {
|
||||
id: raw.id,
|
||||
url: raw.externalUrl,
|
||||
name: raw.name
|
||||
}
|
||||
if (raw.avatar) artist.pictures = parseThumbnails(raw.avatar).map((e) => e.url)
|
||||
if (raw.albums) artist.albums = raw.albums.map((e) => parseAlbum(e))
|
||||
return artist
|
||||
}
|
||||
|
||||
export function parseTrack(raw: SpotifyTrack) {
|
||||
const track: Track = {
|
||||
title: raw.name,
|
||||
id: raw.id,
|
||||
url: raw.externalUrl,
|
||||
explicit: raw.explicit,
|
||||
trackNumber: raw.trackNumber,
|
||||
discNumber: raw.discNumber,
|
||||
artists: raw.artists?.map((e) => parseArtist(e)) ?? [],
|
||||
durationMs: raw.durationMs
|
||||
}
|
||||
if (raw.album) track.album = parseAlbum(raw.album)
|
||||
return track
|
||||
}
|
||||
|
||||
export function parseAlbum(raw: SpotifyAlbum): Album {
|
||||
return {
|
||||
title: raw.name,
|
||||
id: raw.id,
|
||||
url: raw.externalUrl,
|
||||
trackCount: raw.totalTracks,
|
||||
releaseDate: raw.releaseDate,
|
||||
coverArtwork: parseThumbnails(raw.coverArtwork),
|
||||
artists: raw.artists.map((e) => parseArtist(e))
|
||||
}
|
||||
}
|
||||
@ -86,6 +86,7 @@ export interface Streamer {
|
||||
search(query: string, limit: number): Promise<SearchResults>
|
||||
getTypeFromUrl(url: string): ItemType
|
||||
getByUrl(url: string): Promise<GetByUrlResponse>
|
||||
disconnect?(): Promise<void>
|
||||
}
|
||||
|
||||
export interface StreamerWithLogin extends Streamer {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user