feat: 后端改为deno
This commit is contained in:
230
backend-deno/modules/apple_music.ts
Normal file
230
backend-deno/modules/apple_music.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* Apple Music 控制和状态获取模块 (Deno 版本)
|
||||
* 负责与 Apple Music 应用交互的所有功能
|
||||
*/
|
||||
|
||||
export interface MusicStatus {
|
||||
status: "playing" | "paused" | "stopped" | "notrunning" | "error";
|
||||
track_name?: string;
|
||||
artist?: string;
|
||||
position?: number;
|
||||
duration?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ControlResult {
|
||||
status: "success" | "error";
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行 AppleScript 并返回结果
|
||||
*/
|
||||
async function runAppleScript(script: string): Promise<string> {
|
||||
const process = new Deno.Command("osascript", {
|
||||
args: ["-e", script],
|
||||
stdout: "piped",
|
||||
stderr: "piped",
|
||||
});
|
||||
|
||||
try {
|
||||
const { code, stdout, stderr } = await process.output();
|
||||
const output = new TextDecoder().decode(stdout).trim();
|
||||
const error = new TextDecoder().decode(stderr).trim();
|
||||
|
||||
if (code !== 0) {
|
||||
console.warn(`AppleScript 执行失败: ${error}`);
|
||||
return "";
|
||||
}
|
||||
|
||||
return output;
|
||||
} catch (error) {
|
||||
console.error(`执行 AppleScript 时发生错误: ${error}`);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Apple Music 播放状态 - 适配 macOS Tahoe
|
||||
*/
|
||||
export async function getMusicStatus(): Promise<MusicStatus> {
|
||||
// 先获取播放状态
|
||||
const statusScript = `
|
||||
tell application "Music"
|
||||
if it is running then
|
||||
set playerState to (get player state) as string
|
||||
return playerState
|
||||
else
|
||||
return "notrunning"
|
||||
end if
|
||||
end tell
|
||||
`;
|
||||
|
||||
try {
|
||||
const playerState = await runAppleScript(statusScript);
|
||||
|
||||
if (!playerState) {
|
||||
console.error("AppleScript 执行失败,返回了空值");
|
||||
return { status: "error", error: "AppleScript execution failed" };
|
||||
}
|
||||
|
||||
if (playerState === "notrunning") {
|
||||
return { status: "notrunning" };
|
||||
}
|
||||
|
||||
if (playerState === "stopped") {
|
||||
return { status: "stopped" };
|
||||
}
|
||||
|
||||
// 尝试获取歌曲信息
|
||||
try {
|
||||
const infoScript = `
|
||||
tell application "Music"
|
||||
if it is running then
|
||||
try
|
||||
set trackName to ""
|
||||
set artistName to ""
|
||||
set pos to 0
|
||||
set dur to 0
|
||||
|
||||
if exists current track then
|
||||
set t to current track
|
||||
try
|
||||
set trackName to (name of t) as text
|
||||
end try
|
||||
try
|
||||
set artistName to (artist of t) as text
|
||||
end try
|
||||
try
|
||||
set dur to (duration of t) as real
|
||||
end try
|
||||
end if
|
||||
|
||||
try
|
||||
set pos to (player position) as real
|
||||
end try
|
||||
|
||||
return "success|||" & trackName & "|||" & artistName & "|||" & (pos as text) & "|||" & (dur as text)
|
||||
on error errMsg
|
||||
return "error|||" & errMsg
|
||||
end try
|
||||
else
|
||||
return "notrunning"
|
||||
end if
|
||||
end tell
|
||||
`;
|
||||
|
||||
const infoResult = await runAppleScript(infoScript);
|
||||
|
||||
if (infoResult && infoResult.startsWith("success|||")) {
|
||||
const parts = infoResult.split("|||");
|
||||
if (parts.length >= 5) {
|
||||
return {
|
||||
status: playerState as "playing" | "paused",
|
||||
track_name: parts[1],
|
||||
artist: parts[2],
|
||||
position: parseFloat(parts[3]),
|
||||
duration: parseFloat(parts[4]),
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`获取歌曲信息失败: ${error}`);
|
||||
}
|
||||
|
||||
// 如果无法获取歌曲信息,至少返回播放状态
|
||||
return {
|
||||
status: playerState as "playing" | "paused",
|
||||
track_name: "无法获取歌曲信息",
|
||||
artist: "无法获取艺术家信息",
|
||||
position: 0,
|
||||
duration: 0,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`获取音乐状态时发生异常: ${error}`);
|
||||
return { status: "error", error: String(error) };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制 Apple Music 播放
|
||||
*/
|
||||
export async function controlMusic(
|
||||
action: string,
|
||||
position?: number
|
||||
): Promise<ControlResult> {
|
||||
let script = "";
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case "playpause":
|
||||
script = `
|
||||
tell application "Music"
|
||||
if it is running then
|
||||
playpause
|
||||
return "ok"
|
||||
else
|
||||
return "notrunning"
|
||||
end if
|
||||
end tell
|
||||
`;
|
||||
break;
|
||||
|
||||
case "previous":
|
||||
script = `
|
||||
tell application "Music"
|
||||
if it is running then
|
||||
previous track
|
||||
return "ok"
|
||||
else
|
||||
return "notrunning"
|
||||
end if
|
||||
end tell
|
||||
`;
|
||||
break;
|
||||
|
||||
case "next":
|
||||
script = `
|
||||
tell application "Music"
|
||||
if it is running then
|
||||
next track
|
||||
return "ok"
|
||||
else
|
||||
return "notrunning"
|
||||
end if
|
||||
end tell
|
||||
`;
|
||||
break;
|
||||
|
||||
case "seek":
|
||||
if (position !== undefined) {
|
||||
script = `
|
||||
tell application "Music"
|
||||
if it is running then
|
||||
set player position to ${position}
|
||||
return "ok"
|
||||
else
|
||||
return "notrunning"
|
||||
end if
|
||||
end tell
|
||||
`;
|
||||
} else {
|
||||
return { status: "error", message: "位置参数不能为空" };
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return { status: "error", message: "未知的命令" };
|
||||
}
|
||||
|
||||
const result = await runAppleScript(script);
|
||||
|
||||
if (result === "ok") {
|
||||
return { status: "success" };
|
||||
} else {
|
||||
return { status: "error", message: result };
|
||||
}
|
||||
} catch (error) {
|
||||
return { status: "error", message: String(error) };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user