Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ declare module 'vue' {
IconLucideCloudUpload: typeof import('~icons/lucide/cloud-upload')['default']
IconLucideCopy: typeof import('~icons/lucide/copy')['default']
IconLucideDatabase: typeof import('~icons/lucide/database')['default']
IconLucideDisc: typeof import('~icons/lucide/disc')['default']
IconLucideDisc3: typeof import('~icons/lucide/disc3')['default']
IconLucideDownload: typeof import('~icons/lucide/download')['default']
IconLucideEllipsis: typeof import('~icons/lucide/ellipsis')['default']
Expand Down Expand Up @@ -135,6 +134,7 @@ declare module 'vue' {
IconLucideShuffle: typeof import('~icons/lucide/shuffle')['default']
IconLucideSkipBack: typeof import('~icons/lucide/skip-back')['default']
IconLucideSkipForward: typeof import('~icons/lucide/skip-forward')['default']
IconLucideSparkles: typeof import('~icons/lucide/sparkles')['default']
IconLucideTextQuote: typeof import('~icons/lucide/text-quote')['default']
IconLucideTrash2: typeof import('~icons/lucide/trash2')['default']
IconLucideTriangleAlert: typeof import('~icons/lucide/triangle-alert')['default']
Expand Down Expand Up @@ -168,6 +168,7 @@ declare module 'vue' {
NavSearch: typeof import('./src/components/layout/NavSearch.vue')['default']
NavSearchInput: typeof import('./src/components/layout/NavSearchInput.vue')['default']
NavUser: typeof import('./src/components/layout/NavUser.vue')['default']
NewReleaseGroup: typeof import('./src/components/list/NewReleaseGroup.vue')['default']
NumberFieldDecrement: typeof import('reka-ui')['NumberFieldDecrement']
NumberFieldIncrement: typeof import('reka-ui')['NumberFieldIncrement']
NumberFieldInput: typeof import('reka-ui')['NumberFieldInput']
Expand Down
2 changes: 2 additions & 0 deletions electron/main/apis/netease/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ const NON_CACHEABLE: ReadonlySet<string> = new Set([
"personal_fm",
"fm_trash",
"recommend_songs",
"artist_new_song_playall",
"artist_new_song_mv_list_v2",
]);

/** 国内 IP 前缀池 */
Expand Down
16 changes: 16 additions & 0 deletions electron/main/apis/netease/modules/artist_new_song_mv_list_v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/** 关注歌手的新歌曲发布流(需登录) */

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

const artistNewSongMvListV2: NeteaseModule = (query, request) => {
const data = {
startTimestamp: query.startTimestamp || Date.now(),
sourceType: query.sourceType || 1,
limit: query.limit || 10,
firstRequest: query.firstRequest ?? true,
};
return request("/api/sub/artist/new/works/song-mv/list/v2", data, createOption(query, "eapi"));
};

export default artistNewSongMvListV2;
13 changes: 13 additions & 0 deletions electron/main/apis/netease/modules/artist_new_song_playall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* 所有关注歌手最近的 50 首新歌(需登录)
*
* 响应:`{ code, data: { count, songList: NeteaseSong[] } }`
*/

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

const artistNewSongPlayall: NeteaseModule = (query, request) =>
request("/api/sub/artist/new/works/song/playall", {}, createOption(query, "eapi"));

export default artistNewSongPlayall;
4 changes: 4 additions & 0 deletions electron/main/apis/netease/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ import personalized from "./personalized";
import recommend_resource from "./recommend_resource";
import top_artists from "./top_artists";
import album_new from "./album_new";
import artist_new_song_playall from "./artist_new_song_playall";
import artist_new_song_mv_list_v2 from "./artist_new_song_mv_list_v2";

// 歌单 / 喜欢
import playlist_detail from "./playlist_detail";
Expand Down Expand Up @@ -164,6 +166,8 @@ export const modules: Record<string, NeteaseModule> = {
recommend_resource,
top_artists,
album_new,
artist_new_song_playall,
artist_new_song_mv_list_v2,

playlist_detail,
playlist_create,
Expand Down
121 changes: 120 additions & 1 deletion src/apis/recommend/netease.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import type { Track } from "@shared/types/player";
import type { CoverItem } from "@/types/artist";
import type { NeteaseSong } from "@/types/netease";
import { netease as neteaseApi } from "@/apis/netease";
import { songsToTracks, withPicSize, toPlaylist, toArtist, toAlbum } from "@/utils/format/netease";
import {
ensureOk,
songsToTracks,
withPicSize,
toPlaylist,
toArtist,
toAlbum,
} from "@/utils/format/netease";
import { playlistToCoverItem, artistToCoverItem, albumToCoverItem } from "@/utils/format/coverItem";

/** 每日推荐歌曲(每日 30 首,需登录) */
Expand All @@ -11,6 +18,118 @@ export const fetchDailySongs = async (): Promise<Track[]> => {
return songsToTracks(body?.data?.dailySongs);
};

interface FollowedArtistPlayAllResponse {
code?: number;
data?: {
count?: number;
songList?: NeteaseSong[];
};
}

/** 播放所有关注歌手最近的 50 首新歌(需登录) */
export const fetchFollowedArtistPlayAll = async (): Promise<Track[]> => {
const body = ensureOk(await neteaseApi.artist_new_song_playall<FollowedArtistPlayAllResponse>());
return songsToTracks(body.data?.songList);
};

interface RawNewReleaseTitle {
artistName: string;
artistId: number;
artistUserId?: number;
imgUrl?: string;
resourceId: number;
resourceName: string;
resourcePicUrl?: string;
publishTime: number;
}

interface RawNewReleaseWork {
sourceType: number;
info?: {
blockTitle?: RawNewReleaseTitle;
blockType?: string;
songLists?: NeteaseSong[];
publishTime?: number;
albumSongCount?: number;
};
}

interface FollowedArtistNewWorksResponse {
code?: number;
data?: {
hasMore?: boolean;
latestVisitTime?: number;
newSongCount?: number;
newWorks?: RawNewReleaseWork[];
};
}

export interface FollowedArtistNewWork {
id: string;
sourceType: number;
blockType: string;
artistId: string;
artistName: string;
artistAvatar?: string;
resourceId: string;
resourceName: string;
resourceCover?: string;
publishTime: number;
albumSongCount: number;
tracks: Track[];
}

export interface FollowedArtistNewWorksPage {
works: FollowedArtistNewWork[];
hasMore: boolean;
newSongCount: number;
nextTimestamp?: number;
}

/** 关注歌手新歌发布流,按歌手和专辑/单曲分块 */
export const fetchFollowedArtistNewWorks = async (params: {
startTimestamp: number;
firstRequest: boolean;
limit?: number;
}): Promise<FollowedArtistNewWorksPage> => {
const body = ensureOk(
await neteaseApi.artist_new_song_mv_list_v2<FollowedArtistNewWorksResponse>({
startTimestamp: params.startTimestamp,
sourceType: 1,
limit: params.limit ?? 10,
firstRequest: params.firstRequest,
}),
);
const works = (body.data?.newWorks ?? []).flatMap<FollowedArtistNewWork>((raw) => {
const title = raw.info?.blockTitle;
if (!title) return [];
const tracks = songsToTracks(raw.info?.songLists);
return [
{
id: `${raw.sourceType}:${title.resourceId}:${title.publishTime}`,
sourceType: raw.sourceType,
blockType: raw.info?.blockType ?? "song",
artistId: String(title.artistId),
artistName: title.artistName,
artistAvatar: withPicSize(title.imgUrl, 100),
resourceId: String(title.resourceId),
resourceName: title.resourceName,
resourceCover: withPicSize(title.resourcePicUrl) ?? tracks[0]?.cover,
publishTime: raw.info?.publishTime ?? title.publishTime,
albumSongCount: raw.info?.albumSongCount ?? tracks.length,
tracks,
},
];
});
const lastTimestamp = works.at(-1)?.publishTime;
return {
works,
hasMore: body.data?.hasMore ?? false,
newSongCount: body.data?.newSongCount ?? 0,
nextTimestamp: lastTimestamp === undefined ? undefined : Math.max(0, lastTimestamp - 1),
};
};

/**
* 心动模式智能播放列表
* @param seedId - 种子歌曲 id
Expand Down
6 changes: 3 additions & 3 deletions src/components/layout/SideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import IconLucideStar from "~icons/lucide/star";
import IconLucideHistory from "~icons/lucide/history";
import IconLucideDownload from "~icons/lucide/download";
import IconLucideCloud from "~icons/lucide/cloud";
import IconLucideSparkles from "~icons/lucide/sparkles";
import IconLucidePlus from "~icons/lucide/plus";
import IconLucideChevronDown from "~icons/lucide/chevron-down";
import IconSpHeartMode from "~icons/sp/heart-mode";
Expand Down Expand Up @@ -58,9 +59,7 @@ const handleCreate = (): void => {

/** 新建成功后跳转到该歌单 */
const handleCreated = (playlistId: string, scope: ContentScope): void => {
router.push(
`/collection/${scope === "local" ? "local" : "netease"}/playlist/${playlistId}`,
);
router.push(`/collection/${scope === "local" ? "local" : "netease"}/playlist/${playlistId}`);
};

/** 我的歌单分组头部 */
Expand Down Expand Up @@ -169,6 +168,7 @@ const menuItems = computed<SMenuItem[]>(() => [
),
},
{ key: "/favorites", label: t("nav.favorites"), icon: markRaw(IconLucideStar) },
{ key: "/new-releases", label: t("nav.newReleases"), icon: markRaw(IconLucideSparkles) },
{ key: "/cloud", label: t("nav.cloud"), icon: markRaw(IconLucideCloud) },
...(systemSettings.download.enabled
? ([
Expand Down
Loading