"use strict"; const mockGet = jest.fn(); const mockPost = jest.fn(); jest.mock("axios", () => ({ create: jest.fn(() => ({ get: mockGet, post: mockPost, })), })); const plugin = require("./netease_17000"); function makeSongUrlResponse(id, url) { return { data: { data: [ { id, url, time: 0, }, ], }, }; } describe("netease_17000 getMediaSource fallback", () => { beforeEach(() => { mockGet.mockReset(); mockPost.mockReset(); }); test("falls back to noCopyrightRcmd.songId when original id has no source", async () => { const originalId = "5268162"; const fallbackId = "2035171101"; const fallbackUrl = "http://example.com/fallback.mp3"; mockPost.mockResolvedValueOnce({ data: { primary: null, alternates: [], }, }); mockGet.mockImplementation((url, config) => { const params = (config && config.params) || {}; if (url.includes("/song/url/v1") && String(params.id) === originalId) { return Promise.resolve(makeSongUrlResponse(Number(originalId), null)); } if (url.includes("/song/detail") && String(params.ids) === originalId) { return Promise.resolve({ data: { songs: [ { id: Number(originalId), name: "origin", ar: [{ name: "artist" }], al: { name: "album" }, dt: 260000, noCopyrightRcmd: { songId: fallbackId, }, }, ], }, }); } if (url.includes("/song/url/v1") && String(params.id) === fallbackId) { return Promise.resolve(makeSongUrlResponse(Number(fallbackId), fallbackUrl)); } throw new Error(`Unexpected request: ${url} ${JSON.stringify(params)}`); }); const result = await plugin.getMediaSource( { id: originalId, title: "origin", artist: "artist", album: "album", duration: 260, }, "standard", ); expect(result).toEqual({ url: fallbackUrl }); const requestedIds = mockGet.mock.calls .filter((call) => call[0].includes("/song/url/v1")) .map((call) => String((call[1] && call[1].params && call[1].params.id) || "")); expect(requestedIds).toContain(originalId); expect(requestedIds).toContain(fallbackId); }); test("upgrades 126 netease media url to https during fallback", async () => { const originalId = "5268162"; const fallbackId = "2035171101"; const fallbackUrl = "http://m701.music.126.net/test-path/audio.mp3?vuutv=a+b/c"; mockPost.mockResolvedValueOnce({ data: { primary: null, alternates: [], }, }); mockGet.mockImplementation((url, config) => { const params = (config && config.params) || {}; if (url.includes("/song/url/v1") && String(params.id) === originalId) { return Promise.resolve(makeSongUrlResponse(Number(originalId), null)); } if (url.includes("/song/detail") && String(params.ids) === originalId) { return Promise.resolve({ data: { songs: [ { id: Number(originalId), name: "origin", ar: [{ name: "artist" }], al: { name: "album" }, dt: 260000, noCopyrightRcmd: { songId: fallbackId, }, }, ], }, }); } if (url.includes("/song/url/v1") && String(params.id) === fallbackId) { return Promise.resolve(makeSongUrlResponse(Number(fallbackId), fallbackUrl)); } throw new Error(`Unexpected request: ${url} ${JSON.stringify(params)}`); }); const result = await plugin.getMediaSource( { id: originalId, title: "origin", artist: "artist", album: "album", duration: 260, }, "standard", ); expect(result).toEqual({ url: "https://m701.music.126.net/test-path/audio.mp3?vuutv=a+b/c", }); }); test("prefers relay source for fallback song id when relay can resolve it", async () => { const originalId = "5268162"; const fallbackId = "2035171101"; const relayUrl = "http://111.228.62.29:17000/api/unblock/stream/fallback-token"; const officialFallbackUrl = "http://m701.music.126.net/from-official.mp3"; mockPost .mockResolvedValueOnce({ data: { primary: null, alternates: [], }, }) .mockResolvedValueOnce({ data: { primary: { streamUrl: relayUrl, }, alternates: [], }, }); mockGet.mockImplementation((url, config) => { const params = (config && config.params) || {}; const id = String(params.id || params.ids || ""); if (url.includes("/song/url/v1") && id === originalId) { return Promise.resolve(makeSongUrlResponse(Number(originalId), null)); } if (url.includes("/song/detail") && String(params.ids) === originalId) { return Promise.resolve({ data: { songs: [ { id: Number(originalId), name: "origin", ar: [{ name: "artist" }], al: { name: "album" }, dt: 260000, noCopyrightRcmd: { songId: fallbackId, }, }, ], }, }); } if (url.includes("/song/detail") && String(params.ids) === fallbackId) { return Promise.resolve({ data: { songs: [ { id: Number(fallbackId), name: "fallback-name", ar: [{ name: "fallback-artist" }], al: { name: "fallback-album" }, dt: 260000, }, ], }, }); } if (url.includes("/song/url/v1") && id === fallbackId) { return Promise.resolve(makeSongUrlResponse(Number(fallbackId), officialFallbackUrl)); } throw new Error(`Unexpected request: ${url} ${JSON.stringify(params)}`); }); const result = await plugin.getMediaSource( { id: originalId, title: "origin", artist: "artist", album: "album", duration: 260, }, "standard", ); expect(result).toEqual({ url: relayUrl }); const fallbackOfficialCalls = mockGet.mock.calls.filter( (call) => call[0].includes("/song/url/v1") && String((call[1] && call[1].params && call[1].params.id) || "") === fallbackId, ); expect(fallbackOfficialCalls).toHaveLength(0); }); }); describe("netease_17000 recommend sheets", () => { beforeEach(() => { mockGet.mockReset(); mockPost.mockReset(); }); test("builds recommend tag groups from playlist catlist", async () => { mockGet.mockImplementation((url) => { if (url.includes("/playlist/catlist")) { return Promise.resolve({ data: { categories: { 0: "语种", 1: "风格", }, sub: [ { category: 0, name: "华语", hot: true }, { category: 0, name: "欧美", hot: false }, { category: 1, name: "流行", hot: true }, ], }, }); } throw new Error(`Unexpected request: ${url}`); }); const result = await plugin.getRecommendSheetTags(); expect(result).toEqual({ pinned: [ { id: "华语", title: "华语" }, { id: "流行", title: "流行" }, ], data: [ { title: "语种", data: [ { id: "华语", title: "华语" }, { id: "欧美", title: "欧美" }, ], }, { title: "风格", data: [ { id: "流行", title: "流行" }, ], }, ], }); }); test("loads recommend sheets by tag through top playlist endpoint", async () => { mockGet.mockImplementation((url, config) => { const params = (config && config.params) || {}; if (url.includes("/top/playlist")) { return Promise.resolve({ data: { playlists: [ { id: 101, name: "歌单A", creator: { nickname: "作者A" }, description: "descA", coverImgUrl: "coverA", trackCount: 50, playCount: 1000, }, ], total: 21, more: true, }, }); } throw new Error(`Unexpected request: ${url} ${JSON.stringify(params)}`); }); const result = await plugin.getRecommendSheetsByTag({ id: "华语" }, 1); expect(result).toEqual({ isEnd: false, data: [ { id: "101", title: "歌单A", artist: "作者A", description: "descA", coverImg: "coverA", worksNum: 50, playCount: 1000, }, ], }); const topPlaylistCall = mockGet.mock.calls.find((call) => call[0].includes("/top/playlist"), ); expect(topPlaylistCall).toBeTruthy(); expect(topPlaylistCall[1].params).toMatchObject({ cat: "华语", limit: 20, offset: 0, order: "hot", }); }); });