[O] Filesystem -> mongodb
This commit is contained in:
@@ -0,0 +1,84 @@
|
|||||||
|
import * as ne from '@neteasecloudmusicapienhanced/api'
|
||||||
|
import { aiParseLyrics } from '../../shared/lyricsParse'
|
||||||
|
import type { NeteaseSongBrief } from '../../shared/types'
|
||||||
|
import { db } from './db'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional wrapper to cache API results to MongoDB.
|
||||||
|
* @param collectionName Name of the MongoDB collection.
|
||||||
|
* @param fn Function to get data if not cached.
|
||||||
|
* @param keyFn Function to get cache key from argument. Defaults to using the argument directly.
|
||||||
|
* @returns Function that gets data from cache or calls fn and caches result.
|
||||||
|
*/
|
||||||
|
const cached = <T, R>(collectionName: string, fn: (arg: T) => Promise<R>, keyFn: (arg: T) => any = (x) => x): (arg: T, noCache?: boolean) => Promise<R> =>
|
||||||
|
async (arg: T, noCache = false): Promise<R> => {
|
||||||
|
const key = keyFn(arg)
|
||||||
|
if (!noCache) {
|
||||||
|
const doc = await db.collection(collectionName).findOne({ _id: key })
|
||||||
|
if (doc) return doc.data
|
||||||
|
}
|
||||||
|
const result = await fn(arg)
|
||||||
|
await db.collection(collectionName).replaceOne({ _id: key }, { _id: key, data: result }, { upsert: true })
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a playlist reference (URL or ID) to an ID number.
|
||||||
|
*/
|
||||||
|
function parsePlaylistRef(ref: string): number {
|
||||||
|
const urlMatch = ref.match(/playlist\?id=(\d+)/)
|
||||||
|
if (urlMatch) return +urlMatch[1]
|
||||||
|
if (/^\d+$/.test(ref)) return +ref
|
||||||
|
throw new Error('Invalid playlist reference')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get raw playlist data from cache or netease API.
|
||||||
|
*/
|
||||||
|
const getPlaylistRaw = cached('playlists',
|
||||||
|
async (id: number) => {
|
||||||
|
const pl = ((await ne.playlist_detail({ id })).body as any)
|
||||||
|
|
||||||
|
// Save each song
|
||||||
|
for (const track of pl.playlist.tracks) {
|
||||||
|
await db.collection('songs').replaceOne({ _id: track.id }, { _id: track.id, data: track }, { upsert: true })
|
||||||
|
}
|
||||||
|
return pl
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getSongMeta = cached('songs',
|
||||||
|
async (songId: number) => {
|
||||||
|
const detail = await ne.song_detail({ ids: songId.toString() })
|
||||||
|
return detail.body.songs[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
export const parseBrief = (songData: any): NeteaseSongBrief => ({
|
||||||
|
id: songData.id,
|
||||||
|
name: songData.name,
|
||||||
|
album: songData.al.name,
|
||||||
|
albumId: songData.al.id,
|
||||||
|
albumPic: songData.al.picUrl,
|
||||||
|
artists: songData.ar.map((ar: any) => ({ id: ar.id, name: ar.name }))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of songs from a playlist reference.
|
||||||
|
*/
|
||||||
|
export async function getSongsFromPlaylist(ref: string): Promise<any[]> {
|
||||||
|
const playlistId = parsePlaylistRef(ref)
|
||||||
|
const plData = await getPlaylistRaw(playlistId)
|
||||||
|
return plData.playlist.tracks.map(parseBrief)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NeteaseLyricsResponse { lrc: { lyric: string } }
|
||||||
|
|
||||||
|
export const getLyricsRaw = cached('lyrics_raw',
|
||||||
|
async (songId: number) => (await ne.lyric({ id: songId })).body as any as NeteaseLyricsResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
export const getLyricsProcessed = cached('lyrics_processed',
|
||||||
|
async (songId: number) => {
|
||||||
|
const raw = await getLyricsRaw(songId)
|
||||||
|
return aiParseLyrics(raw.lrc.lyric)
|
||||||
|
})
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// import { log } from 'console';
|
// import { log } from 'console';
|
||||||
import { getSongMeta, parseBrief } from '../shared/songs';
|
import { getSongMeta, parseBrief } from '../lib/server/songs';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params }) => {
|
export const load: PageServerLoad = async ({ params }) => {
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
import * as ne from '@neteasecloudmusicapienhanced/api';
|
|
||||||
import * as fs from 'fs/promises';
|
|
||||||
import * as path from 'path';
|
|
||||||
import { aiParseLyrics } from './lyricsParse';
|
|
||||||
import type { NeteaseSongBrief } from './types';
|
|
||||||
|
|
||||||
// Filesystem structure:
|
|
||||||
// $data/playlists/{playlistId}/detail.json
|
|
||||||
// $data/songs/{songId}/meta.json
|
|
||||||
// $data/songs/{songId}/cover.jpg
|
|
||||||
// $data/songs/{songId}/lyrics.json
|
|
||||||
// $data/songs/{songId}/lyricsProcessed.json
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functional wrapper to cache API results to filesystem.
|
|
||||||
* @param filePath Function to get cache file path from argument.
|
|
||||||
* @param fn Function to get data if not cached.
|
|
||||||
* @returns Function that gets data from cache or calls fn and caches result.
|
|
||||||
*/
|
|
||||||
const cached = <T, R>(filePath: (arg: T) => Promise<string>, fn: (arg: T) => Promise<R>): (arg: T, noCache?: boolean) => Promise<R> =>
|
|
||||||
async (arg: T, noCache = false): Promise<R> => {
|
|
||||||
const file = await filePath(arg)
|
|
||||||
if (!noCache && await fs.stat(file).catch(() => false)) {
|
|
||||||
const data = await fs.readFile(file, 'utf-8')
|
|
||||||
return JSON.parse(data) as R
|
|
||||||
}
|
|
||||||
const result = await fn(arg)
|
|
||||||
await fs.mkdir(path.dirname(file), { recursive: true })
|
|
||||||
await fs.writeFile(file, JSON.stringify(result), 'utf-8')
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a playlist reference (URL or ID) to an ID number.
|
|
||||||
*/
|
|
||||||
function parsePlaylistRef(ref: string): number {
|
|
||||||
const urlMatch = ref.match(/playlist\?id=(\d+)/)
|
|
||||||
if (urlMatch) return +urlMatch[1]
|
|
||||||
if (/^\d+$/.test(ref)) return +ref
|
|
||||||
throw new Error('Invalid playlist reference')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get raw playlist data from cache or netease API.
|
|
||||||
*/
|
|
||||||
const getPlaylistRaw = cached(
|
|
||||||
async ({ id }: { id: number }) => path.join('data', 'playlists', `${id}`, 'detail.json'),
|
|
||||||
async ({ id }: { id: number }) => {
|
|
||||||
const pl = ((await ne.playlist_detail({ id })).body as any)
|
|
||||||
|
|
||||||
// Save each song
|
|
||||||
for (const track of pl.playlist.tracks) {
|
|
||||||
const p = path.join('data', 'songs', `${track.id}`, 'meta.json')
|
|
||||||
await fs.mkdir(path.dirname(p), { recursive: true })
|
|
||||||
await fs.writeFile(p, JSON.stringify(track), 'utf-8')
|
|
||||||
}
|
|
||||||
return pl
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getSongMeta = cached(
|
|
||||||
async (songId: number) => path.join('data', 'songs', `${songId}`, 'meta.json'),
|
|
||||||
async (songId: number) => {
|
|
||||||
const detail = await ne.song_detail({ ids: songId.toString() })
|
|
||||||
return detail.body.songs[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
export const parseBrief = (songData: any): NeteaseSongBrief => ({
|
|
||||||
id: songData.id,
|
|
||||||
name: songData.name,
|
|
||||||
album: songData.al.name,
|
|
||||||
albumId: songData.al.id,
|
|
||||||
albumPic: songData.al.picUrl,
|
|
||||||
artists: songData.ar.map((ar: any) => ({ id: ar.id, name: ar.name }))
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of songs from a playlist reference.
|
|
||||||
*/
|
|
||||||
export async function getSongsFromPlaylist(ref: string): Promise<any[]> {
|
|
||||||
const playlistId = parsePlaylistRef(ref)
|
|
||||||
const plData = await getPlaylistRaw({ id: playlistId })
|
|
||||||
return plData.playlist.tracks.map(parseBrief)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NeteaseLyricsResponse { lrc: { lyric: string } }
|
|
||||||
|
|
||||||
export const getLyricsRaw = cached(
|
|
||||||
async (songId: number) => path.join('data', 'songs', `${songId}`, 'lyrics.json'),
|
|
||||||
async (songId: number) => (await ne.lyric({ id: songId })).body as any as NeteaseLyricsResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
export const getLyricsProcessed = cached(
|
|
||||||
async (songId: number) => path.join('data', 'songs', `${songId}`, 'lyricsProcessed.json'),
|
|
||||||
async (songId: number) => {
|
|
||||||
const raw = await getLyricsRaw(songId)
|
|
||||||
return aiParseLyrics(raw.lrc.lyric)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// console.log((await getSongsFromPlaylist('580208139')).length)
|
|
||||||
// console.log(await getLyricsProcessed(25723366))
|
|
||||||
Reference in New Issue
Block a user