mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-03 14:20:50 +08:00
feat: mac 添加权限
This commit is contained in:
@@ -16,5 +16,7 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.files.downloads.read-write</key>
|
<key>com.apple.security.files.downloads.read-write</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.device.microphone</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ mac:
|
|||||||
- NSCameraUsageDescription: Application requests access to the device's camera.
|
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||||
|
- NSMicrophoneUsageDescription: Application requests access to the microphone for audio visualization.
|
||||||
notarize: false
|
notarize: false
|
||||||
dmg:
|
dmg:
|
||||||
artifactName: ${name}-${version}.${ext}
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
|||||||
@@ -52,6 +52,31 @@ export default {
|
|||||||
copyFailed: 'Copy failed',
|
copyFailed: 'Copy failed',
|
||||||
backgroundDownload: 'Background Download'
|
backgroundDownload: 'Background Download'
|
||||||
},
|
},
|
||||||
|
disclaimer: {
|
||||||
|
title: 'Terms of Use',
|
||||||
|
warning:
|
||||||
|
'This application is a development test version. Functions are not yet perfect, and there may be many problems and bugs. It is for learning and exchange only.',
|
||||||
|
item1:
|
||||||
|
'This application is for personal learning, research and technical exchange only. Please do not use it for any commercial purposes.',
|
||||||
|
item2:
|
||||||
|
'Please delete it within 24 hours after downloading. If you need to use it for a long time, please support the genuine music service.',
|
||||||
|
item3:
|
||||||
|
'By using this application, you understand and assume the relevant risks. The developer is not responsible for any loss.',
|
||||||
|
agree: 'I have read and agree',
|
||||||
|
disagree: 'Disagree and Exit'
|
||||||
|
},
|
||||||
|
donate: {
|
||||||
|
title: 'Support Developer',
|
||||||
|
subtitle: 'Your support is my motivation',
|
||||||
|
tip: 'Donation is completely voluntary. All functions can be used normally without donation. Thank you for your understanding and support!',
|
||||||
|
wechat: 'WeChat',
|
||||||
|
alipay: 'Alipay',
|
||||||
|
wechatQR: 'WeChat QR Code',
|
||||||
|
alipayQR: 'Alipay QR Code',
|
||||||
|
scanTip: 'Please use your phone to scan the QR code above to donate',
|
||||||
|
enterApp: 'Enter App',
|
||||||
|
noForce: 'No forced donation, click to enter'
|
||||||
|
},
|
||||||
coffee: {
|
coffee: {
|
||||||
title: 'Buy me a coffee',
|
title: 'Buy me a coffee',
|
||||||
alipay: 'Alipay',
|
alipay: 'Alipay',
|
||||||
|
|||||||
@@ -52,6 +52,31 @@ export default {
|
|||||||
copyFailed: 'コピーに失敗しました',
|
copyFailed: 'コピーに失敗しました',
|
||||||
backgroundDownload: 'バックグラウンドダウンロード'
|
backgroundDownload: 'バックグラウンドダウンロード'
|
||||||
},
|
},
|
||||||
|
disclaimer: {
|
||||||
|
title: '使用上の注意',
|
||||||
|
warning:
|
||||||
|
'このアプリは開発テスト版であり、機能が不完全で、多くの問題やバグが存在する可能性があります。学習と交流のみを目的としています。',
|
||||||
|
item1:
|
||||||
|
'このアプリは個人の学習、研究、技術交流のみを目的としています。商業目的で使用しないでください。',
|
||||||
|
item2:
|
||||||
|
'ダウンロード後24時間以内に削除してください。長期使用を希望される場合は、正規の音楽サービスをサポートしてください。',
|
||||||
|
item3:
|
||||||
|
'このアプリを使用することで、関連するリスクを理解し、負担するものとします。開発者は一切の損失に対して責任を負いません。',
|
||||||
|
agree: '以上の内容を読み、同意します',
|
||||||
|
disagree: '同意せずに終了'
|
||||||
|
},
|
||||||
|
donate: {
|
||||||
|
title: '開発者を支援',
|
||||||
|
subtitle: '皆様のサポートが私の原動力です',
|
||||||
|
tip: '寄付は完全に任意です。寄付しなくてもすべての機能を通常通り使用できます。ご理解とご支援に感謝します!',
|
||||||
|
wechat: 'WeChat',
|
||||||
|
alipay: 'Alipay',
|
||||||
|
wechatQR: 'WeChat 受取コード',
|
||||||
|
alipayQR: 'Alipay 受取コード',
|
||||||
|
scanTip: 'スマートフォンのアプリで上記のQRコードをスキャンして寄付してください',
|
||||||
|
enterApp: 'アプリに入る',
|
||||||
|
noForce: '寄付は強制ではありません。クリックして入れます'
|
||||||
|
},
|
||||||
coffee: {
|
coffee: {
|
||||||
title: 'コーヒーをおごる',
|
title: 'コーヒーをおごる',
|
||||||
alipay: 'Alipay',
|
alipay: 'Alipay',
|
||||||
|
|||||||
@@ -51,6 +51,31 @@ export default {
|
|||||||
copyFailed: '복사 실패',
|
copyFailed: '복사 실패',
|
||||||
backgroundDownload: '백그라운드 다운로드'
|
backgroundDownload: '백그라운드 다운로드'
|
||||||
},
|
},
|
||||||
|
disclaimer: {
|
||||||
|
title: '이용 안내',
|
||||||
|
warning:
|
||||||
|
'본 앱은 개발 테스트 버전으로 기능이 아직 미흡하며, 다수의 문제와 버그가 존재할 수 있습니다. 학습 및 교류 목적으로만 사용하십시오.',
|
||||||
|
item1:
|
||||||
|
'본 앱은 개인의 학습, 연구 및 기술 교류 목적으로만 사용되며, 상업적 용도로 사용하지 마십시오.',
|
||||||
|
item2:
|
||||||
|
'다운로드 후 24시간 이내에 삭제해 주십시오. 장기 사용을 원하시면 정품 음악 서비스를 이용해 주십시오.',
|
||||||
|
item3:
|
||||||
|
'본 앱을 사용함으로써 관련 위험을 이해하고 감수하는 것으로 간주합니다. 개발자는 어떠한 손실에 대해서도 책임을 지지 않습니다.',
|
||||||
|
agree: '숙지하였으며 이에 동의합니다',
|
||||||
|
disagree: '동의하지 않음 및 정지'
|
||||||
|
},
|
||||||
|
donate: {
|
||||||
|
title: '개발자 지원',
|
||||||
|
subtitle: '여러분의 지원이 저의 원동력입니다',
|
||||||
|
tip: '후원은 완전히 자율적입니다. 후원하지 않더라도 모든 기능을 정상적으로 사용할 수 있습니다. 이해와 지원에 감사드립니다!',
|
||||||
|
wechat: 'WeChat',
|
||||||
|
alipay: 'Alipay',
|
||||||
|
wechatQR: 'WeChat 결제 코드',
|
||||||
|
alipayQR: 'Alipay 결제 코드',
|
||||||
|
scanTip: '휴대전화로 위 QR 코드를 스캔하여 후원해 주세요',
|
||||||
|
enterApp: '앱 시작하기',
|
||||||
|
noForce: '후원은 강제가 아닙니다. 클릭하여 시작할 수 있습니다'
|
||||||
|
},
|
||||||
coffee: {
|
coffee: {
|
||||||
title: '커피 한 잔 사주세요',
|
title: '커피 한 잔 사주세요',
|
||||||
alipay: '알리페이',
|
alipay: '알리페이',
|
||||||
|
|||||||
@@ -50,6 +50,27 @@ export default {
|
|||||||
copyFailed: '复制失败',
|
copyFailed: '复制失败',
|
||||||
backgroundDownload: '后台下载'
|
backgroundDownload: '后台下载'
|
||||||
},
|
},
|
||||||
|
disclaimer: {
|
||||||
|
title: '使用须知',
|
||||||
|
warning: '本应用为开发测试版本,功能尚不完善,可能存在较多问题和 Bug,仅供学习交流使用。',
|
||||||
|
item1: '本应用仅供个人学习、研究和技术交流使用,请勿用于任何商业用途。',
|
||||||
|
item2: '请在下载后 24 小时内删除,如需长期使用请支持正版音乐服务。',
|
||||||
|
item3: '使用本应用即表示您理解并承担相关风险,开发者不对任何损失负责。',
|
||||||
|
agree: '我已阅读并同意',
|
||||||
|
disagree: '不同意并退出'
|
||||||
|
},
|
||||||
|
donate: {
|
||||||
|
title: '支持开发者',
|
||||||
|
subtitle: '您的支持是我前进的动力',
|
||||||
|
tip: '捐赠完全自愿,不捐赠也可以正常使用所有功能,感谢您的理解与支持!',
|
||||||
|
wechat: '微信',
|
||||||
|
alipay: '支付宝',
|
||||||
|
wechatQR: '微信收款码',
|
||||||
|
alipayQR: '支付宝收款码',
|
||||||
|
scanTip: '请使用手机扫描上方二维码进行捐赠',
|
||||||
|
enterApp: '进入应用',
|
||||||
|
noForce: '不强制捐赠,点击即可进入'
|
||||||
|
},
|
||||||
coffee: {
|
coffee: {
|
||||||
title: '请我喝咖啡',
|
title: '请我喝咖啡',
|
||||||
alipay: '支付宝',
|
alipay: '支付宝',
|
||||||
|
|||||||
@@ -50,6 +50,27 @@ export default {
|
|||||||
copyFailed: '複製失敗',
|
copyFailed: '複製失敗',
|
||||||
backgroundDownload: '背景下載'
|
backgroundDownload: '背景下載'
|
||||||
},
|
},
|
||||||
|
disclaimer: {
|
||||||
|
title: '使用說明',
|
||||||
|
warning: '本程式為開發測試版本,功能尚未完善,可能存在諸多問題及臭蟲,僅供學習交流使用。',
|
||||||
|
item1: '本程式僅供個人學習、研究及技術交流之目的,不得用於任何商業用途。',
|
||||||
|
item2: '請在下載後 24 小時內刪除,若對您有所幫助,請支持正版音樂。',
|
||||||
|
item3: '使用本程式即代表您已了解並同意相關風險,開發者對任何損失概不負責。',
|
||||||
|
agree: '我已了解並同意',
|
||||||
|
disagree: '不同意並退出'
|
||||||
|
},
|
||||||
|
donate: {
|
||||||
|
title: '支援開發者',
|
||||||
|
subtitle: '您的支援是我持續更新的動力',
|
||||||
|
tip: '捐贈完全採自願原則。即使不捐贈,您依然可以正常使用所有功能。感謝您的理解與支援!',
|
||||||
|
wechat: '微信支付',
|
||||||
|
alipay: '支付寶',
|
||||||
|
wechatQR: '微信收款碼',
|
||||||
|
alipayQR: '支付寶收款碼',
|
||||||
|
scanTip: '請使用手機 App 掃描 QR Code 進行捐贈',
|
||||||
|
enterApp: '進入程式',
|
||||||
|
noForce: '捐贈並非強制,您可以點擊按鈕直接進入'
|
||||||
|
},
|
||||||
coffee: {
|
coffee: {
|
||||||
title: '請我喝杯咖啡',
|
title: '請我喝杯咖啡',
|
||||||
alipay: '支付寶',
|
alipay: '支付寶',
|
||||||
|
|||||||
@@ -143,6 +143,12 @@ export function initializeWindowManager() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 强制退出应用(用于免责声明拒绝等场景)
|
||||||
|
ipcMain.on('quit-app', () => {
|
||||||
|
setAppQuitting(true);
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.on('mini-tray', (event) => {
|
ipcMain.on('mini-tray', (event) => {
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
const win = BrowserWindow.fromWebContents(event.sender);
|
||||||
if (win) {
|
if (win) {
|
||||||
|
|||||||
@@ -23,14 +23,57 @@ ipcMain.handle('unblock-music', async (_event, id, songData, enabledSources) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function startMusicApi(): Promise<void> {
|
/**
|
||||||
console.log('MUSIC API STARTED');
|
* 检查端口是否可用
|
||||||
|
*/
|
||||||
const port = (store.get('set') as any).musicApiPort || 30488;
|
function checkPortAvailable(port: number): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
await server.serveNcmApi({
|
const net = require('net');
|
||||||
port
|
const tester = net
|
||||||
|
.createServer()
|
||||||
|
.once('error', () => {
|
||||||
|
resolve(false);
|
||||||
|
})
|
||||||
|
.once('listening', () => {
|
||||||
|
tester.close(() => resolve(true));
|
||||||
|
})
|
||||||
|
.listen(port);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function startMusicApi(): Promise<void> {
|
||||||
|
console.log('MUSIC API STARTING...');
|
||||||
|
|
||||||
|
const settings = store.get('set') as any;
|
||||||
|
let port = settings?.musicApiPort || 30488;
|
||||||
|
const maxRetries = 10;
|
||||||
|
|
||||||
|
// 检查端口是否可用,如果不可用则尝试下一个端口
|
||||||
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
|
const isAvailable = await checkPortAvailable(port);
|
||||||
|
if (isAvailable) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
console.log(`端口 ${port} 被占用,尝试切换到端口 ${port + 1}`);
|
||||||
|
port++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果端口发生变化,保存新端口到配置
|
||||||
|
const originalPort = settings?.musicApiPort || 30488;
|
||||||
|
if (port !== originalPort) {
|
||||||
|
console.log(`端口从 ${originalPort} 切换到 ${port}`);
|
||||||
|
store.set('set', { ...settings, musicApiPort: port });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await server.serveNcmApi({
|
||||||
|
port
|
||||||
|
});
|
||||||
|
console.log(`MUSIC API STARTED on port ${port}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`MUSIC API 启动失败:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export { startMusicApi };
|
export { startMusicApi };
|
||||||
|
|||||||
7
src/preload/index.d.ts
vendored
7
src/preload/index.d.ts
vendored
@@ -4,6 +4,7 @@ interface API {
|
|||||||
minimize: () => void;
|
minimize: () => void;
|
||||||
maximize: () => void;
|
maximize: () => void;
|
||||||
close: () => void;
|
close: () => void;
|
||||||
|
quitApp: () => void;
|
||||||
dragStart: (data: any) => void;
|
dragStart: (data: any) => void;
|
||||||
miniTray: () => void;
|
miniTray: () => void;
|
||||||
miniWindow: () => void;
|
miniWindow: () => void;
|
||||||
@@ -25,11 +26,7 @@ interface API {
|
|||||||
importLxMusicScript: () => Promise<{ name: string; content: string } | null>;
|
importLxMusicScript: () => Promise<{ name: string; content: string } | null>;
|
||||||
invoke: (channel: string, ...args: any[]) => Promise<any>;
|
invoke: (channel: string, ...args: any[]) => Promise<any>;
|
||||||
getSearchSuggestions: (keyword: string) => Promise<any>;
|
getSearchSuggestions: (keyword: string) => Promise<any>;
|
||||||
lxMusicHttpRequest: (request: {
|
lxMusicHttpRequest: (request: { url: string; options: any; requestId: string }) => Promise<any>;
|
||||||
url: string;
|
|
||||||
options: any;
|
|
||||||
requestId: string;
|
|
||||||
}) => Promise<any>;
|
|
||||||
lxMusicHttpCancel: (requestId: string) => Promise<void>;
|
lxMusicHttpCancel: (requestId: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const api = {
|
|||||||
minimize: () => ipcRenderer.send('minimize-window'),
|
minimize: () => ipcRenderer.send('minimize-window'),
|
||||||
maximize: () => ipcRenderer.send('maximize-window'),
|
maximize: () => ipcRenderer.send('maximize-window'),
|
||||||
close: () => ipcRenderer.send('close-window'),
|
close: () => ipcRenderer.send('close-window'),
|
||||||
|
quitApp: () => ipcRenderer.send('quit-app'),
|
||||||
dragStart: (data) => ipcRenderer.send('drag-start', data),
|
dragStart: (data) => ipcRenderer.send('drag-start', data),
|
||||||
miniTray: () => ipcRenderer.send('mini-tray'),
|
miniTray: () => ipcRenderer.send('mini-tray'),
|
||||||
miniWindow: () => ipcRenderer.send('mini-window'),
|
miniWindow: () => ipcRenderer.send('mini-window'),
|
||||||
@@ -61,11 +62,8 @@ const api = {
|
|||||||
getSearchSuggestions: (keyword: string) => ipcRenderer.invoke('get-search-suggestions', keyword),
|
getSearchSuggestions: (keyword: string) => ipcRenderer.invoke('get-search-suggestions', keyword),
|
||||||
|
|
||||||
// 落雪音乐 HTTP 请求(绕过 CORS)
|
// 落雪音乐 HTTP 请求(绕过 CORS)
|
||||||
lxMusicHttpRequest: (request: {
|
lxMusicHttpRequest: (request: { url: string; options: any; requestId: string }) =>
|
||||||
url: string;
|
ipcRenderer.invoke('lx-music-http-request', request),
|
||||||
options: any;
|
|
||||||
requestId: string;
|
|
||||||
}) => ipcRenderer.invoke('lx-music-http-request', request),
|
|
||||||
|
|
||||||
lxMusicHttpCancel: (requestId: string) => ipcRenderer.invoke('lx-music-http-cancel', requestId)
|
lxMusicHttpCancel: (requestId: string) => ipcRenderer.invoke('lx-music-http-cancel', requestId)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<n-message-provider>
|
<n-message-provider>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
<traffic-warning-drawer v-if="!isElectron"></traffic-warning-drawer>
|
<traffic-warning-drawer v-if="!isElectron"></traffic-warning-drawer>
|
||||||
|
<disclaimer-modal></disclaimer-modal>
|
||||||
</n-message-provider>
|
</n-message-provider>
|
||||||
</n-dialog-provider>
|
</n-dialog-provider>
|
||||||
</n-config-provider>
|
</n-config-provider>
|
||||||
@@ -18,6 +19,7 @@ import { computed, nextTick, onMounted, watch } from 'vue';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import DisclaimerModal from '@/components/common/DisclaimerModal.vue';
|
||||||
import TrafficWarningDrawer from '@/components/TrafficWarningDrawer.vue';
|
import TrafficWarningDrawer from '@/components/TrafficWarningDrawer.vue';
|
||||||
import { usePlayerStore } from '@/store/modules/player';
|
import { usePlayerStore } from '@/store/modules/player';
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ import { useI18n } from 'vue-i18n';
|
|||||||
// 导入收款码图片
|
// 导入收款码图片
|
||||||
import alipayQRCode from '@/assets/alipay.png';
|
import alipayQRCode from '@/assets/alipay.png';
|
||||||
import wechatQRCode from '@/assets/wechat.png';
|
import wechatQRCode from '@/assets/wechat.png';
|
||||||
|
import { isElectron, isLyricWindow } from '@/utils';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -250,20 +251,8 @@ const qrcodeType = ref<'wechat' | 'alipay'>('wechat');
|
|||||||
const isTransitioning = ref(false); // 防止用户点击过快
|
const isTransitioning = ref(false); // 防止用户点击过快
|
||||||
|
|
||||||
// 检查是否需要显示免责声明
|
// 检查是否需要显示免责声明
|
||||||
const shouldShowDisclaimer = (): boolean => {
|
const shouldShowDisclaimer = () => {
|
||||||
const agreedTime = localStorage.getItem(DISCLAIMER_AGREED_KEY);
|
return !localStorage.getItem(DISCLAIMER_AGREED_KEY);
|
||||||
|
|
||||||
// 从未同意过
|
|
||||||
if (!agreedTime) return true;
|
|
||||||
|
|
||||||
const savedTime = parseInt(agreedTime, 10);
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
// 随机 3-10 天后再次显示
|
|
||||||
const randomDays = Math.floor(Math.random() * 8) + 3; // 3-10 天
|
|
||||||
const intervalMs = randomDays * 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
return now - savedTime >= intervalMs;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理同意
|
// 处理同意
|
||||||
@@ -278,13 +267,18 @@ const handleAgree = () => {
|
|||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理不同意 - 关闭窗口
|
// 处理不同意 - 退出应用
|
||||||
const handleDisagree = () => {
|
const handleDisagree = () => {
|
||||||
if (isTransitioning.value) return;
|
if (isTransitioning.value) return;
|
||||||
isTransitioning.value = true;
|
isTransitioning.value = true;
|
||||||
|
|
||||||
// Web 环境下尝试关闭窗口
|
if (isElectron) {
|
||||||
window.close();
|
// Electron 环境下强制退出应用
|
||||||
|
window.api?.quitApp?.();
|
||||||
|
} else {
|
||||||
|
// Web 环境下尝试关闭窗口
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
isTransitioning.value = false;
|
isTransitioning.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -316,6 +310,9 @@ const handleEnterApp = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 歌词窗口不显示免责声明
|
||||||
|
if (isLyricWindow.value) return;
|
||||||
|
|
||||||
// 检查是否需要显示免责声明
|
// 检查是否需要显示免责声明
|
||||||
if (shouldShowDisclaimer()) {
|
if (shouldShowDisclaimer()) {
|
||||||
showDisclaimer.value = true;
|
showDisclaimer.value = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user