wyapi / web.ts
stnh70's picture
Update web.ts
5a00447 verified
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
import { crypto } from "jsr:@std/crypto";
import dayjs from "npm:dayjs";
// 引入支持ECB模式的AES库
import { encode as hexEncode } from "https://deno.land/std@0.208.0/encoding/hex.ts";
const pageSize = 30;
// 音质参数
const qualityMap = {
"low": "standard",
"standard": "exhigh",
"high": "lossless",
"super": "hires",
};
// CORS 头部
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
"Access-Control-Max-Age": "86400",
};
// MD5 加密函数
async function MD5(data: string): Promise<string> {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const hashBuffer = await crypto.subtle.digest("MD5", dataBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// AES-ECB 加密函数 (用原生Web Crypto API实现ECB模式)
async function AES(data: string): Promise<string> {
// 由于WebCrypto API不直接支持ECB模式,我们需要手动实现
// 将输入分成16字节的块,单独加密每个块,然后拼接结果
const key = "e82ckenh8dichen8";
const encoder = new TextEncoder();
const keyBuffer = encoder.encode(key);
// 为AES-CBC创建密钥(我们会复用它来模拟ECB)
const cryptoKey = await crypto.subtle.importKey(
"raw",
keyBuffer,
{ name: "AES-CBC" },
false,
["encrypt"]
);
// 准备数据
const dataBuffer = encoder.encode(data);
// 处理PKCS7填充
const blockSize = 16;
const paddingSize = blockSize - (dataBuffer.length % blockSize);
const paddedBuffer = new Uint8Array(dataBuffer.length + paddingSize);
paddedBuffer.set(dataBuffer);
// 填充剩余字节
paddedBuffer.fill(paddingSize, dataBuffer.length);
// 将数据分成16字节的块
const blocks = [];
for (let i = 0; i < paddedBuffer.length; i += blockSize) {
blocks.push(paddedBuffer.slice(i, i + blockSize));
}
// 为每个块单独加密(模拟ECB模式)
const encryptedBlocks = await Promise.all(blocks.map(async (block) => {
// 使用零IV(在ECB模式下IV不相关)
const iv = new Uint8Array(16);
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-CBC", iv },
cryptoKey,
block
);
// 只取加密结果的前16字节(块大小)
return new Uint8Array(encrypted).slice(0, blockSize);
}));
// 合并所有加密块
const encryptedArray = new Uint8Array(encryptedBlocks.reduce((acc, block) => {
const combined = new Uint8Array(acc.length + block.length);
combined.set(acc);
combined.set(block, acc.length);
return combined;
}, new Uint8Array(0)));
// 转换为大写十六进制
return Array.from(encryptedArray)
.map(b => b.toString(16).padStart(2, '0'))
.join('')
.toUpperCase();
}
// EAPI 请求函数
async function EAPI(path: string, json: any = {}, music_u?: string): Promise<any> {
try {
const params = [path, JSON.stringify(json)];
const md5Hash = await MD5("nobody" + params.join("use") + "md5forencrypt");
params.push(md5Hash);
const encryptedParams = await AES(params.join("-36cd479b6b5-"));
// 处理cookie格式
let cookieValue = "os=pc;";
if (music_u) {
// 检查是否已经包含MUSIC_U/MUSIC_A格式
const musicMatch = music_u.match(/MUSIC_[UA]=([^;]+)/i);
if (musicMatch) {
cookieValue = `os=pc; appver=9.0.25; MUSIC_U=${musicMatch[1]}`;
} else {
cookieValue = `os=pc; appver=9.0.25; MUSIC_U=${music_u}`;
}
}
const response = await fetch(path.replace("/", "https://interface.music.163.com/e"), {
method: "POST",
body: "params=" + encryptedParams,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Cookie": cookieValue,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
});
if (!response.ok) {
throw new Error(`API错误: ${response.status} ${response.statusText}`);
}
const text = await response.text();
try {
return JSON.parse(text);
} catch (e) {
console.error("JSON解析失败:", text);
throw new Error(`JSON解析失败: ${e.message}`);
}
} catch (error) {
console.error("EAPI请求失败:", error);
throw error;
}
}
// 格式化歌曲信息
function formatMusicItem(item: any): any {
const _ = item.baseInfo || item.song || item;
const name = _.name || _.songname;
const singer = _.ar && _.ar.map((a: any) => a.name).join('&') || "";
const albumName = _.al && _.al.name;
const albumId = (_.al && _.al.id) || "";
const picUrl = (_.al && _.al.picUrl) || "";
const qualities: any = {};
for (const k of ['l', 'h', 'sq', 'hr']) {
if (_[k] || (k === 'l' && _['m'])) {
const quality = {
'm': "low",
'l': "low",
'h': "standard",
'sq': "high",
'hr': "super"
}[k === 'l' && _['m'] ? 'm' : k];
if (quality) {
qualities[quality] = {
size: _[k === 'l' && _['m'] ? 'm' : k].size
};
}
}
}
const content = ((_.fee === 0 || _.fee === 8) && (_.privilege ? (_.privilege.st > -1) : 1)) ? 0 : 1;
return {
id: _.id,
artist: singer,
title: name,
duration: _.dt,
album: albumName,
artwork: picUrl,
qualities,
albumId,
content,
rawLrc: _.lyrics,
};
}
// 格式化专辑信息
function formatAlbumItem(item: any): any {
return {
id: item.id,
title: item.name,
artist: item.artist.name,
artwork: item.picUrl,
description: item.description,
date: dayjs.unix(item.publishTime / 1000).format("YYYY-MM-DD"),
worksNum: item.artist.musicSize,
content: 4
};
}
// 格式化作者信息
function formatArtistItem(item: any): any {
return {
id: item.id,
name: item.name,
avatar: item.img1v1Url,
description: item.briefDesc || item.description,
worksNum: item.musicSize || item.albumSize,
content: 5
};
}
// 格式化歌单信息
function formatSheetItem(item: any): any {
const _ = item.baseInfo || item;
return {
id: _.id || _.resourceId,
title: _.name || _.title,
artist: (_.creator && _.creator.nickname),
artwork: _.coverImgUrl || _.picUrl || _.coverImg,
description: _.description,
worksNum: _.trackCount,
playCount: _.playCount,
date: _.updateTime,
createUserId: _.userId,
createTime: _.createTime,
content: 2
};
}
// 获取排行榜详情
async function getTopListDetail(topListItem: any, music_u?: string): Promise<any> {
const res = await getMusicSheetInfo(topListItem, 1, music_u);
res.topListItem = res.sheetItem;
res.topListItem.content = 3;
return res;
}
// API 路由处理器
async function handleRequest(req: Request): Promise<Response> {
// 处理 CORS 预检请求
if (req.method === "OPTIONS") {
return new Response(null, {
status: 200,
headers: corsHeaders
});
}
const url = new URL(req.url);
const path = url.pathname;
const params = url.searchParams;
try {
let result: any = {};
// 获取请求体数据(如果有)
let body: any = {};
if (req.method === "POST") {
try {
body = await req.json();
} catch {
// 忽略解析错误
}
}
const music_u = params.get("music_u") || body.music_u;
switch (path) {
case "/search":
const query = params.get("query") || body.query;
const page = parseInt(params.get("page") || body.page || "1");
const type = params.get("type") || body.type || "music";
result = await search(query, page, type, music_u);
break;
case "/music/info":
const musicId = params.get("id") || body.id;
result = await getMusicInfo({ id: musicId }, music_u);
break;
case "/music/url":
const songId = params.get("id") || body.id;
const quality = params.get("quality") || body.quality || "standard";
const musicItem = { id: songId, qualities: { [quality]: {} } };
result = await getMediaSource(musicItem, quality, music_u);
break;
case "/lyric":
const lyricId = params.get("id") || body.id;
result = await getLyric({ id: lyricId }, music_u);
break;
case "/playlist/detail":
const playlistId = params.get("id") || body.id;
const playlistPage = parseInt(params.get("page") || body.page || "1");
result = await getMusicSheetInfo({ id: playlistId }, playlistPage, music_u);
break;
case "/album/detail":
const albumId = params.get("id") || body.id;
result = await getAlbumInfo({ id: albumId }, music_u);
break;
case "/artist/works":
const artistId = params.get("id") || body.id;
const artistPage = parseInt(params.get("page") || body.page || "1");
const artistType = params.get("type") || body.type || "music";
result = await getArtistWorks({ id: artistId }, artistPage, artistType, music_u);
break;
case "/toplists":
result = await getTopLists(music_u);
break;
case "/toplistdetail":
result = await getTopListDetail(
{ id: params.get("id") || body.id },
music_u
);
break;
case "/recommend/tags":
result = await getRecommendSheetTags(music_u);
break;
case "/recommend/sheets":
const tag = params.get("tag") || body.tag;
const sheetPage = parseInt(params.get("page") || body.page || "1");
result = await getRecommendSheetsByTag(tag, sheetPage, music_u);
break;
case "/import/sheet":
const urlLike = params.get("url") || body.url;
result = await importMusicSheet(urlLike, music_u);
break;
case "/import/music":
const musicUrl = params.get("url") || body.url;
result = await importMusicItem(musicUrl, music_u);
break;
case "/comments":
const commentId = params.get("id") || body.id;
const commentPage = parseInt(params.get("page") || body.page || "1");
result = await getMusicComments({ id: commentId }, commentPage, music_u);
break;
// case "/":
// case "/health":
// result = {
// status: "ok",
// message: "网易云音乐 API 服务正在运行",
// version: "2025.01.22",
// endpoints: [
// "/search - 搜索音乐",
// "/music/info - 获取歌曲详情",
// "/music/url - 获取播放链接",
// "/lyric - 获取歌词",
// "/playlist/detail - 获取歌单详情",
// "/album/detail - 获取专辑详情",
// "/artist/works - 获取歌手作品",
// "/toplists - 获取排行榜",
// "/recommend/tags - 获取推荐标签",
// "/recommend/sheets - 获取推荐歌单",
// "/import/sheet - 导入歌单",
// "/import/music - 导入单曲",
// "/comments - 获取评论"
// ]
// };
// break;
case "/":
case "/health":
result = {
status: "ok",
message: "云音乐 API 服务正在运行",
version: "2025.07.22",
description: "基于 Deno 的云音乐 API 服务,支持搜索、播放、歌词等功能",
baseUrl: `${url.protocol}//${url.host}`,
authentication: {
description: "部分接口需要登录态,可通过 music_u 参数传递",
parameter: "music_u",
usage: "在查询参数或请求体中添加 music_u 字段"
},
endpoints: [
{
path: "/search",
method: "GET/POST",
description: "搜索音乐、专辑、歌手、歌单",
parameters: {
query: "搜索关键词(必需)",
page: "页码,默认为1",
type: "搜索类型:music(歌曲)、album(专辑)、artist(歌手)、sheet(歌单)、lyric(歌词),默认music",
music_u: "登录凭证(可选)"
},
example: "/search?query=周杰伦&page=1&type=music"
},
{
path: "/music/info",
method: "GET/POST",
description: "获取歌曲详细信息",
parameters: {
id: "歌曲ID(必需)",
music_u: "登录凭证(可选)"
},
example: "/music/info?id=123456"
},
{
path: "/music/url",
method: "GET/POST",
description: "获取歌曲播放链接",
parameters: {
id: "歌曲ID(必需)",
quality: "音质:low(标准)、standard(较高)、high(无损)、super(Hi-Res),默认standard",
music_u: "登录凭证(推荐,提高成功率)"
},
example: "/music/url?id=123456&quality=high"
},
{
path: "/lyric",
method: "GET/POST",
description: "获取歌曲歌词",
parameters: {
id: "歌曲ID(必需)",
music_u: "登录凭证(可选)"
},
example: "/lyric?id=123456"
},
{
path: "/playlist/detail",
method: "GET/POST",
description: "获取歌单详情和歌曲列表",
parameters: {
id: "歌单ID(必需)",
page: "页码,默认为1",
music_u: "登录凭证(可选)"
},
example: "/playlist/detail?id=123456"
},
{
path: "/album/detail",
method: "GET/POST",
description: "获取专辑详情和歌曲列表",
parameters: {
id: "专辑ID(必需)",
music_u: "登录凭证(可选)"
},
example: "/album/detail?id=123456"
},
{
path: "/artist/works",
method: "GET/POST",
description: "获取歌手作品(热门歌曲或专辑)",
parameters: {
id: "歌手ID(必需)",
page: "页码,默认为1",
type: "作品类型:music(歌曲)、album(专辑),默认music",
music_u: "登录凭证(可选)"
},
example: "/artist/works?id=123456&type=music"
},
{
path: "/toplists",
method: "GET/POST",
description: "获取所有排行榜列表",
parameters: {
music_u: "登录凭证(可选)"
},
example: "/toplists"
},
{
path: "/recommend/tags",
method: "GET/POST",
description: "获取歌单推荐标签",
parameters: {
music_u: "登录凭证(可选)"
},
example: "/recommend/tags"
},
{
path: "/recommend/sheets",
method: "GET/POST",
description: "根据标签获取推荐歌单",
parameters: {
tag: "标签名称(可选,不传则返回推荐歌单)",
page: "页码,默认为1",
music_u: "登录凭证(可选)"
},
example: "/recommend/sheets?tag=流行&page=1"
},
{
path: "/import/sheet",
method: "GET/POST",
description: "通过URL或ID导入歌单",
parameters: {
url: "歌单URL或歌单ID(必需)",
music_u: "登录凭证(可选)"
},
example: "/import/sheet?url=123456 或 /import/sheet?url=https://music.163.com/playlist?id=123456"
},
{
path: "/import/music",
method: "GET/POST",
description: "通过URL或ID导入单曲",
parameters: {
url: "歌曲URL或歌曲ID(必需)",
music_u: "登录凭证(可选)"
},
example: "/import/music?url=123456 或 /import/music?url=https://music.163.com/song?id=123456"
},
{
path: "/comments",
method: "GET/POST",
description: "获取歌曲评论",
parameters: {
id: "歌曲ID(必需)",
page: "页码,默认为1",
music_u: "登录凭证(可选)"
},
example: "/comments?id=123456&page=1"
}
],
responseFormat: {
success: {
description: "成功响应格式",
example: {
isEnd: "boolean - 是否为最后一页(分页接口)",
data: "array - 数据列表",
total: "number - 总数(部分接口)"
}
},
error: {
description: "错误响应格式",
example: {
error: "string - 错误描述",
message: "string - 详细错误信息"
}
}
},
notes: [
"所有接口均支持 GET 和 POST 请求方式",
"GET 请求参数通过查询字符串传递",
"POST 请求参数通过 JSON 格式的请求体传递",
"推荐使用 music_u 参数以获得更好的接口响应",
"部分高音质音频需要会员权限",
"请遵守相关法律法规,合理使用 API"
]
};
break;
default:
return new Response(JSON.stringify({
error: "API 路径不存在",
path: path
}), {
status: 404,
headers: {
"Content-Type": "application/json",
...corsHeaders
}
});
}
return new Response(JSON.stringify(result), {
status: 200,
headers: {
"Content-Type": "application/json",
...corsHeaders
}
});
} catch (error) {
// console.error("API Error:", error);
// return new Response(JSON.stringify({
// error: "服务器内部错误",
// message: error.message
// }), {
// status: 500,
// headers: {
// "Content-Type": "application/json",
// ...corsHeaders
// }
// });
console.error("API错误:", error);
console.error("错误堆栈:", error.stack);
// 尝试获取请求信息以便调试
try {
const url = new URL(req.url);
console.error("路径:", url.pathname);
console.error("查询参数:", Object.fromEntries(url.searchParams.entries()));
if (req.method === "POST") {
try {
const cloned = req.clone();
const body = await cloned.text();
console.error("请求体:", body);
} catch (e) {
console.error("无法读取请求体:", e);
}
}
} catch (e) {
console.error("无法获取请求信息:", e);
}
return new Response(JSON.stringify({
error: "服务器内部错误",
message: error.message,
stack: Deno.env.get("DEBUG") === "true" ? error.stack : undefined
}), {
status: 500,
headers: {
"Content-Type": "application/json",
...corsHeaders
}
});
}
}
// 实现具体的 API 功能函数
async function search(query: string, page: number, type: string, music_u?: string): Promise<any> {
const stype: any = {
music: { t: "song", m: formatMusicItem },
album: { t: "album", v: "/v1", m: formatAlbumItem },
artist: { t: "artist", v: "/v1", m: formatArtistItem },
sheet: { t: "playlist", m: formatSheetItem },
lyric: { t: "resource/lyric", m: formatMusicItem }
}[type];
const path = "/api" + (stype.v || "") + "/search/" + stype.t + (stype.t.includes("/") ? "" : "/get");
const data = {
"filterCode": "-1",
"offset": ((page - 1) * pageSize).toString(),
"limit": pageSize.toString(),
"channel": "typing",
"keyword": query,
"scene": "normal",
"s": query,
};
const res = await EAPI(path, data, music_u);
const result = res.data || res.result;
const list = result.resources || result.albums || result.artists || result.songs || [];
const total1 = page * pageSize;
const total2 = result.songCount || result.playlistCount || result.albumCount || result.totalCount || (total1 - pageSize + list.length);
return {
isEnd: total2 <= total1,
data: list.map(stype.m)
};
}
async function getMusicInfo(musicItem: any, music_u?: string): Promise<any> {
const res = await EAPI("/api/v3/song/detail", {
c: `[{"id":"${musicItem.id}"}]`
}, music_u);
const song = res.songs[0] || res.privileges[0];
song.privilege = res.privileges[0];
return formatMusicItem(song);
}
async function getMediaSource(musicItem: any, quality: string, music_u?: string): Promise<any> {
if (!musicItem.qualities[quality]) {
return false;
}
const res = await EAPI("/api/song/enhance/player/url/v1", {
ids: `["${musicItem.id}"]`,
encodeType: "flac",
immerseType: "c51",
trialMode: "23",
level: qualityMap[quality as keyof typeof qualityMap]
}, music_u);
if (res.data && res.data[0] && res.data[0].url) {
return {
url: String(res.data[0].url).split("?")[0],
size: res.data[0].size,
quality,
};
}
return false;
}
async function getLyric(musicItem: any, music_u?: string): Promise<any> {
const res = await EAPI("/api/song/lyric", {
id: musicItem.id,
lv: -1,
kv: -1,
tv: -1
}, music_u);
return {
rawLrc: res.lrc?.lyric || "",
};
}
async function getMusicSheetInfo(sheet: any, page: number = 1, music_u?: string): Promise<any> {
const res = await EAPI("/api/v6/playlist/detail", {
n: 99999,
id: sheet.id
}, music_u);
const playlist = res.playlist;
const list = playlist.tracks || [];
return {
isEnd: 99999 >= playlist.trackCount,
sheetItem: formatSheetItem(playlist),
musicList: list.map(formatMusicItem)
};
}
async function getAlbumInfo(albumItem: any, music_u?: string): Promise<any> {
const res = await EAPI("/api/v1/album/" + albumItem.id, {}, music_u);
return {
isEnd: true,
albumItem: formatAlbumItem(res.album),
musicList: (res.songs || []).map(formatMusicItem),
};
}
async function getArtistWorks(artistItem: any, page: number, type: string, music_u?: string): Promise<any> {
const typeConfig: any = {
"music": {
"path1": "/api/v1/artist/",
"path2": "hotSongs",
"mapJs": formatMusicItem
},
"album": {
"path1": "/api/artist/albums/",
"path2": "hotAlbums",
"mapJs": formatAlbumItem
},
}[type];
const res = await EAPI(typeConfig.path1 + artistItem.id, {}, music_u);
return {
isEnd: true,
artistItem: formatArtistItem(res.artist),
data: res[typeConfig.path2].map(typeConfig.mapJs),
};
}
async function getTopLists(music_u?: string): Promise<any> {
const group1: any[] = [];
const group2 = await EAPI("/api/toplist/detail/v2", {}, music_u);
group2.data.map((item: any) => {
if (item.list && item.list.length) {
const group3: any[] = [];
item.list.map((listItem: any) => {
if (listItem.id !== 0) {
group3.push({
title: listItem.name,
coverImg: listItem.coverUrl,
content: 3,
id: listItem.id,
description: listItem.updateFrequency
});
}
});
group1.push({
title: item.name,
data: group3
});
}
});
return group1;
}
async function getRecommendSheetTags(music_u?: string): Promise<any> {
const pinned = [
{ title: "推荐", id: "_SPECIAL_CLOUD_VILLAGE_PLAYLIST" },
{ title: "官方", id: "官方" },
{ title: "雷达", id: "_RADAR" },
{ title: "原创", id: "_SPECIAL_ORIGIN_SONG_LOCATION" },
{ title: "心情", id: "_FEELING_PLAYLIST_LOCATION" },
{ title: "场景", id: "_SCENE_PLAYLIST_LOCATION" },
{ title: "专属", id: "_COMBINATION" },
{ title: "全部", id: "全部歌单" },
{ title: "新热", id: "_NEW_SONG_AND_ALBUM" },
{ title: "影视", id: "_FIRM_PLAYLIST" },
{ title: "奖项", id: "_AWARDS_PLAYLIST" },
];
const data = ["语种", "风格", "场景", "情感", "主题"].map(name => ({
title: name,
data: []
}));
const res = await EAPI("/api/playlist/catalogue/v1", {}, music_u);
res.sub.map((item: any) => {
data[item.category].data.push({
title: item.name,
id: item.name
});
});
return { pinned, data };
}
async function getRecommendSheetsByTag(tag: string | null, page: number, music_u?: string): Promise<any> {
let res: any;
const tagId = tag;
if (!tagId || tagId === "" || tagId === "true") {
res = await EAPI("/api/personalized/playlist", { limit: 30 }, music_u);
} else if (/^_[A-Z]+/.test(tagId)) {
res = await EAPI("/api/link/page/rcmd/resource/show", {
"pageCode": "HOME_RECOMMEND_PAGE",
"isFirstScreen": "false",
"cursor": "6",
"refresh": "true",
"blockCodeOrderList": `["PAGE_RECOMMEND${tagId}"]`
}, music_u);
res = res.data.blocks[0].dslData.blockResource;
} else {
res = await EAPI("/api/playlist/list", {
cat: tagId || "全部",
order: "hot",
limit: pageSize,
offset: (page - 1) * pageSize,
total: true,
csrf_token: "",
}, music_u);
}
const list = res.result || res.playlists || res.resources || [];
const total1 = page * pageSize;
const total2 = res.total || (total1 - pageSize + list.length);
return {
isEnd: (res.more !== true) || (total2 <= total1),
data: list.map(formatSheetItem)
};
}
async function importMusicSheet(urlLike: string, music_u?: string): Promise<any> {
let id = (urlLike.match(/^(\d+)$/) || [])[1];
if (!id && !urlLike.match(/music\.163\.com/i)) {
return false;
}
if (!id) {
id = (urlLike.match(/playlist(\/|.*?[\?\&]id=)(\d+)/i) || [])[2];
}
if (!id) {
return false;
}
const result = await getMusicSheetInfo({ id }, 1, music_u);
return result.musicList;
}
async function importMusicItem(urlLike: string, music_u?: string): Promise<any> {
let id = (urlLike.match(/^(\d+)$/) || [])[1];
if (!id && !urlLike.match(/music\.163\.com/i)) {
return false;
}
if (!id) {
id = (urlLike.match(/song(.*?[\?\&]id=|\/)(\d+)/i) || [])[2];
}
if (!id) {
return false;
}
return await getMusicInfo({ id }, music_u);
}
async function getMusicComments(musicItem: any, page: number = 1, music_u?: string): Promise<any> {
const res = await EAPI("/api/v2/resource/comments", {
"threadId": "R_SO_4_" + musicItem.id,
"cursor": "20",
"sortType": "1",
"pageNo": page,
"pageSize": pageSize.toString(),
"parentCommentld": "0",
"showlnner": false
}, music_u);
const formatComment = (comment: any): any => ({
id: comment.commentId,
nickName: comment.user && comment.user.nickname,
avatar: comment.user && comment.user.avatarUrl,
comment: comment.content,
like: comment.likedCount,
createAt: comment.time,
location: comment.ipLocation && comment.ipLocation.location,
replies: (comment.beReplied || []).map(formatComment),
content: 6
});
return {
isEnd: res.data.hasMore !== true,
data: res.data.comments.map(formatComment)
};
}
let hbCount = 0;
setInterval(()=>{
hbCount++;
if (hbCount % 6 === 0) { // every 60s if interval is 10s
console.log('[diagnostic] heartbeat 60s elapsed, process alive');
}
}, 10_000);
// 启动服务器
const port = parseInt(Deno.env.get("PORT") || "8080");
console.log(`🎵 Deno 网易云音乐 API 服务启动成功!`);
console.log(`🚀 服务运行在: http://localhost:${port}`);
console.log(`📖 API 文档: http://localhost:${port}/`);
serve(handleRequest, { port });