Skip to content
Merged
16 changes: 16 additions & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ declare module 'vue' {
ContextMenuSubContent: typeof import('reka-ui')['ContextMenuSubContent']
ContextMenuSubTrigger: typeof import('reka-ui')['ContextMenuSubTrigger']
ContextMenuTrigger: typeof import('reka-ui')['ContextMenuTrigger']
CoverCard: typeof import('./src/components/list/CoverCard.vue')['default']
CoverList: typeof import('./src/components/list/CoverList.vue')['default']
DbCacheManager: typeof import('./src/components/settings/custom/DbCacheManager.vue')['default']
DeviceSelector: typeof import('./src/components/settings/custom/DeviceSelector.vue')['default']
Expand Down Expand Up @@ -63,8 +64,10 @@ declare module 'vue' {
FullPlayer: typeof import('./src/components/player/FullPlayer/index.vue')['default']
HotkeyConfig: typeof import('./src/components/settings/custom/HotkeyConfig.vue')['default']
IconLucideArrowRight: typeof import('~icons/lucide/arrow-right')['default']
IconLucideArrowRightToLine: typeof import('~icons/lucide/arrow-right-to-line')['default']
IconLucideArrowUp: typeof import('~icons/lucide/arrow-up')['default']
IconLucideArrowUpCircle: typeof import('~icons/lucide/arrow-up-circle')['default']
IconLucideCalendarDays: typeof import('~icons/lucide/calendar-days')['default']
IconLucideCheck: typeof import('~icons/lucide/check')['default']
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
IconLucideChevronLeft: typeof import('~icons/lucide/chevron-left')['default']
Expand All @@ -83,15 +86,23 @@ declare module 'vue' {
IconLucideFolderPlus: typeof import('~icons/lucide/folder-plus')['default']
IconLucideGithub: typeof import('~icons/lucide/github')['default']
IconLucideHardDrive: typeof import('~icons/lucide/hard-drive')['default']
IconLucideHeadphones: typeof import('~icons/lucide/headphones')['default']
IconLucideHeart: typeof import('~icons/lucide/heart')['default']
IconLucideHeartOff: typeof import('~icons/lucide/heart-off')['default']
IconLucideHistory: typeof import('~icons/lucide/history')['default']
IconLucideInfinity: typeof import('~icons/lucide/infinity')['default']
IconLucideInfo: typeof import('~icons/lucide/info')['default']
IconLucideKeyRound: typeof import('~icons/lucide/key-round')['default']
IconLucideLink: typeof import('~icons/lucide/link')['default']
IconLucideList: typeof import('~icons/lucide/list')['default']
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideListMusic: typeof import('~icons/lucide/list-music')['default']
IconLucideListOrdered: typeof import('~icons/lucide/list-ordered')['default']
IconLucideLocate: typeof import('~icons/lucide/locate')['default']
IconLucideLock: typeof import('~icons/lucide/lock')['default']
IconLucideLogOut: typeof import('~icons/lucide/log-out')['default']
IconLucideMaximize: typeof import('~icons/lucide/maximize')['default']
IconLucideMenu: typeof import('~icons/lucide/menu')['default']
IconLucideMessageCircle: typeof import('~icons/lucide/message-circle')['default']
IconLucideMic: typeof import('~icons/lucide/mic')['default']
IconLucideMicVocal: typeof import('~icons/lucide/mic-vocal')['default']
Expand Down Expand Up @@ -125,7 +136,12 @@ declare module 'vue' {
IconLucideX: typeof import('~icons/lucide/x')['default']
IconLucideZap: typeof import('~icons/lucide/zap')['default']
IconMaterialSymbolsFavoriteOutlineRounded: typeof import('~icons/material-symbols/favorite-outline-rounded')['default']
IconMaterialSymbolsPlaylistPlayRounded: typeof import('~icons/material-symbols/playlist-play-rounded')['default']
IconMaterialSymbolsShuffleRounded: typeof import('~icons/material-symbols/shuffle-rounded')['default']
IconSpHeartMode: typeof import('~icons/sp/heart-mode')['default']
IconSpLossless: typeof import('~icons/sp/lossless')['default']
IconSpPlayOrder: typeof import('~icons/sp/play-order')['default']
IconSpRepeatOff: typeof import('~icons/sp/repeat-off')['default']
LoginCookieDialog: typeof import('./src/components/modals/LoginCookieDialog.vue')['default']
LoginDialog: typeof import('./src/components/modals/LoginDialog.vue')['default']
LyricFormatOrderConfig: typeof import('./src/components/settings/custom/LyricFormatOrderConfig.vue')['default']
Expand Down
4 changes: 4 additions & 0 deletions electron/main/apis/netease/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ const NON_CACHEABLE: ReadonlySet<string> = new Set([
"user_cloud",
"user_cloud_del",
"album_sub",
"playmode_intelligence",
"personal_fm",
"fm_trash",
"recommend_songs",
]);

/** 内存缓存 */
Expand Down
15 changes: 15 additions & 0 deletions electron/main/apis/netease/modules/album_new.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* 新碟上架(无需登录)
*
* 响应:`{ code, albums: NeteaseAlbum[] }`
*/

import { createOption } from "../core/option";
import type { NeteaseModule } from "../core/types";

const albumNew: NeteaseModule = (query, request) => {
const data = { area: "ALL", offset: 0, total: true, limit: query.limit ?? 30 };
return request("/api/album/new", data, createOption(query, "weapi"));
};

export default albumNew;
22 changes: 22 additions & 0 deletions electron/main/apis/netease/modules/fm_trash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* 私人 FM 减少推荐(不喜欢,影响后续推荐)
*
* params:
* - id 歌曲 id
* - time 已播放秒数,默认 25
* - alg 推荐算法标识,默认 "RT"
*/

import { createOption } from "../core/option";
import type { NeteaseModule } from "../core/types";

const fmTrash: NeteaseModule = (query, request) => {
const data = {
songId: query.id,
alg: query.alg ?? "RT",
time: query.time ?? 25,
};
return request("/api/radio/trash/add", data, createOption(query, "weapi"));
};

export default fmTrash;
19 changes: 19 additions & 0 deletions electron/main/apis/netease/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ import cloud_lyric_get from "./cloud_lyric_get";
// 播放
import song_detail from "./song_detail";
import song_url from "./song_url";
import playmode_intelligence from "./playmode_intelligence";
import personal_fm from "./personal_fm";
import fm_trash from "./fm_trash";

// 每日推荐 / 发现
import recommend_songs from "./recommend_songs";
import personalized from "./personalized";
import recommend_resource from "./recommend_resource";
import top_artists from "./top_artists";
import album_new from "./album_new";

// 歌单 / 喜欢
import playlist_detail from "./playlist_detail";
Expand Down Expand Up @@ -119,6 +129,15 @@ export const modules: Record<string, NeteaseModule> = {

song_detail,
song_url,
playmode_intelligence,
personal_fm,
fm_trash,

recommend_songs,
personalized,
recommend_resource,
top_artists,
album_new,

playlist_detail,
playlist_create,
Expand Down
15 changes: 15 additions & 0 deletions electron/main/apis/netease/modules/personal_fm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* 私人 FM(需登录)
* 服务端按用户偏好返回一批推荐曲目,通常 3 首
*
* 响应:`{ code, data: NeteaseSong[] }`
*/

import { createOption } from "../core/option";
import type { NeteaseModule } from "../core/types";

const personalFm: NeteaseModule = (query, request) => {
return request("/api/v1/radio/get", {}, createOption(query, "weapi"));
};

export default personalFm;
15 changes: 15 additions & 0 deletions electron/main/apis/netease/modules/personalized.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* 推荐歌单(无需登录)
*
* 响应:`{ code, result: NeteasePlaylist[] }`
*/

import { createOption } from "../core/option";
import type { NeteaseModule } from "../core/types";

const personalized: NeteaseModule = (query, request) => {
const data = { limit: query.limit ?? 30, total: true, n: 1000 };
return request("/api/personalized/playlist", data, createOption(query, "weapi"));
};

export default personalized;
28 changes: 28 additions & 0 deletions electron/main/apis/netease/modules/playmode_intelligence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* 心动模式 / 智能播放列表
*
* params:
* - id 种子歌曲 id
* - pid 歌单 id(通常为「我喜欢的音乐」)
* - sid 起始歌曲 id,缺省取 id
*
* count 字段服务端必传,缺省取 1,否则返回 500。
*
* 响应:`{ code, data: [{ songInfo: NeteaseSong, recommended }] }`
*/

import { createOption } from "../core/option";
import type { NeteaseModule } from "../core/types";

const playmodeIntelligence: NeteaseModule = (query, request) => {
const data = {
songId: query.id,
type: "fromPlayOne",
playlistId: query.pid,
startMusicId: query.sid ?? query.id,
count: query.count ?? 1,
};
return request("/api/playmode/intelligence/list", data, createOption(query, "weapi"));
};

export default playmodeIntelligence;
14 changes: 14 additions & 0 deletions electron/main/apis/netease/modules/recommend_resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* 每日推荐歌单 / 专属歌单(需登录)
*
* 响应:`{ code, recommend: NeteasePlaylist[] }`
*/

import { createOption } from "../core/option";
import type { NeteaseModule } from "../core/types";

const recommendResource: NeteaseModule = (query, request) => {
return request("/api/v1/discovery/recommend/resource", {}, createOption(query, "weapi"));
};

export default recommendResource;
17 changes: 17 additions & 0 deletions electron/main/apis/netease/modules/recommend_songs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* 每日推荐歌曲(每日 30 首,需登录)
*
* 响应:`{ code, data: { dailySongs: NeteaseSong[], recommendReasons } }`
*/

import { createOption } from "../core/option";
import type { NeteaseModule } from "../core/types";

const recommendSongs: NeteaseModule = (query, request) => {
if (query.cookie && typeof query.cookie === "object") {
(query.cookie as Record<string, string>).os = "ios";
}
return request("/api/v3/discovery/recommend/songs", {}, createOption(query, "weapi"));
};

export default recommendSongs;
15 changes: 15 additions & 0 deletions electron/main/apis/netease/modules/top_artists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* 热门歌手(无需登录)
*
* 响应:`{ code, artists: NeteaseArtist[] }`
*/

import { createOption } from "../core/option";
import type { NeteaseModule } from "../core/types";

const topArtists: NeteaseModule = (query, request) => {
const data = { offset: 0, total: true, limit: query.limit ?? 50 };
return request("/api/artist/top", data, createOption(query, "weapi"));
};

export default topArtists;
26 changes: 26 additions & 0 deletions electron/main/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export const getDb = (): Database.Database => {
return db;
};

/** 数据库是否已打开 */
export const isDbOpen = (): boolean => db !== null;

/** 初始化数据库:打开连接、启用 WAL、建表建索引、执行迁移 */
export const initDatabase = (): void => {
fs.mkdirSync(dbDir, { recursive: true });
Expand Down Expand Up @@ -93,6 +96,27 @@ export const initDatabase = (): void => {
);
CREATE INDEX IF NOT EXISTS idx_song_cache_last_used ON song_cache(last_used_at);
CREATE INDEX IF NOT EXISTS idx_song_cache_source ON song_cache(source);

CREATE TABLE IF NOT EXISTS play_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
track_id TEXT NOT NULL,
source TEXT NOT NULL,
started_at INTEGER NOT NULL,
listened_ms INTEGER NOT NULL,
track_json TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_play_history_started ON play_history(started_at);
CREATE INDEX IF NOT EXISTS idx_play_history_track ON play_history(source, track_id);

CREATE TABLE IF NOT EXISTS favorite_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
track_id TEXT NOT NULL,
source TEXT NOT NULL,
action TEXT NOT NULL,
at INTEGER NOT NULL,
track_json TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_favorite_history_at ON favorite_history(at);
`);
migrate(db);
libraryLog.info(`数据库已初始化: ${dbPath}`);
Expand Down Expand Up @@ -120,6 +144,8 @@ export {
getAlbumTracks,
getArtistTracks,
getTracksByIds,
getRandomTrack,
getRandomTracks,
} from "./queries";

export type { FileRecord, UpsertTrack } from "./queries";
Loading
Loading