Repository: iAJue/MoeKoeMusic Branch: main Commit: 88847bf44adf Files: 98 Total size: 893.1 KB Directory structure: gitextract_l9rjb4a1/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── discussion.yml │ │ └── feature_request.yml │ ├── PULL_REQUEST_TEMPLATE/ │ │ ├── bug_fix.md │ │ ├── docs_update.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── SECURITY.md │ └── workflows/ │ ├── AiIssueCheck.yml │ ├── AutoCloseIssues.yml │ ├── release.yml │ └── version.yml ├── .gitignore ├── .gitmodules ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── build/ │ ├── icons/ │ │ └── icon.icns │ ├── installer.nsh │ └── license.txt ├── docker-compose.yml ├── docs/ │ ├── README_en.md │ ├── README_ja.md │ ├── README_ko.md │ ├── README_ru.md │ └── README_tw.md ├── electron/ │ ├── appServices.js │ ├── extensions/ │ │ ├── extensionIPC.js │ │ ├── extensionManager.js │ │ └── extensions.js │ ├── language/ │ │ └── i18n.js │ ├── main.js │ ├── preload.cjs │ └── services/ │ ├── apiService.js │ ├── externalLinkHandler.js │ ├── statusBarLyricsService.js │ └── updater.js ├── index.html ├── nginx.conf ├── package.json └── src/ ├── App.vue ├── assets/ │ ├── style/ │ │ └── PlayerControl.css │ └── themes/ │ └── dark.css ├── components/ │ ├── AlbumGrid.vue │ ├── ArtistGrid.vue │ ├── BirthdayEasterEgg.vue │ ├── ContextMenu.vue │ ├── CustomModal.vue │ ├── Disclaimer.vue │ ├── ExtensionManager.vue │ ├── Header.vue │ ├── MessageNotification.vue │ ├── PlayerControl.vue │ ├── PlaylistGrid.vue │ ├── PlaylistSelectModal.vue │ ├── QueueList.vue │ ├── StatusBarLyrics.vue │ ├── TitleBar.vue │ └── player/ │ ├── AudioController.js │ ├── Helpers.js │ ├── LyricsHandler.js │ ├── MediaSession.js │ ├── PlaybackMode.js │ ├── ProgressBar.js │ ├── SongQueue.js │ └── index.js ├── language/ │ ├── en.json │ ├── ja.json │ ├── ko.json │ ├── ru.json │ ├── zh-CN.json │ └── zh-TW.json ├── layouts/ │ └── HomeLayout.vue ├── main.js ├── plugins/ │ ├── MessagePlugin.js │ └── ModalPlugin.js ├── router/ │ └── router.js ├── stores/ │ ├── musicQueue.js │ └── store.js ├── utils/ │ ├── apiBaseUrl.js │ ├── i18n.js │ ├── request.js │ └── utils.js └── views/ ├── CloudDrive.vue ├── Discover.vue ├── Home.vue ├── Library.vue ├── LocalMusic.vue ├── Login.vue ├── Lyrics.vue ├── PlaylistDetail.vue ├── Ranking.vue ├── Search.vue ├── Settings.vue └── VideoPlayer.vue ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: "🐞 Bug报告" description: 创建一个报告来帮助我们改进产品 title: '[Bug]: ' labels: ['bug'] body: - type: markdown attributes: value: | **在开始之前...** 此表单仅用于提交Bug报告。如果您有使用问题或不确定这是否真的是一个Bug,请确保: - 阅读[文档](https://music.moekoe.cn/) - 搜索是否有类似的问题 - 它可能已经被回答或修复 - 提供有效的 Bug 描述和明确的问题内容,否则 issue 将可能被直接关闭或忽略处理 如果您发现一个旧的、已关闭的问题在最新版本中仍然存在,请使用下面的表单打开一个新问题。 - type: input id: version attributes: label: 产品版本 placeholder: 例如:@ MoeKoe Music V1.4.3 - darwin validations: required: true - type: textarea id: steps-to-reproduce attributes: label: 复现步骤 description: | 我们需要做什么才能复现这个bug?请提供清晰简洁的复现说明,这对我们及时分类您的问题很重要。 placeholder: | 1. 打开... 2. 点击... 3. 滚动到... 4. 查看错误 validations: required: true - type: textarea id: expected attributes: label: 预期行为 description: 您期望看到什么? validations: required: true - type: textarea id: actually-happening attributes: label: 实际行为 description: 实际发生了什么? validations: required: true - type: textarea id: system-info attributes: label: 系统信息 description: 操作系统、网络环境、设备等 placeholder: | - 操作系统: [例如 Windows 10, macOS 12.0, Linux] - 网络环境: [例如 中国, 日本, 移动, WiFi] - 设备信息: [例如 笔记本, GTX1060, 16G, 台式机] - type: textarea id: additional-comments attributes: label: 其他补充说明 description: 例如:一些关于您如何遇到这个bug的背景/上下文。 ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: 🌈 社区交流 url: https://github.com/iAJue/MoeKoeMusic/discussions about: 加入我们的社区进行交流和讨论 - name: 📚 项目文档 url: https://music.moekoe.cn/ about: 查看项目文档以获取更多帮助信息 - name: 🛠️ 开发与编译 url: https://github.com/iAJue/MoeKoeMusic?tab=readme-ov-file#%EF%B8%8F-%E5%BC%80%E5%8F%91 about: 了解项目如何进行开发和编译 - name: 🤝 贡献指南 url: https://github.com/iAJue/MoeKoeMusic/blob/main/CONTRIBUTING.md about: 了解如何为项目做出贡献 ================================================ FILE: .github/ISSUE_TEMPLATE/discussion.yml ================================================ name: "💬 讨论问题" description: 提出一个需要讨论的话题或问题 title: '[讨论]: ' labels: ['question'] body: - type: markdown attributes: value: | **欢迎参与讨论!** 这个模板适用于那些不属于Bug报告或新特性请求的讨论话题。 例如:设计决策、架构问题、最佳实践问题等。 在开始之前,请确保: - 您已经搜索过现有的讨论,避免重复 - 您的问题足够清晰,以便其他人能够理解和参与讨论 或前往 [社区](https://github.com/iAJue/MoeKoeMusic/discussions) 进行讨论 - type: textarea id: topic attributes: label: 讨论主题 description: 请简明扼要地描述您想讨论的主题 placeholder: 我想讨论关于... validations: required: true - type: textarea id: context attributes: label: 背景和上下文 description: 请提供一些背景信息,帮助其他人理解为什么这个话题值得讨论 placeholder: | 这个话题在以下情况下很重要... 我遇到了以下挑战... validations: required: true - type: textarea id: questions attributes: label: 关键问题 description: 您希望通过此讨论解答哪些问题? placeholder: | 1. 我们应该如何处理...? 2. 什么是...的最佳实践? 3. 社区对...有什么看法? validations: required: true - type: textarea id: proposed-ideas attributes: label: 您的想法 description: 您对这个话题有什么想法或建议?分享您的初步思考 placeholder: 我认为我们可以... validations: required: false - type: dropdown id: topic-area attributes: label: 话题领域 description: 这个讨论主要涉及哪个领域? options: - 架构设计 - 用户体验 - 性能优化 - 开发流程 - 文档改进 - 社区建设 - 其他 validations: required: false - type: textarea id: additional-info attributes: label: 补充信息 description: 还有什么其他信息可以帮助丰富这次讨论? placeholder: 相关资源、链接、截图等... validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: "✨ 新特性请求" description: 为项目提出一个新想法或建议 title: '[新需求]: ' labels: ['enhancement'] body: - type: markdown attributes: value: | **感谢您的新特性建议!** 请花点时间填写以下表单,以便我们更好地理解您的需求。 在提交之前,请确保: - 搜索现有的issues和讨论,确保这个特性尚未被提出 - 确认这是一个新特性而不是bug修复 - 有完整的上下文链,帮助我们评估是否是值得添加的特性 - type: textarea id: problem-description attributes: label: 问题描述 description: 您想解决什么问题?请简明扼要地描述您遇到的问题或痛点。 placeholder: 我在使用产品时遇到的问题是... validations: required: true - type: textarea id: solution-description attributes: label: 解决方案描述 description: 您建议如何解决这个问题?请描述您期望的功能或改进。 placeholder: 我希望产品能够... validations: required: true - type: textarea id: alternatives attributes: label: 替代方案 description: 您考虑过哪些替代解决方案或功能? placeholder: 我也考虑过通过...来解决这个问题 validations: required: false - type: textarea id: additional-context attributes: label: 其他上下文 description: 您还有什么其他信息、截图或示例可以帮助我们更好地理解这个特性请求? placeholder: 其他相关信息... validations: required: false - type: dropdown id: importance attributes: label: 重要程度 description: 您认为这个特性对您使用产品的重要程度如何? options: - 必需(无法没有它) - 重要(显著改善体验) - 一般(有帮助但不关键) - 微小(锦上添花) validations: required: false ================================================ FILE: .github/PULL_REQUEST_TEMPLATE/bug_fix.md ================================================ --- name: 🐞 Bug Fix | 修复 Bug about: 修复现有的问题或异常 title: 'fix: 修复 [问题名称]' labels: bug --- ## 🐛 修复了什么?What is fixed? 请简要说明修复的 Bug 内容: > Describe the bug that has been fixed. --- ## 🔍 修复方法 How was it fixed? > 简述你是如何定位并解决该问题的。 Explain your approach or steps taken to fix the issue. --- ## ✅ 测试验证 How did you test? - [ ] 本地测试通过 Passed local tests - [ ] 已测试关键路径功能 Tested critical paths - [ ] 附加截图或日志(如适用)Attached screenshot/logs (if applicable) --- ## 🔗 相关 Issues / Related Issues > Closes #xxx 或 Related to #xxx ================================================ FILE: .github/PULL_REQUEST_TEMPLATE/docs_update.md ================================================ --- name: 📝 Docs Update | 文档更新 about: 提交文档相关的修改 title: 'docs: 更新 [文档主题]' labels: documentation --- ## 📄 更新内容 Description 请说明你更新了哪些文档,以及为什么更新它们: > Describe what parts of the documentation were updated and why. --- ## 📌 文档类型 Type of Docs - [ ] 使用说明 Usage guide - [ ] 开发者文档 Developer guide - [ ] API 文档 API references - [ ] 项目规范 Project rules - [ ] 其他 Other: _________ --- ## ✅ 其他说明 Notes > 是否与代码变更有关?是否同步更新了? Mention if this change is associated with any code changes or dependencies. --- ## 🔗 相关 Issues / Related Issues > Ref #xxx(如有关联) ================================================ FILE: .github/PULL_REQUEST_TEMPLATE/feature_request.md ================================================ --- name: ✨ New Feature | 新功能 about: 实现一个新功能或对现有功能进行增强 title: 'feat: 实现 [功能名称]' labels: enhancement --- ## 🌟 实现了什么?What is added or changed? 请说明新增了哪些功能、修改了哪些行为: > Describe the new functionality or changes introduced. --- ## 🎯 为什么需要这个改动?Why is it needed? > 请说明这个功能的背景、价值或需求来源。 Explain the motivation behind this feature or change. --- ## 🧪 如何测试?How to test? - [ ] 添加了单元测试 Added unit tests - [ ] 手动验证通过 Verified manually - [ ] 不需要额外测试 No additional test needed --- ## 🔗 相关 Issues / Related Issues > Resolves #xxx 或 Implements #xxx ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## ✨ 变更类型 Type of Change - [ ] Bug 修复 (fix) - [ ] 新功能 (feat) - [ ] 文档更新 (docs) - [ ] 样式调整 (style) - [ ] 重构 (refactor) - [ ] 测试相关 (test) - [ ] 构建/工具 (chore) - [ ] 其他 (other): --- ## 📋 变更描述 Description 请简要描述此次变更内容: > Describe your changes here. --- ## 🐛 如果是 Bug 修复,请描述问题和修复方法 Bug Fix - 问题是什么?What was the problem? - 如何修复的?How was it fixed? --- ## ✅ 测试验证 How did you test? - [ ] 本地测试通过 Passed local tests - [ ] 关键功能测试 Tested critical features - [ ] 其他测试描述 (如自动化测试、手动测试等): > Please describe testing details --- ## 📚 相关 Issues / Related Issues > Closes #xxx 或 Related to #xxx --- ## 📷 截图 / Screenshots (如果适用) > 如果有界面变动,附上截图或录屏 ================================================ FILE: .github/SECURITY.md ================================================ # Security Policy ## Supported Versions We actively maintain security updates for the following versions: | Version | Supported | |---------|--------------------| | 1.x | ✅ | | 0.x | ❌ (no longer supported) | ## Reporting a Vulnerability If you discover a security vulnerability in this project, please follow these steps: 1. **Do not open a public issue.** Please email us **privately** to allow time for remediation before public disclosure. 2. **Contact:** - 📧 Email: [MoeJue@qq.com](mailto:MoeJue@qq.com) - 🕒 Expected Response Time: 1–3 business days 3. **Information to include in your report:** - A clear and detailed description of the vulnerability - Steps to reproduce the issue - Potential impact or severity - (Optional) Any suggested fix or patch 4. **Responsible Disclosure Policy:** We believe in responsible disclosure and commit to: - Confirming and triaging valid reports promptly - Patching and releasing a fix within **30 days** - Crediting the reporter in the changelog (with consent) ## Security Best Practices (for users) To keep your environment safe, we recommend: - Always use the latest stable version - Avoid using deprecated versions - Keep your dependencies updated regularly - Use a secure environment (e.g. HTTPS, proper file permissions) --- 🔐 Thank you for helping us keep our project safe and secure! ================================================ FILE: .github/workflows/AiIssueCheck.yml ================================================ name: AI Issue Checker on: issues: types: [opened, edited] permissions: issues: write pull-requests: write jobs: check-issue: if: | github.event_name == 'issues' && ( github.event.action == 'opened' || ( github.event.action == 'edited' && github.event.issue.state == 'open' && contains(github.event.issue.labels.*.name, 'invalid') ) ) runs-on: ubuntu-latest steps: - name: Debug API Key existence run: | if [ -z "${{ secrets.OPENAI_API_KEY }}" ]; then echo "❌ OPENAI_API_KEY is NOT set" exit 1 else echo "✅ OPENAI_API_KEY is set" echo "API Key length: ${#OPENAI_API_KEY}" fi env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - name: Run AI Issue Validation uses: actions/github-script@v6 env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ISSUE_RULES: ${{ vars.ISSUE_RULES }} OPENAI_API_URL: ${{ secrets.OPENAI_API_URL }} with: script: | const issue = context.payload.issue const issueBody = issue.body || "" const rules = process.env.ISSUE_RULES const response = await fetch(`${process.env.OPENAI_API_URL}`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${process.env.OPENAI_API_KEY}` }, body: JSON.stringify({ model: "gpt-4o-mini", messages: [ { role: "system", content: rules }, { role: "user", content: issueBody } ], max_tokens: 200 }) }) const data = await response.json() let result try { result = JSON.parse(data.choices[0].message.content) } catch (e) { result = { valid: true, missing: [] } } const { data: comments } = await github.rest.issues.listComments({ issue_number: issue.number, owner: context.repo.owner, repo: context.repo.repo }) const botComment = comments.find(c => c.user.type === "Bot" && c.body.includes("感谢提交 Issue")) if (!result.valid) { const missingText = result.missing.join("、") const reasonText = result.reason const newBody = `⚠️ 感谢提交 Issue,但你的内容缺少以下部分:**${missingText}**。\n\n**${reasonText}**\n\n请根据模板补充完整后再提交,谢谢!` if (botComment) { await github.rest.issues.updateComment({ comment_id: botComment.id, owner: context.repo.owner, repo: context.repo.repo, body: newBody }) } else { await github.rest.issues.createComment({ issue_number: issue.number, owner: context.repo.owner, repo: context.repo.repo, body: newBody }) } await github.rest.issues.addLabels({ issue_number: issue.number, owner: context.repo.owner, repo: context.repo.repo, labels: ["invalid"] }) } else { const passBody = `✅ 感谢提交 Issue,你的 Issue 已经符合要求了,我们会尽快处理~\n\n💖 听说点了Star许愿的成功率更高哦~` if (botComment) { await github.rest.issues.updateComment({ comment_id: botComment.id, owner: context.repo.owner, repo: context.repo.repo, body: passBody }) } else { await github.rest.issues.createComment({ issue_number: issue.number, owner: context.repo.owner, repo: context.repo.repo, body: passBody }) } try { await github.rest.issues.removeLabel({ issue_number: issue.number, owner: context.repo.owner, repo: context.repo.repo, name: "invalid" }) } catch (e) { } } ================================================ FILE: .github/workflows/AutoCloseIssues.yml ================================================ name: Auto Close Invalid Issues on: schedule: - cron: "0 3 * * *" # 每天 UTC 03:00 运行一次 workflow_dispatch: permissions: issues: write jobs: close-invalid: runs-on: ubuntu-latest steps: - name: Close invalid issues uses: actions/github-script@v6 with: script: | const { data: issues } = await github.rest.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, state: "open", labels: "invalid", per_page: 100 }) const now = new Date() for (const issue of issues) { const updatedAt = new Date(issue.updated_at) const hoursSinceUpdate = (now - updatedAt) / (1000 * 60 * 60) if (hoursSinceUpdate > 24) { console.log(`Closing issue #${issue.number}`) const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number }) const botComment = comments.find(c => c.user.type === "Bot") const message = "⏳ 由于此 Issue 已标记为 **invalid** 并且超过 24 小时未更新,现自动关闭。" if (botComment) { await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: botComment.id, body: message }) } else { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, body: message }) } await github.rest.issues.update({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, state: "closed" }) } } ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: branches: - main workflow_dispatch: permissions: contents: write jobs: # 创建版本号 increment-version: if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && contains(github.event.head_commit.message, 'release')) name: Increment Version runs-on: ubuntu-latest outputs: version: ${{ steps.set-version.outputs.NEW_VERSION }} body: ${{ steps.create-release.outputs.RELEASE_BODY }} steps: - name: Checkout code uses: actions/checkout@v2 with: fetch-depth: 0 submodules: recursive - name: Increment version id: set-version run: | VERSION=$(git tag --sort=-v:refname | head -n 1) MAJOR=$(echo $VERSION | awk -F. '{print $1}' | sed 's/v//') MINOR=$(echo $VERSION | awk -F. '{print $2}') PATCH=$(echo $VERSION | awk -F. '{print $3}') PATCH=$((PATCH + 1)) if [ "$PATCH" -gt 9 ]; then PATCH=0 MINOR=$((MINOR + 1)) fi NEW_VERSION="v$MAJOR.$MINOR.$PATCH" echo "::set-output name=NEW_VERSION::$NEW_VERSION" - name: Create release body id: create-release run: | # 获取上一个tag PREV_TAG=$(git describe --tags --abbrev=0) # 获取最后一个tag到现在的所有commit信息 COMMITS=$(git log ${PREV_TAG}..HEAD --pretty=format:"* %s") # 如果没有commit信息,使用默认消息 if [ -z "$COMMITS" ]; then COMMITS="* Regular update and bug fixes" fi # 创建changelog链接 CHANGELOG_LINK="https://github.com/${GITHUB_REPOSITORY}/compare/${PREV_TAG}...${{ steps.set-version.outputs.NEW_VERSION }}" # 创建release body RELEASE_BODY="## What's Changed\n\n${COMMITS}\n\n**Full Changelog**: [${PREV_TAG}...${{ steps.set-version.outputs.NEW_VERSION }}](${CHANGELOG_LINK})" # 将release body转义后输出 echo "RELEASE_BODY<> $GITHUB_OUTPUT echo -e "$RELEASE_BODY" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.set-version.outputs.NEW_VERSION }} token: ${{ secrets.GITHUB_TOKEN }} name: "Release ${{ steps.set-version.outputs.NEW_VERSION }}" body: ${{ steps.create-release.outputs.RELEASE_BODY }} prerelease: false draft: false build-and-upload: name: Build and Upload needs: increment-version strategy: matrix: node: [lts/*] os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v6 with: submodules: true - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} cache: 'npm' # use cache - name: Build env: os: ${{ runner.os == 'Windows' && 'win' || runner.os == 'macOS' && 'macos' || 'linux' }} run: | npm run install-all npm run build npm run electron:build:${{ env.os }} - name: Rename files - win if: runner.os == 'Windows' run: | mv "./dist_electron/MoeKoe_Music_Setup_${{ needs.increment-version.outputs.version }}.exe" "./MoeKoe_Music_Setup_${{ needs.increment-version.outputs.version }}.exe" mv "./dist_electron/MoeKoe_Music_Setup_${{ needs.increment-version.outputs.version }}.exe.blockmap" "./MoeKoe_Music_Setup_${{ needs.increment-version.outputs.version }}.exe.blockmap" mv "./dist_electron/latest.yml" "./latest.yml" - name: Rename files - mac if: runner.os == 'macOS' run: | mv "./dist_electron/MoeKoe Music-arm64.dmg" "./MoeKoe_Music_${{ needs.increment-version.outputs.version }}-arm64.dmg" mv "./dist_electron/MoeKoe Music-x64.dmg" "./MoeKoe_Music_${{ needs.increment-version.outputs.version }}-x64.dmg" - name: Rename files - linux if: runner.os == 'Linux' run: | mv "./dist_electron/MoeKoe Music.AppImage" "./MoeKoe_Music_${{ needs.increment-version.outputs.version }}.AppImage" mv "./dist_electron/MoeKoe Music.deb" "./MoeKoe_Music_${{ needs.increment-version.outputs.version }}_amd64.deb" - name: Upload to Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.increment-version.outputs.version }} files: | ./MoeKoe_Music_Setup_${{ needs.increment-version.outputs.version }}.exe ./MoeKoe_Music_Setup_${{ needs.increment-version.outputs.version }}.exe.blockmap ./latest.yml ./MoeKoe_Music_${{ needs.increment-version.outputs.version }}-arm64.dmg ./MoeKoe_Music_${{ needs.increment-version.outputs.version }}-x64.dmg ./MoeKoe_Music_${{ needs.increment-version.outputs.version }}.AppImage ./MoeKoe_Music_${{ needs.increment-version.outputs.version }}_amd64.deb ================================================ FILE: .github/workflows/version.yml ================================================ name: Version Check on: workflow_dispatch: permissions: contents: read jobs: # 计算版本号 check-version: if: github.event_name == 'workflow_dispatch' name: Check Next Version runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 with: fetch-depth: 0 - name: Get latest tag run: | echo "Latest tag: $(git tag --sort=-v:refname | head -n 1)" - name: Calculate next version id: set-version run: | VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "v1.2.0") MAJOR=$(echo $VERSION | awk -F. '{print $1}' | sed 's/v//') MINOR=$(echo $VERSION | awk -F. '{print $2}') PATCH=$(echo $VERSION | awk -F. '{print $3}') PATCH=$((PATCH + 1)) if [ "$PATCH" -gt 9 ]; then PATCH=0 MINOR=$((MINOR + 1)) fi NEW_VERSION="v$MAJOR.$MINOR.$PATCH" echo "::set-output name=NEW_VERSION::$NEW_VERSION" - name: Get previous tag run: | PREV_TAG=$(git describe --tags --abbrev=0) echo "Previous version: $PREV_TAG" - name: Display version information run: | echo "Current version: $(git describe --tags --abbrev=0)" echo "Next version will be: ${{ steps.set-version.outputs.NEW_VERSION }}" echo "Changes since last tag:" git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:"* %s" ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* .history node_modules dist_electron dist dist-ssr *.local test bin/* .serena/* api/.serena/* # Editor directories and files .codebuddy/* .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? dev-dist/* pnpm-lock.yaml pnpm-workspace.yaml vite.config.js* plugins/extensions/* ================================================ FILE: .gitmodules ================================================ [submodule "api"] path = api url = https://github.com/MakcRe/KuGouMusicApi ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # 贡献者公约 ## 我们的承诺 为了营造一个开放和友好的环境,作为贡献者和维护者,我们承诺让每个人都能参与我们的项目和社区,享受无骚扰的体验,无论年龄、体型、残疾、种族、性别特征、性别认同和表达、经验水平、教育、社会经济地位、国籍、个人外貌、种族、宗教或性别认同和取向如何。 ## 我们的标准 有助于创造积极环境的行为包括: - 使用友好和包容的语言 - 尊重不同的观点和经验 - 优雅地接受建设性的批评 - 关注对社区最有利的事情 - 对其他社区成员表示同理心 不可接受的行为包括: - 使用性暗示的语言或图像 - 发表侮辱性或贬损性的评论 - 公开或私下的骚扰 - 未经明确许可发布他人的私人信息 - 其他不道德或专业上不适当的行为 ## 我们的责任 项目维护者有责任澄清可接受行为的标准,并应对任何不可接受的行为采取适当和公平的纠正措施。 项目维护者有权和责任删除、编辑或拒绝不符合本行为准则的评论、提交、代码、wiki编辑、问题和其他贡献,或暂时或永久地禁止任何贡献者进行其他他们认为不适当、威胁、冒犯或有害的行为。 ## 适用范围 本行为准则适用于项目空间和公共空间,当个人代表项目或其社区时。代表项目或社区的例子包括使用官方项目电子邮件地址、通过官方社交媒体账号发布信息,或在线上或线下活动中作为指定代表。项目维护者可以进一步定义和澄清代表项目的情况。 ## 执行 可以通过联系项目团队来举报虐待、骚扰或其他不可接受的行为。所有投诉都将被审查和调查,并会做出必要和适当的回应。项目团队有义务对事件报告者保密。 不遵守或不执行行为准则的项目维护者可能会面临项目领导层决定的临时或永久性后果。 ## 归属 本行为准则改编自[贡献者公约][homepage],版本 1.4,可在 https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 查看 [homepage]: https://www.contributor-covenant.org ## 翻译 本行为准则有多个语言版本。请访问 https://www.contributor-covenant.org/translations 查看其他语言版本。 ================================================ FILE: CONTRIBUTING.md ================================================ # 贡献指南 感谢您考虑为我们的项目做出贡献!无论是报告bug、提出新功能建议,还是提交代码,您的参与都将帮助我们改进这个项目。 ## 目录 - [行为准则](#行为准则) - [如何贡献](#如何贡献) - [报告Bug](#报告Bug) - [提出新特性](#提出新特性) - [参与讨论](#参与讨论) - [提交代码](#提交代码) - [开发流程](#开发流程) - [环境设置](#环境设置) - [代码风格](#代码风格) - [测试](#测试) - [提交规范](#提交规范) - [Pull Request流程](#Pull-Request流程) - [分支管理策略](#分支管理策略) - [发布流程](#发布流程) - [联系我们](#联系我们) ## 行为准则 本项目遵循[贡献者公约](https://www.contributor-covenant.org/)行为准则。参与本项目,即表示您同意遵守此准则。不可接受的行为可以向项目维护者报告。 ## 如何贡献 ### 报告Bug 如果您发现了bug,请使用项目的Issue模板创建一个新的Issue,并尽可能详细地提供以下信息: - 查看已有的 Issues,确保该问题尚未被报告 - 使用"🐞 Bug报告"模板 - 清晰描述发生了什么以及您期望的行为 - 提供详细的复现步骤 - 如可能,提供错误的截图或录屏 - 提供您使用的系统和浏览器信息 ### 提出新特性 如果您希望看到新的功能或改进,请使用"✨ 新特性请求"模板创建一个Issue,并: - 清晰描述您希望解决的问题 - 描述您想要的解决方案 - 考虑其他可能的替代解决方案 - 提供相关示例或参考(如果有) ### 参与讨论 对于需要更多社区意见的话题,请使用"💬 讨论问题"模板创建一个Issue,或者参与现有的讨论。您的洞见和观点对项目的发展至关重要。 ### 提交代码 如果您希望通过代码贡献参与项目,请遵循以下步骤: 1. 查看现有Issues,找到您感兴趣的任务或问题 2. 在Issue上留言表示您计划处理这个问题,避免重复工作 3. Fork项目仓库到您的个人账号 4. 创建一个新的分支进行您的修改 5. 编写代码和测试 6. 提交Pull Request ## 开发流程 ### 环境设置 1. **Fork 本仓库** 点击页面右上角的 `Fork` 按钮,复制仓库到你的 GitHub 账户。 2. **克隆仓库到本地** 使用 Git 克隆你 Fork 的仓库: ```bash git clone https://github.com/your-username/MoeKoeMusic.git ``` 3. **创建一个新的分支** 为你的功能或修复创建一个新的分支: ```bash git checkout -b your-feature-branch ``` 4. **安装依赖并进行开发** 请根据项目的文档安装所需的依赖,并开始开发。 5. **提交更改并推送** 完成开发后,提交并推送你的更改: ```bash git add . git commit -m "Your detailed commit message" git push origin your-feature-branch ``` 6. **提交合并请求(PR)** 提交 PR 之前,请确保你的分支已经更新,并且没有冲突。打开你的 GitHub 仓库,点击 Compare & pull request 提交 PR。 ### 代码风格 我们使用ESLint和Prettier来保持代码风格一致。在提交代码前,请确保您的代码符合项目的风格指南: - 使用统一的代码格式(例如:缩进、空格、命名规范) - 遵循项目的代码结构 - 请编写易于理解的代码,并添加必要的注释 ### 测试 提交代码前,请确保所有测试通过: ```bash npm run test ``` 如果您添加了新功能,也请同时添加相应的测试。 ### 提交规范 我们使用[约定式提交](https://www.conventionalcommits.org/)规范来格式化提交信息,基本格式如下: ``` <类型>[可选的作用域]: <描述> [可选的正文] [可选的脚注] ``` 常用的提交类型包括: - **feat**: 新功能 - **fix**: 修复Bug - **docs**: 文档更新 - **style**: 代码风格调整(不影响代码逻辑) - **refactor**: 代码重构 - **perf**: 性能优化 - **test**: 添加或更新测试 - **chore**: 构建过程或辅助工具的变动 另外我们也接受 **Gitmoji** 的提交风格: | Emoji | 类型 | 说明 | 示例 | | ----- | ------------ | ----------------------- | ---------------------------------------- | | ✨ | `feat` | 新功能(Feature) | `✨ feat: 添加搜索功能` | | 🐛 | `fix` | 修复 Bug | `🐛 fix: 修复登录时闪退的问题` | | ♻️ | `refactor` | 代码重构(非功能性更改) | `♻️ refactor: 重构用户模块代码结构` | | 📝 | `docs` | 修改文档 | `📝 docs: 更新 README 安装说明` | | 🎨 | `style` | 格式/排版修改(不影响代码逻辑) | `🎨 style: 调整代码缩进和格式` | | ✅ | `test` | 添加或修改测试代码 | `✅ test: 添加用户服务单元测试` | | 🚀 | `perf` | 性能优化 | `🚀 perf: 提升图片加载速度` | | 🔧 | `chore` | 构建配置/脚本/依赖等杂项 | `🔧 chore: 更新依赖包` | | 🔥 | `remove` | 删除无用代码或文件 | `🔥 remove: 删除未使用的组件` | | 📦 | `build` | 打包相关改动(构建、CI) | `📦 build: 配置打包输出目录` | | 🔀 | `merge` | 合并分支 | `🔀 merge: 合并 dev 分支` | | 🚧 | `wip` | 开发中(Work In Progress) | `🚧 wip: 正在实现订单详情页面` | | ⬆️ | `upgrade` | 升级依赖 | `⬆️ upgrade: 升级 Electron 到 v28` | | ⬇️ | `downgrade` | 降级依赖 | `⬇️ downgrade: 降级 vue-router 到 v4.0.0`| | 🐳 | `docker` | 与 Docker 相关的更改 | `🐳 docker: 添加 Dockerfile` | | 💄 | `ui` | 修改 UI 或样式 | `💄 ui: 优化按钮样式` | | 💥 | `breaking` | 破坏性更新(需注意兼容) | `💥 breaking: 移除旧版 API` | | 📈 | `analytics` | 数据分析或埋点代码 | `📈 analytics: 添加页面浏览统计` | | 🔖 | `release` | 正式发布一个版本 | `🔖 release: v1.2.0` | ## Pull Request流程 1. 确保您的Pull Request(PR)有一个清晰的标题和描述 2. 将您的PR关联到相关的Issue(如果有) 3. 确保所有自动化测试通过 4. 至少需要一名项目维护者的代码审查和批准 5. 如果需要更改,请在同一PR中进行修改 6. 一旦获得批准,您的代码将被合并到主分支 ## 分支管理策略 我们使用以下分支命名和管理策略: - `main`/`master`: 主分支,包含稳定、可发布的代码 - `develop`: 开发分支,包含最新的开发代码 - `feature/*`: 新功能分支,从`develop`分支创建 - `bugfix/*`: Bug修复分支,从`develop`分支创建 - `hotfix/*`: 紧急修复分支,从`main`分支创建 - `release/*`: 发布准备分支,从`develop`分支创建 ## 发布流程 我们使用[语义化版本](https://semver.org/)进行版本管理: - MAJOR版本:当你做了不兼容的API修改 - MINOR版本:当你做了向下兼容的功能性新增 - PATCH版本:当你做了向下兼容的问题修正 ## 联系我们 如果您有任何问题或需要进一步的帮助,请通过以下方式联系我们: - 在GitHub上创建Issue - 在Blog中留言 [Blog](https://MoeJue.cn) 再次感谢你对项目的支持和贡献! ================================================ FILE: Dockerfile ================================================ # Stage 1: Build Frontend FROM node:20-alpine AS frontend-builder WORKDIR /app COPY package*.json ./ # Remove electron and electron-builder from package.json RUN node -e "const fs = require('fs'); const filePath = './package.json'; \ let rawdata = fs.readFileSync(filePath); let packageJson = JSON.parse(rawdata); \ if (packageJson.devDependencies) { \ delete packageJson.devDependencies.electron; \ delete packageJson.devDependencies['electron-builder']; \ } \ if (packageJson.dependencies) { \ delete packageJson.dependencies.electron; \ } \ fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2));" RUN npm install COPY . . RUN npm run build:docker # Stage 2: Setup Combined App FROM node:20-alpine WORKDIR /app # Install Nginx RUN apk add --no-cache nginx # Copy API code COPY ./api ./api # Install API dependencies WORKDIR /app/api RUN npm install --production # Reset WORKDIR to /app WORKDIR /app # Copy built frontend static assets from the builder stage COPY --from=frontend-builder /app/dist/ ./dist/ # Expose ports # For frontend served by 'serve' EXPOSE 8080 # For API EXPOSE 6521 # Copy Nginx configuration COPY nginx.conf /etc/nginx/nginx.conf # Command to run both services # API runs from /app/api directory, frontend served by Nginx # CMD ["sh", "-c", "cd /app/api && node app.js & nginx -g 'daemon off;'"] CMD sh -c "\ echo 'client running @ http://127.0.0.1:8080/'; \ cd /app/api && node app.js & nginx -g 'daemon off;'" ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ================================================ FILE: README.md ================================================

Logo

MoeKoe Music

一款开源简洁高颜值的酷狗第三方客户端
🌎 GitHub仓库  |   📦️ 下载安装包  |   💬 访问博客  |   🏠 项目主页

🇨🇳 简体中文  |   🇨🇳 繁体中文  |   🇯🇵 日本語  |   🇺🇸 English  |   🇰🇷 한국어  |   🇷🇺 Русский

![images](https://github.com/iAJue/MoeKoeMusic/raw/main/images/1.png) ## ❤️ 前言 早在10年前后的样子,那会在用网页版QQ的时候我就已经开始使用酷狗音乐了(也是十来年的老粉了),所以这些年收藏的歌曲全部都在上面.后来我也尝试开始使用网易云或QQ音乐,也尝试把酷狗的歌单导入进去,但是效果都不尽人意.我听的大多是日漫OP,好多歌曲都没办法找到. 兜兜转转最后还是回到酷狗,但是在Mac端的酷狗,时常可能会出现不能播放的情况,虽说界面没什么功能,但也挺好的.在网友的安利下,我现在一直是在酷狗的[概念版](https://t1.kugou.com/d2tBza3CSV2)上听歌,并且是市面上为数不多能免费听VIP歌曲的音乐播放软件了,力推. 我在我的个人介绍页面说我特别喜欢听歌,尤其是日漫OP.怎么证明呢?(之前我网页版歌单也年久失修了)那就自己开发一个音乐播放器. ## ✨ 特性 - ✅ 使用 Vue.js 全家桶开发 - 🔴 酷狗账号登录(扫码/手机/账号登录) - 📃 支持歌词显示 - 📻 每日推荐歌曲 - 🚫🤝 无任何社交功能 - 🔗 官方服务器直连, 无任何第三方 API - ✔️ 每日自动领取VIP, 登录就是VIP - 🎨 主题色切换 - 👋 启动问候语 - ⚙️ 多平台支持 - 🛠 更多特性开发中 ## 📢 Todo List - [x] 📺 支持 MV 播放 - [x] 🌚 Light/Dark Mode 自动切换 - [x] 👆 支持 Touch Bar - [x] 🖥️ 支持 PWA,可在 Chrome/Edge 里点击地址栏右边的 ➕ 安装到电脑 - [ ] 🎧 支持 Mpris - [x] ⌨️ 全局快捷键 - [x] 🤟 多语言支持 - [x] 📻 桌面歌词 - [x] ⚙️ 系统架构优化 - [x] 🎶 歌曲、歌单/收藏、取消 更新日志请查看 [Commits](https://github.com/iAJue/MoeKoeMusic/commits/main/) ## 📦️ 安装 ### 1. 客户端安装 访问本项目的 [Releases](https://github.com/iAJue/MoeKoeMusic/releases) 页面下载安装包。 ### 2. WEB端安装(docker) * 注意:部署后请开放服务器对应端口才可使用,或者使用反向代理实现域名访问。 1. 方式一:快速启动(推荐) ``` git clone https://github.com/iAJue/MoeKoeMusic.git cd MoeKoeMusic git submodule update --init --recursive docker compose up -d & ``` 2. ~~方式二:使用docker-compose一键安装 (镜像暂未上传官方)~~ ``` docker run -d --name MoeKoeMusic -p 8080:8080 -p 6521:6521 -e PORT=6521 -e platform=lite iajue/moekoe-music:latest ``` 3. 方式三:宝塔容器编排 * 远程镜像,版本可能会落后于官方 ``` version: '3.3' services: moekoe-music: # 镜像地址 image: registry.cn-wulanchabu.aliyuncs.com/youngxj/moekoe-music:latest container_name: moekoe-music # 容器名 restart: unless-stopped # 自动重启 build: context: . dockerfile: Dockerfile environment: - PORT=6521 - platform=lite ports: # 端口映射 - "8080:8080" # 前端服务 - "6521:6521" # 接口服务 ``` 复制内容上面的内容,粘贴到宝塔面板的容器编排里面,编排名称为MoeKoeMusic,点击部署即可。 ### 3. 一键部署 [![使用 EdgeOne Pages 部署](https://cdnstatic.tencentcs.com/edgeone/pages/deploy.svg)](https://edgeone.ai/pages/new?template=https://github.com/iAJue/moekoemusic&install-command=npm%20install&output-directory=dist&root-directory=.%2F&build-command=npm%20run%20build&env=VITE_APP_API_URL) 需在环境变量(VITE_APP_API_URL)中填写自己的API地址 ## ⚙️ 开发 1. 克隆本仓库 ```sh git clone --recurse-submodules https://github.com/iAJue/MoeKoeMusic.git ``` 2. 进入目录并安装依赖 ```sh cd MoeKoeMusic npm run install-all ``` 3. 启动开发者模式 ```sh npm run dev ``` 4. 打包项目 ```sh npm run build ``` 5. 编译项目 - Windows: ```sh npm run electron:build:win [默认 NSIS 安装包] ``` - Linux: ```sh npm run electron:build:linux [默认 AppImage 格式] ``` - macOS: ```sh npm run electron:build:macos [默认 macOS 双架构] ``` 更多命令请查看 `package.json` 文件 `scripts` ## 👷‍♂️ 编译客户端 如果在 Release 页面没有找到适合你的设备的安装包的话,你可以根据下面的步骤来打包自己的客户端。 1. 安装 [Node.js](https://nodejs.org/en/),并确保 `Node.js` 版本 >= 18.0.0。 2. 使用 `git clone https://github.com/iAJue/MoeKoeMusic.git` 克隆本仓库到本地。 3. 使用 `npm install` 安装项目依赖。 4. 编译API服务端 - Windows: ```sh npm run build:api:win ``` - Linux: ```sh npm run build:api:linux ``` - macOS: ```sh npm run build:api:macos ``` 5. 选择下列的命令来打包适合的你的安装包,打包出来的文件在 `/dist_electron` 目录下。了解更多信息可访问 [electron-builder 文档](https://www.electron.build/cli) #### 1. 打包 macOS 平台 - 通用的 macOS 包(Intel 和 Apple Silicon 双架构): ``` npm run electron:build -- --mac --universal ``` - 仅 Intel 架构: ``` npm run electron:build -- --mac --x64 ``` - 仅 Apple Silicon 架构: ``` npm run electron:build -- --mac --arm64 ``` #### 2. 打包 Windows 平台 - 默认 NSIS 安装包(适合大多数 Windows 用户): ``` npm run electron:build -- --win ``` - 为 Windows 创建 EXE 文件和 Squirrel 安装包: ``` npm run electron:build -- --win --ia32 --x64 --arm64 --target squirrel ``` - --ia32 为 32 位 Windows 架构。 - --x64 为 64 位 Windows 架构。 - --arm64 为 ARM Windows 架构(Surface 等设备)。 - 为 Windows 生成便携式的 EXE 文件(免安装): ``` npm run electron:build -- --win --portable ``` #### 3. 打包 Linux 平台 - 默认 AppImage 格式(适用于大多数 Linux 发行版): ``` npm run electron:build -- --linux ``` - snap(适用于 Ubuntu 和支持 snap 的发行版): ``` npm run electron:build -- --linux --target snap ``` - deb(适用于 Debian/Ubuntu 系列): ``` npm run electron:build -- --linux --target deb ``` - rpm(适用于 Red Hat/Fedora 系列): ``` npm run electron:build -- --linux --target rpm ``` - ARM64架构(ARM v8+): ``` npm run build:api:linux-arm64 //编译API npm run electron:build:linux-arm64 //编译主程序 ``` #### 4. 打包所有平台 如果需要同时生成 Windows、macOS 和 Linux 的安装包,可以使用以下命令: ``` npm run electron:build -- -mwl ``` #### 5. 自定义编译设置 您可以根据需要添加其他选项来进一步自定义打包,例如指定 x64 和 arm64 架构,或选择不同的目标格式。 ## ⭐ 支持项目 如果您觉得这个项目对您有帮助,欢迎给我们一个 Star!您的支持是我们持续改进的动力。 [![GitHub stars](https://img.shields.io/github/stars/iAJue/MoeKoeMusic.svg?style=social&label=Star)](https://github.com/iAJue/MoeKoeMusic) ## ✅ 反馈 如有任何问题或建议,欢迎提交 issue 或 pull request。 ## ⚠️ 免责声明 0. 本程序是酷狗第三方客户端,并非酷狗官方,需要更完善的功能请下载官方客户端体验. 1. 本项目仅供学习使用,请尊重版权,请勿利用此项目从事商业行为及非法用途! 2. 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在 24 小时内清除使用本项目的过程中所产生的版权数据。 3. 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。 1. 禁止在违反当地法律法规的情况下使用本项目。对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。 2. 音乐平台不易,请尊重版权,支持正版。 3. 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。 4. 如果官方音乐平台觉得本项目不妥,可联系本项目更改或移除。 ## 📜 开源许可 本项目仅供个人学习研究使用,禁止用于商业及非法用途。 基于 [GNU General Public License v2.0 (GPL-2.0)](https://github.com/iAJue/MoeKoeMusic/blob/main/LICENSE) 许可进行开源。 ## 👍 灵感来源 API 源代码来自 [MakcRe/KuGouMusicApi](https://github.com/MakcRe/KuGouMusicApi) - [Apple Music](https://music.apple.com) - [YouTube Music](https://music.youtube.com) - [YesPlayMusic](https://github.com/qier222/YesPlayMusic) - [酷狗音乐](https://kugou.com/) ## 🖼️ 截图 ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/2.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/3.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/4.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/5.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/6.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/7.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/8.png) ## 🗓️ Star History [![Star History Chart](https://api.star-history.com/svg?repos=iAJue/MoeKoeMusic&type=Date)](https://www.star-history.com/#iAJue/MoeKoeMusic&Date) ================================================ FILE: build/installer.nsh ================================================ !include "MUI.nsh" !define MUI_FINISHPAGE_LINK_LOCATION "https://MoeJue.cn" !define MUI_FINISHPAGE_LINK "访问作者(阿珏酱)主页" !define MUI_FINISHPAGE_SHOWREADME_TEXT "访问 GitHub 项目主页" !define MUI_FINISHPAGE_SHOWREADME "https://github.com/iAJue/MoeKoeMusic" !insertmacro MUI_PAGE_WELCOME ; Register the moekoe:// protocol !macro customInstall DeleteRegKey HKCR "moekoe" WriteRegStr HKCR "moekoe" "" "URL:MoeKoe Music Protocol" WriteRegStr HKCR "moekoe" "URL Protocol" "" WriteRegStr HKCR "moekoe\DefaultIcon" "" "$INSTDIR\${PRODUCT_NAME}.exe,0" WriteRegStr HKCR "moekoe\shell" "" "" WriteRegStr HKCR "moekoe\shell\open" "" "" WriteRegStr HKCR "moekoe\shell\open\command" "" '"$INSTDIR\${PRODUCT_NAME}.exe" "%1"' !macroend ================================================ FILE: build/license.txt ================================================ MoeKoe Music Software License Agreement 1. Terms of Use This software is released under the GPL-2.0 license. You are free to use, modify, and distribute this software, but must comply with all terms of the GPL-2.0 license. This means any modified versions must also be open-sourced under the same license. 2. Disclaimer This software is provided "as is" without any express or implied warranties. 3. Copyright Notice Copyright 2024 MoeKoe. All rights reserved. 4. Usage Restrictions - Prohibited for any illegal purposes - Prohibited for commercial use 5. Privacy Policy This software collects necessary usage data to improve service quality. 6. Termination Any violation of the terms in this agreement will result in automatic termination of the license. By installing or using this software, you agree to accept all terms of this agreement. ================================================ FILE: docker-compose.yml ================================================ version: '3.3' services: moekoe-music: container_name: moekoe-music # 容器名 restart: unless-stopped # 自动重启 build: context: . dockerfile: Dockerfile environment: - PORT=6521 - platform=lite ports: # 端口映射 - "8080:8080" # 前端服务 - "6521:6521" # 接口服务 ================================================ FILE: docs/README_en.md ================================================ > **Note**: This English document may not be updated in a timely manner. For the latest content, please refer to the [Simplified Chinese version](https://github.com/iAJue/MoeKoeMusic/README.md).

Logo

MoeKoe Music

An open-source, concise, and aesthetically pleasing third-party client for KuGou
🌎 GitHub Repository  |   📦️ Download Packages  |   💬 Visit Blog  |   🏠 Project Homepage

🇨🇳 简体中文  |   🇨🇳 繁体中文  |   🇯🇵 日本語  |   🇺🇸 English  |   🇰🇷 한국어  |   🇷🇺 Русский

![images]( https://github.com/iAJue/MoeKoeMusic/raw/main/images/1.png ) ## ❤️ Preface As early as around 10 years ago, when I was using the web version of QQ, I had already started using Kugou Music (which I have been a fan of for over ten years), so all the songs I collected over the years were on it Later on, I also tried using NetEase Cloud or QQ Music, and tried importing Kugou's playlists into it, but the results were not satisfactory I mostly listen to Japanese anime OP, and I can't find many songs After wandering around, I finally returned to Kugou. However, on the Mac version of Kugou, there may often be situations where it cannot be played. Although the interface does not have many functions, it is still quite good With the support of netizens, I have been working on [Kugou's concept version](https://t1.kugou.com/d2tBza3CSV2)Listen to music online, and it is one of the few music playback software on the market that allows you to listen to VIP songs for free. It is highly recommended I said on my personal introduction page that I particularly enjoy listening to music, especially Japanese anime OP How can we prove it? (My web version of the playlist was also in disrepair for a long time before) So I'll develop my own music player ## ✨ characteristic - ✅ Developing with Vue.js Family Bucket - 🔴 KuGou account login (scan code/phone/account login) - 📃 Support lyric display - 📻 Daily recommended songs - 🚫🤝 No social function - 🔗 Official server direct connection, without any third-party APIs - ✔️ Automatically claim VIP every day, log in to become VIP - 🎨 Theme color switching - 👋 Initiate greetings - ⚙️ Multi platform support - 🛠 More features under development ## 📢 Todo List - [x] 📺 Support MV playback - [x] 🌚 Light/Dark Mode Automatic switching - [x] 👆 Support Touch Bar - [x] 🖥️ Support PWA, you can click on the right side of the address bar in Chrome/Edge ➕ Install to computer - [ ] 🎧 Support Mpris - [x] ⌨️ Global shortcut keys - [x] 🤟 Multi language support - [x] 📻 Desktop Lyrics - [x] ⚙️ System architecture optimization - [x] 🎶 Songs, playlists/favorites, cancellation Please check the for the update log [Commits](https://github.com/iAJue/MoeKoeMusic/commits/main/) ## 📦️ Installation ### 1. Client Installation Visit the [Releases](https://github.com/iAJue/MoeKoeMusic/releases) page of this project to download the installation package. ### 2. Web Installation (Docker) * Note: Please open the corresponding port on the server after deployment, or use a reverse proxy for domain access. 1. Method 1: Quick Start (Recommended) ``` git clone https://github.com/iAJue/MoeKoeMusic.git cd MoeKoeMusic git submodule update --init --recursive docker compose up -d & ``` 2. ~~Method 2: One-click installation using docker-compose (image not yet uploaded officially)~~ ``` docker run -d --name MoeKoeMusic -p 8080:8080 -p 6521:6521 -e PORT=6521 -e platform=lite iajue/moekoe-music:latest ``` 3. Method 3: Baota Container Orchestration * Remote image, version may be behind the official ``` version: '3.3' services: moekoe-music: # Image address image: registry.cn-wulanchabu.aliyuncs.com/youngxj/moekoe-music:latest container_name: moekoe-music # Container name restart: unless-stopped # Auto restart build: context: . dockerfile: Dockerfile environment: - PORT=6521 - platform=lite ports: # Port mapping - "8080:8080" # Frontend service - "6521:6521" # API service ``` Copy the content above and paste it into the container orchestration in the Baota panel, name the orchestration as MoeKoeMusic, and click deploy. ### 3. One-Click Deployment [![使用 EdgeOne Pages 部署](https://cdnstatic.tencentcs.com/edgeone/pages/deploy.svg)](https://edgeone.ai/pages/new?template=https://github.com/iAJue/moekoemusic&install-command=npm%20install&output-directory=dist&root-directory=.%2F&build-command=npm%20run%20build&env=VITE_APP_API_URL) You need to fill in your own API address in the environment variable VITE_APP_API_URL. ## ⚙️ development 1. Clone this repository ```sh git clone --recurse-submodules https://github.com/iAJue/MoeKoeMusic.git ``` 2. Enter the directory and install dependencies ```sh cd MoeKoeMusic npm run install-all ``` 3. Launch developer mode ```sh npm run dev ``` 4. Package project ```sh npm run build ``` 5. Compile the project - Windows: ```sh Npm run electron: build: win [default NSIS installation package] ``` - Linux: ```sh Npm run electron: build: Linux [default AppImage format] ``` - macOS: ```sh Npm run electron: build: macos [default universal architecture] ``` For more commands, please refer to the ` package.json ` file ` scripts ` ## 👷‍♂️ Compile client If you cannot find the installation package suitable for your device on the Release page, you can follow the steps below to package your own client. 1. Install [Node.js](https://nodejs.org/en/)And ensure that the 'Node. js' version is>=18.0.0. 2. Use ` git clone https://github.com/iAJue/MoeKoeMusic.git `Clone this repository locally. 3. Use 'npm install' to install project dependencies. 4. Compile API server - Windows: ```sh npm run build:api:win ``` - Linux: ```sh npm run build:api:linux ``` - macOS: ```sh npm run build:api:macos ``` 5. Choose the following command to package the appropriate installation package for you, and the packaged file should be located in the '/dits_electron' directory. For more information, please visit the [Electron Builder documentation](https://www.electron.build/cli ) #### 1. Package macOS platform - Universal macOS package (Intel and Apple Silicon dual architecture): ``` npm run electron:build -- --mac --universal ``` - Only Intel architecture: ``` npm run electron:build -- --mac --x64 ``` - Only Apple Silicon architecture: ``` npm run electron:build -- --mac --arm64 ``` #### 2. Package Windows Platform - Default NSIS installation package (suitable for most Windows users): ``` npm run electron:build -- --win ``` - Create EXE files and Squirrel installation packages for Windows: ``` npm run electron:build -- --win --ia32 --x64 --arm64 --target squirrel ``` - Ia32 is a 32-bit Windows architecture. - X64 is a 64 bit Windows architecture. - Arm64 is based on ARM Windows architecture (for devices such as Surface). - Generate portable EXE files for Windows (installation free): ``` npm run electron:build -- --win --portable ``` #### 3. Packaging Linux Platform - Default AppImage format (applicable to most Linux distributions): ``` npm run electron:build -- --linux ``` - Snap (for Ubuntu and Snap supported distributions): ``` npm run electron:build -- --linux --target snap ``` - Deb (applicable to the Debian/Ubuntu series): ``` npm run electron:build -- --linux --target deb ``` - RPM (applicable to Red Hat/Fedora series): ``` npm run electron:build -- --linux --target rpm ``` #### 4. Package all platforms If you need to generate installation packages for Windows, macOS, and Linux simultaneously, you can use the following command: ``` npm run electron:build -- -mwl ``` #### 5. Custom compilation settings You can add other options as needed to further customize the packaging, such as specifying x64 and arm64 architectures, or selecting different target formats. ## ⭐ Support This Project If you find this project helpful, please consider giving us a star! Your support motivates us to keep improving. [![GitHub stars](https://img.shields.io/github/stars/iAJue/MoeKoeMusic.svg?style=social&label=Star)](https://github.com/iAJue/MoeKoeMusic) ## ☑️ feedback If you have any questions or suggestions, please feel free to submit an issue or pull request. ## ⚠️ Disclaimers 0. This program is a third-party client of KuGou, not an official KuGou client. If you need more complete functions, please download the official client to experience it 1. This project is for learning purposes only. Please respect copyright and do not use this project for commercial activities or illegal purposes! 2. Copyright data may be generated during the use of this project. For these copyrighted data, this project does not own them. To avoid infringement, users must clear any copyright data generated during the use of this project within 24 hours. 3. The user shall be responsible for any direct, indirect, special, incidental, or consequential damages of any nature arising from the use of this project, including but not limited to damages caused by loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. 1. It is prohibited to use this project in violation of local laws and regulations. Any illegal or irregular behavior caused by the use of this project by users who are aware or unaware that local laws and regulations do not allow it shall be borne by the users, and this project shall not be held responsible for any direct, indirect, special, incidental or consequential liability arising therefrom. 2. Music platforms are not easy, please respect copyright and support genuine versions. 3. This project is only for the exploration and research of technical feasibility, and does not accept any commercial (including but not limited to advertising, etc.) cooperation or donations. If the official music platform finds this project inappropriate, they can contact this project to make changes or remove it. ## 📜 Open source license This project is for personal learning and research purposes only, and is prohibited from being used for commercial or illegal purposes. Based on [MIT license](https://opensource.org/licenses/MIT)License to open source. ## 👍 Inspiration source The API source code comes from [MakcRe/KuGouMusicApi](https://github.com/MakcRe/KuGouMusicApi ) - [Apple Music]( https://music.apple.com ) - [YouTube Music]( https://music.youtube.com ) - [YesPlayMusic]( https://github.com/qier222/YesPlayMusic ) - [Cool Dog Music](https://kugou.com/ ) ## 🖼️ screenshot ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/2.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/3.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/4.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/5.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/6.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/7.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/8.png) ## 🗓️ Star History [![Star History Chart](https://api.star-history.com/svg?repos=iAJue/MoeKoeMusic&type=Date)](https://www.star-history.com/#iAJue/MoeKoeMusic&Date) ================================================ FILE: docs/README_ja.md ================================================ > **注意**: この日本語ドキュメントはタイムリーに更新されない場合があります。最新の内容については[簡体字中国語版](https://github.com/iAJue/MoeKoeMusic/README.md)をご参照ください。

Logo

MoeKoe Music

オープンソースで簡潔で高ルックスのクールな犬のサードパーティクライアント
🌎 GitHub倉庫  |   📦️インストールパッケージのダウンロード  |   💬 ブログへのアクセス  |   🏠 プロジェクトホームページ

🇨🇳 简体中文  |   🇨🇳 繁体中文  |   🇯🇵 日本語  |   🇺🇸 English  |   🇰🇷 한국어  |   🇷🇺 Русский

![images](https://github.com/iAJue/MoeKoeMusic/raw/main/images/1.png) ### ❤️ はじめに 10年ほど前の様子では、Web版QQを使っている間に私はすでにクールな犬音楽を使い始めていたので(10年以上の古い粉でもある)、これらの年に所蔵されている曲はすべて上にあります。その後、私も網易雲やQQ音楽を使ってみたり、クールな犬の歌を導入してみたりしましたが、効果はあまりありませんでした。私が聴いているのはほとんど日漫OPで、多くの曲は見つけることができませんでした。 ぐるぐる回って結局クールドッグに戻るのですが、Mac側のクールドッグでは、時々再生できないことがあります。インタフェースはあまり機能していないとはいえ、いいですね。ネットユーザーのアンリの下で、私は今までクールな犬の[コンセプト版](https://t1.kugou.com/d2tBza3CSV2)で歌を聴いて、しかもVIP曲を無料で聴ける音楽再生ソフトは市販されていないので、力を入れてください。 私は私の個人紹介ページで、私は特に歌を聴くのが好きだと言っています。特に日漫OP.どうやって証明しますか。(以前は私のウェブ版の歌単も長年修理を怠っていました)では、自分で音楽プレーヤーを開発します。 ## ✨ プロパティ - ✅ Vue.jsファミリーバケツを用いた開発 - 🔴 クールドッグアカウント登録(スキャン/携帯/アカウント登録) - 📃 歌詞表示のサポート - 📻 毎日のおすすめ曲 - 🚫🤝 ソーシャル機能なし - 🔗 サードパーティ製APIなしの公式サーバ直結 - ✔️ VIPは毎日自動で受け取り、ログインするとVIPになります - 🎨 テーマカラー切り替え - 👋 開始の挨拶 - ⚙️ マルチプラットフォームサポート - 🛠 その他の機能開発中 ## 📢 Todo List - [x] 📺 MV再生をサポート - [x] 🌚 Light/Dark Mode 自動切り替え - [x] 👆 Touch Bar対応 - [x] 🖥️ PWA対応、Chrome/Edgeでアドレスバー右の➕ コンピュータにインストール - [ ] 🎧 Mprisのサポート - [x] ⌨️ ショートカットとグローバルショートカットのカスタマイズ - [x] 🤟 多言語サポート - [x] 📻 デスクトップ歌詞 - [x] ⚙️ システムアーキテクチャの最適化 - [x] 🎶 曲、歌/コレクション、キャンセル 更新ログは[Commits](https://github.com/iAJue/MoeKoeMusic/commits/main/) ## 📦️ インストール ### 1. クライアントのインストール 本プロジェクトの [Releases](https://github.com/iAJue/MoeKoeMusic/releases) ページにアクセスして、インストールパッケージをダウンロードしてください。 ### 2. WEB版のインストール(docker) * 注意:デプロイ後は、サーバーの対応ポートを開放する必要があります。または、リバースプロキシを使用してドメインアクセスを実現してください。 1. 方法一:クイックスタート(推奨) ``` git clone https://github.com/iAJue/MoeKoeMusic.git cd MoeKoeMusic git submodule update --init --recursive docker compose up -d & ``` 2. ~~方法二:docker-composeを使用したワンクリックインストール(イメージはまだ公式にアップロードされていません)~~ ``` docker run -d --name MoeKoeMusic -p 8080:8080 -p 6521:6521 -e PORT=6521 -e platform=lite iajue/moekoe-music:latest ``` 3. 方法三:宝塔コンテナ編成 * リモートイメージ、バージョンは公式より遅れる可能性があります。 ``` version: '3.3' services: moekoe-music: # イメージアドレス image: registry.cn-wulanchabu.aliyuncs.com/youngxj/moekoe-music:latest container_name: moekoe-music # コンテナ名 restart: unless-stopped # 自動再起動 build: context: . dockerfile: Dockerfile environment: - PORT=6521 - platform=lite ports: # ポートマッピング - "8080:8080" # フロントエンドサービス - "6521:6521" # APIサービス ``` 上記の内容をコピーして、宝塔パネルのコンテナ編成に貼り付け、編成名をMoeKoeMusicとして、デプロイをクリックしてください。 ### 3. ワンクリックデプロイ [![EdgeOne Pagesを使用してデプロイ](https://cdnstatic.tencentcs.com/edgeone/pages/deploy.svg)](https://edgeone.ai/pages/new?template=https://github.com/iAJue/moekoemusic&install-command=npm%20install&output-directory=dist&root-directory=.%2F&build-command=npm%20run%20build&env=VITE_APP_API_URL) 環境変数(VITE_APP_API_URL)に自分のAPIアドレスを入力する必要があります。 ## ⚙️ かいはつ 1.本倉庫のクローニング ```sh git clone --recurse-submodules https://github.com/iAJue/MoeKoeMusic.git ``` 2.ディレクトリにアクセスして依存関係をインストールする ```sh cd MoeKoeMusic npm run install-all ``` 3.開発者モデルの起動 ```sh npm run dev ``` 4.パッケージ項目 ```sh npm run build ``` 5.プロジェクトのコンパイル - Windows: ```sh npm run electron:build:win[デフォルトNSISインストールパッケージ] ``` - Linux: ```sh npm run electron:build:linux[デフォルトAppImageフォーマット] ``` - macOS: ```sh npm run electron:build:macos[デフォルトの双架構] ``` 詳細なコマンドは、「package.json」ファイル「scripts」を参照してください。 ## 👷‍♂️ クライアントのコンパイル Releaseページであなたに適したデバイスのインストールパッケージが見つからない場合は、次の手順に従って自分のクライアントをパッケージ化することができます。 1. [ノード.js](https://nodejs.org/en/)を選択し、` Node.js `バージョン>=18.0.0であることを確認します。 2. を使用する `git clonehttps://github.com/iAJue/MoeKoeMusic.git`この倉庫をローカルにクローニングします。 3. `npm install `を使用してプロジェクト依存性をインストールします。 4. APIサービス端末のコンパイル - Windows: ```sh npm run build:api:win ``` - Linux: ```sh npm run build:api:linux ``` - macOS: ```sh npm run build:api:macos ``` 5. 次のコマンドを選択して適切なインストールパッケージをパッケージ化し、パッケージ化されたファイルは`/dist _ electron `ディレクトリの下にあります。詳細については、[electron-builderドキュメント](https://www.electron.build/cli) #### 1. パッケージmacOSプラットフォーム - 汎用のmacOSパッケージ(IntelとApple Siliconデュアルアーキテクチャ): ``` npm run electron:build -- --mac --universal ``` - Intelアーキテクチャのみ: ``` npm run electron:build -- --mac --x64 ``` - Apple Siliconアーキテクチャのみ: ``` npm run electron:build -- --mac --arm64 ``` #### 2. Windowsプラットフォームのパッケージ化 - デフォルトNSISインストールパッケージ(ほとんどのWindowsユーザー向け): ``` npm run electron:build -- --win ``` - Windows用のEXEファイルとSquirrelインストールパッケージを作成するには: ``` npm run electron:build -- --win --ia32 --x64 --arm64 --target squirrel ``` ---ia 32は32ビットWindowsアーキテクチャです。 ---x 64は64ビットWindowsアーキテクチャです。 ---arm 64はARM Windowsアーキテクチャ(Surfaceなどのデバイス)です。 - Windows用にポータブルEXEファイルを生成する(インストール不要): ``` npm run electron:build -- --win --portable ``` #### 3. Linuxプラットフォームのパッケージ化 - デフォルトのAppImageフォーマット(ほとんどのLinuxリリース用): ``` npm run electron:build -- --linux ``` - snap(Ubuntuおよびsnapをサポートするリリース用): ``` npm run electron:build -- --linux --target snap ``` - deb(Debian/Ubuntuシリーズ用): ``` npm run electron:build -- --linux --target deb ``` - rpm(Red Hat/Fedoraシリーズ用): ``` npm run electron:build -- --linux --target rpm ``` #### 4. すべてのプラットフォームをパッケージ化 Windows、macOS、Linuxのインストールパッケージを同時に生成する必要がある場合は、次のコマンドを使用します。 ``` npm run electron:build -- -mwl ``` #### 5. コンパイル設定のカスタマイズ 必要に応じて他のオプションを追加して、パッケージをさらにカスタマイズすることができます。たとえば、x 64とarm 64スキーマを指定したり、異なるターゲットフォーマットを選択したりすることができます。 ## ⭐ プロジェクトをサポート このプロジェクトがお役に立った場合は、ぜひ星を付けてください!あなたのサポートが私たちの継続的な改善の原動力です。 [![GitHub stars](https://img.shields.io/github/stars/iAJue/MoeKoeMusic.svg?style=social&label=Star)](https://github.com/iAJue/MoeKoeMusic) ## ☑️ フィードバック 何か質問やアドバイスがあれば、issueまたはpull requestを提出してください。 ### ⚠️ 免責事項 0. 本プログラムはクールドッグの第三者クライアントであり、クールドッグの公式ではなく、より完全な機能が必要な場合は公式クライアント体験をダウンロードしてください。 1. 本プロジェクトは学習用にのみ使用されます。著作権を尊重し、このプロジェクトを利用して商業行為や不正な用途に従事しないでください。 2. 本プロジェクトの使用中に著作権データが生成される可能性があります。これらの著作権データに対して、本プロジェクトは所有権を持っていません。権利侵害を回避するために、使用者は、本プロジェクトを使用する過程で生成された著作権データを24時間以内に消去しなければならない。 3. 本事業の使用により生じた、本契約書の使用または使用不能によるいかなる性質を含む、直接的、間接的、特殊、偶発的または結果的な損害(名誉損失、操業停止、コンピュータ故障または故障による損害賠償、またはその他あらゆる商業的損害または損失を含むがこれらに限定されない)は、使用者が責任を負う。 1. 現地の法律法規に違反した場合の本事業の使用を禁止する。使用者が現地の法律法規で許可されていないことを知っているか知らないかのうちに本プロジェクトを使用したことによるいかなる違法行為も使用者が負担し、本プロジェクトはこれによる直接、間接、特殊、偶然、または結果的な責任を負わない。 2. 音楽プラットフォームは容易ではありません。著作権を尊重し、正規版をサポートしてください。 3. 本プロジェクトは技術の実現可能性の探索と研究にのみ使用され、いかなる商業(広告などを含むがこれに限らない)の協力と寄付を受けない。 4. 公式音楽プラットフォームが本プロジェクトに不具合があると感じた場合は、本プロジェクトに連絡して変更または削除することができます。 ## 📜 オープンソースライセンス 本プロジェクトは個人学習研究用にのみ使用され、商業用及び不法用途に使用することは禁止されている。 [MIT license](https://opensource.org/licenses/MIT)オープンソースを許可する。 ### 👍 インスピレーションソース APIソースコードは[MakcRe/KuGouMusicApi](https://github.com/MakcRe/KuGouMusicApi) - [Apple Music](https://music.apple.com) - [YouTube Music](https://music.youtube.com) - [YesPlayMusic](https://github.com/qier222/YesPlayMusic) -[クールドッグミュージック](https://kugou.com/) ## 🖼️ スクリーンショット ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/2.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/3.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/4.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/5.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/6.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/7.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/8.png) ## 🗓️ スター履歴 [![Star History Chart](https://api.star-history.com/svg?repos=iAJue/MoeKoeMusic&type=Date)](https://www.star-history.com/#iAJue/MoeKoeMusic&Date) ================================================ FILE: docs/README_ko.md ================================================ > **주의**: 이 한국어 문서는 업데이트가 지연될 수 있으니, 최신 내용은 [중국어 간체 버전](https://github.com/iAJue/MoeKoeMusic/README.md)을 참고하세요.

Logo

MoeKoe Music

오픈 소스 간결하고 용모가 높은 쿨도그 제3자 클라이언트
🌎 GitHub창고  |   📦️ 설치 패키지 다운로드   |   💬 블로그 방문   |   🏠 프로젝트 홈페이지

🇨🇳 简体中文  |   🇨🇳 繁体中文  |   🇯🇵 日本語  |   🇺🇸 English  |   🇰🇷 한국어  |   🇷🇺 Русский

![images](https://github.com/iAJue/MoeKoeMusic/raw/main/images/1.png) ## ❤️ 머리말 일찍이 10년 전후의 모습, 그것은 웹 페이지 QQ를 사용할 때 나는 이미 쿠거우 음악을 사용하기 시작했다 (또한 10여 년의 오랜 팬이다). 그래서 요 몇 년 동안 소장한 노래는 모두 위에 있다.후에 나도 왕이윈이나 QQ음악을 사용하기 시작했고 쿠거우의 노래 리스트를 도입하려고 시도했지만 효과가 모두 만족스럽지 못했다.내가 들은 것은 대부분 일본 만화 OP이다. 많은 노래를 찾을 수 없다. 빙빙 돌다가 결국 쿠거우로 돌아간다. 그러나 Mac에 있는 쿠거우는 종종 재생할 수 없는 상황이 나타날 수 있다. 비록 인터페이스는 아무런 기능이 없지만 아주 좋다.네티즌의 안리하에 나는 지금 줄곧 쿨개의 [개념판](https://t1.kugou.com/d2tBza3CSV2) 에서 노래를 듣고, 또한 시중에서 VIP 노래를 무료로 들을 수 있는 몇 안 되는 음악 재생 소프트웨어입니다. 나는 나의 개인 소개 페이지에서 내가 특히 노래 듣는 것을 좋아한다고 말했다. 특히 일본 만화 OP.어떻게 증명하죠?(이전에 내 웹페이지 플레이리스트도 오랫동안 수리를 하지 않았다.) 그럼 스스로 음악 플레이어를 하나 개발해라. ## ✨ 특징 - ✅ Vue.js 패밀리 버킷으로 개발 - 🔴 쿠거우 계정 로그인 (코드/핸드폰/계정 로그인) - 📃 가사 표시 지원 - 📻 매일 추천곡 - 🚫🤝 소셜 기능 없음 - 🔗 공식 서버 직접 연결, 타사 API 없음 - ✔️ 매일 VIP 자동 수령, 로그인하면 VIP - 🎨 테마 색상 전환 - 👋 시작 인사말 - ⚙️ 다중 플랫폼 지원 - 🛠 더 많은 기능 개발 중 ## 📢 Todo List - [x] 📺 뮤직비디오 재생 지원 - [x] 🌚 Light/Dark Mode 자동 전환 - [x] 👆 Touch Bar 지원 - [x] 🖥️ PWA 지원, Chrome/Edge에서 주소 표시줄 오른쪽에 있는➕ 컴퓨터에 설치 - [ ] 🎧 지원 Mpris - [x] ⌨️ 단축키 및 전역 단축키 사용자 정의 - [x] 🤟 다국어 지원 - [x] 📻 데스크톱 가사 - [x] ⚙️ 시스템 아키텍처 최적화 - [x] 🎶 노래, 트랙 리스트 / 모음, 취소 로그를 업데이트하려면 [Commits](https://github.com/iAJue/MoeKoeMusic/commits/main/) ## 📦️ 설치 ### 1. 클라이언트 설치 본 프로젝트의 [Releases](https://github.com/iAJue/MoeKoeMusic/releases) 페이지를 방문하여 설치 패키지를 다운로드하세요. ### 2. WEB 설치 (docker) * 주의: 배포 후 서버의 해당 포트를 개방해야 사용할 수 있습니다. 또는 역방향 프록시를 사용하여 도메인 접근을 구현할 수 있습니다. 1. 방법 1: 빠른 시작 (추천) ``` git clone https://github.com/iAJue/MoeKoeMusic.git cd MoeKoeMusic git submodule update --init --recursive docker compose up -d & ``` 2. ~~방법 2: docker-compose를 사용한 원클릭 설치 (이미지 미업로드 중)~~ ``` docker run -d --name MoeKoeMusic -p 8080:8080 -p 6521:6521 -e PORT=6521 -e platform=lite iajue/moekoe-music:latest ``` 3. 방법 3: 바오타 컨테이너 오케스트레이션 * 원격 이미지, 버전이 공식보다 뒤떨어질 수 있음 ``` version: '3.3' services: moekoe-music: # 이미지 주소 image: registry.cn-wulanchabu.aliyuncs.com/youngxj/moekoe-music:latest container_name: moekoe-music # 컨테이너명 restart: unless-stopped # 자동 재시작 build: context: . dockerfile: Dockerfile environment: - PORT=6521 - platform=lite ports: # 포트 매핑 - "8080:8080" # 프론트엔드 서비스 - "6521:6521" # API 서비스 ``` 위의 내용을 복사하여 바오타 패널의 컨테이너 오케스트레이션에 붙여넣고, 오케스트레이션 이름을 MoeKoeMusic으로 설정한 후 배포를 클릭하면 됩니다. ### 3. 원클릭 배포 [![使用 EdgeOne Pages 部署](https://cdnstatic.tencentcs.com/edgeone/pages/deploy.svg)](https://edgeone.ai/pages/new?template=https://github.com/iAJue/moekoemusic&install-command=npm%20install&output-directory=dist&root-directory=.%2F&build-command=npm%20run%20build&env=VITE_APP_API_URL) 환경 변수 VITE_APP_API_URL에 본인의 API 주소를 입력해야 합니다. ## ⚙️ 개발 1. 본 창고 클론 ```sh git clone --recurse-submodules https://github.com/iAJue/MoeKoeMusic.git ``` 2. 디렉터리에 들어가서 종속성 설치 ```sh cd MoeKoeMusic npm run install-all ``` 3. 개발자 모드 시작 ```sh npm run dev ``` 4. 프로젝트 패키지 ```sh npm run build ``` 5. 항목 컴파일 - Windows: ```sh npm run electron:build:win [기본 NSIS 설치 패키지] ``` - Linux: ```sh npm run electron:build:linux [기본 AppImage 형식] ``` - macOS: ```sh npm run electron:build:macos [기본 듀얼 아키텍처] ``` 더 많은 명령은 `package.json` 파일 `scripts`를 참조하십시오. ## 👷‍♂️ 클라이언트 컴파일 Release 페이지에서 장치에 맞는 설치 패키지를 찾지 못하면 다음 단계에 따라 클라이언트를 포장할 수 있습니다. 1. 설치[Node.js](https://nodejs.org/en/) 및 `Node.js` 버전이 > = 18.0.0인지 확인합니다. 2. `git clone 사용https://github.com/iAJue/MoeKoeMusic.git'본 창고를 로컬로 복제합니다. 3. `npm install`을 사용하여 프로젝트 종속성을 설치합니다. 4. API 서버 컴파일 - Windows: ```sh npm run build:api:win ``` - Linux: ```sh npm run build:api:linux ``` - macOS: ```sh npm run build:api:macos ``` 1. 다음 명령을 선택하여 적합한 설치 패키지를 포장합니다. 포장된 파일은'/dist_electron'디렉터리에 있습니다.자세한 내용은 [electron-builder 문서](https://www.electron.build/cli) #### 1. macOS 플랫폼 패키지 - 범용 macOS 패키지(Intel 및 Apple Silicon 듀얼 아키텍처): ``` npm run electron:build -- --mac --universal ``` - Intel 아키텍처만: ``` npm run electron:build -- --mac --x64 ``` - Apple Silicon 아키텍처만: ``` npm run electron:build -- --mac --arm64 ``` #### 2. Windows 플랫폼 패키지 - 기본 NSIS 설치 패키지(대부분의 Windows 사용자용): ``` npm run electron:build -- --win ``` - Windows용 EXE 파일 및 Squirrel 설치 패키지를 만듭니다. ``` npm run electron:build -- --win --ia32 --x64 --arm64 --target squirrel ``` -- ia32는 32비트 Windows 아키텍처입니다. ---x64는 64비트 Windows 아키텍처입니다. -- arm64는 ARM Windows 아키텍처(Surface와 같은 장치)입니다. - Windows용 휴대용 EXE 파일(설치되지 않음) 생성: ``` npm run electron:build -- --win --portable ``` #### 3. Linux 플랫폼 패키지 - 기본 AppImage 형식(대부분의 Linux 배포용): ``` npm run electron:build -- --linux ``` - snap(Ubuntu 및 snap 지원 릴리스용): ``` npm run electron:build -- --linux --target snap ``` - deb(Debian/Ubuntu 시리즈용): ``` npm run electron:build -- --linux --target deb ``` - rpm(Red Hat/Fedora 시리즈용): ``` npm run electron:build -- --linux --target rpm ``` #### 4. 모든 플랫폼 패키지 Windows, macOS 및 Linux를 모두 생성하는 설치 패키지가 필요한 경우 다음 명령을 사용할 수 있습니다. ``` npm run electron:build -- -mwl ``` #### 5. 컴파일 설정 사용자 정의 x64 및 arm64 스키마를 지정하거나 다른 대상 형식을 선택하는 등의 추가 옵션을 추가하여 패키지를 추가로 사용자 지정할 수 있습니다. ## ⭐ 프로젝트 지원 이 프로젝트가 도움이 되었다면 별을 눌러주세요! 여러분의 지원이 저희가 계속 개선할 수 있는 원동력입니다. [![GitHub stars](https://img.shields.io/github/stars/iAJue/MoeKoeMusic.svg?style=social&label=Star)](https://github.com/iAJue/MoeKoeMusic) ## ☑️ 피드백 질문이나 제안이 있으면 issue 또는 pull request를 제출하십시오. ## ⚠️ 면책 조항 0. 본 프로그램은 쿠거우 제3자 클라이언트입니다. 쿠거우 공식이 아닙니다. 더 완벽한 기능이 필요하시면 공식 클라이언트 체험을 다운로드하십시오. 1.본 프로젝트는 학습용입니다.저작권을 존중하고 이 프로젝트를 상업행위 및 불법용도로 이용하지 마십시오! 2. 본 프로젝트를 사용하는 과정에서 저작권 데이터가 발생할 수 있습니다.본 프로젝트는 이러한 저작권 데이터에 대한 소유권이 없습니다.저작권 침해를 방지하기 위해 사용자는 본 프로젝트를 사용하는 과정에서 발생하는 저작권 데이터를 24시간 이내에 삭제해야 합니다. 3. 본 프로젝트의 사용으로 인해 발생하는 본 계약 또는 본 프로젝트의 사용 또는 사용 불가능으로 인해 발생하는 모든 성격을 포함하는 직접, 간접, 특수, 우연 또는 결과적 손해(상업권 손실, 업무 정지, 컴퓨터 고장 또는 고장으로 인한 손해 배상 또는 기타 모든 상업적 손해 또는 손실을 포함하되 이에 국한되지 않음)는 사용자의 책임이다. 1. 현지 법률과 법규를 위반한 경우 본 프로젝트의 사용을 금지한다.사용자가 현지 법률과 법규가 허용하지 않는 것을 뻔히 알거나 모르는 상황에서 본 프로젝트를 사용하여 초래된 어떠한 위법 행위도 사용자가 부담하고 본 프로젝트는 이로 인해 초래된 어떠한 직접, 간접, 특수, 우연 또는 결과적 책임을 지지 않는다. 2. 음악 플랫폼은 쉽지 않습니다.저작권을 존중하고 정품을 지원하십시오. 3. 본 프로젝트는 기술적 타당성에 대한 탐구 및 연구에만 사용되며 어떠한 상업(광고 등을 포함하되 이에 국한되지 않음) 합작과 기부도 받지 않습니다. 4.공식 음악 플랫폼이 본 프로젝트가 부적절하다고 생각되면 본 프로젝트에 연락하여 변경하거나 제거할 수 있습니다. ## 📜 오픈 소스 라이센스 본 프로젝트는 개인의 학습 연구에만 사용되며 상업 및 불법 용도로 사용되는 것을 금지합니다. 기반 [MIT license](https://opensource.org/licenses/MIT) 오픈 소스로 라이센스를 부여합니다. ## 👍 영감의 원천 API 소스 코드는 [MakcRe/KuGouMusicApi](https://github.com/MakcRe/KuGouMusicApi) - [Apple Music](https://music.apple.com) - [YouTube Music](https://music.youtube.com) - [YesPlayMusic](https://github.com/qier222/YesPlayMusic) - [쿨도그 뮤직](https://kugou.com/) ## 🖼️ 캡처 ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/2.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/3.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/4.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/5.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/6.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/7.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/8.png) ## 🗓️ 스타 히스토리 [![Star History Chart](https://api.star-history.com/svg?repos=iAJue/MoeKoeMusic&type=Date)](https://www.star-history.com/#iAJue/MoeKoeMusic&Date) ================================================ FILE: docs/README_ru.md ================================================ > **Note**: Этот документ на русском языке может не быть актуальным вовремя. Для актуального материала, пожалуйста, обратитесь к [версии на упрощенном китайском](https://github.com/iAJue/MoeKoeMusic/README.md).

Logo

MoeKoe Music

Открытый, лаконичный и красивый сторонний клиент для Kugou
GitHub  |   Скачать  |   Блог  |   Сайт проекта

简体中文  |   繁体中文  |   日本語  |   English  |   한국어  |   Русский

![images](https://github.com/iAJue/MoeKoeMusic/raw/main/images/1.png) ## ❤️ Предисловие Ещё около 10 лет назад, когда я пользовался веб-версией QQ, я начал использовать Kugou Music (уже более десяти лет как фанат). За эти годы вся моя коллекция песен оказалась именно там. Позже я пытался перейти на NetEase Cloud или QQ Music и даже импортировать плейлисты из Kugou, но результаты были неудовлетворительными. Я в основном слушаю опенинги аниме, и многие треки просто невозможно найти на других платформах. В итоге я вернулся к Kugou. Однако на Mac-версии Kugou периодически возникали проблемы с воспроизведением. По рекомендации друзей я теперь слушаю музыку через [концептуальную версию](https://t1.kugou.com/d2tBza3CSV2) Kugou — это одно из немногих приложений, где можно бесплатно слушать VIP-треки. Очень рекомендую. На своей странице я писал, что очень люблю слушать музыку, особенно опенинги аниме. Как это доказать? Просто разработать собственный музыкальный плеер. ## ✨ Особенности - ✅ Разработан на Vue.js - 🔴 Авторизация через аккаунт Kugou (QR-код/телефон/логин) - 📃 Отображение текстов песен - 📻 Ежедневные рекомендации - 🚫🤝 Никаких социальных функций - 🔗 Прямое подключение к официальным серверам, без сторонних API - ✔️ Автоматическое получение VIP ежедневно - 🎨 Переключение цветовых тем - 👋 Приветствие при запуске - ⚙️ Поддержка нескольких платформ - 🛠 Больше функций в разработке ## 📢 Список задач - [x] 📺 Поддержка воспроизведения MV - [x] 🌚 Автоматическое переключение Light/Dark Mode - [x] 👆 Поддержка Touch Bar - [x] 🖥️ Поддержка PWA — можно установить через Chrome/Edge, нажав ➕ в адресной строке - [ ] 🎧 Поддержка Mpris - [x] ⌨️ Глобальные горячие клавиши - [x] 🤟 Мультиязычность - [x] 📻 Текст песни на рабочем столе - [x] ⚙️ Оптимизация архитектуры - [x] 🎶 Добавление/удаление песен и плейлистов в избранное История изменений: [Commits](https://github.com/iAJue/MoeKoeMusic/commits/main/) ## 📦️ Установка ### 1. Установка клиента Скачайте установочный пакет на странице [Releases](https://github.com/iAJue/MoeKoeMusic/releases). ### 2. WEB-версия (Docker) * Примечание: после развёртывания откройте соответствующий порт на сервере или используйте обратный прокси. 1. Способ 1: Быстрый запуск (рекомендуется) ``` git clone https://github.com/iAJue/MoeKoeMusic.git cd MoeKoeMusic git submodule update --init --recursive docker compose up -d & ``` 2. ~~Способ 2: docker-compose (образ пока не загружен официально)~~ ``` docker run -d --name MoeKoeMusic -p 8080:8080 -p 6521:6521 -e PORT=6521 -e platform=lite iajue/moekoe-music:latest ``` 3. Способ 3: Через панель управления * Удалённый образ, версия может отставать от официальной ``` version: '3.3' services: moekoe-music: image: registry.cn-wulanchabu.aliyuncs.com/youngxj/moekoe-music:latest container_name: moekoe-music restart: unless-stopped build: context: . dockerfile: Dockerfile environment: - PORT=6521 - platform=lite ports: - "8080:8080" # Фронтенд - "6521:6521" # API ``` ### 3. Развертывание в один клик [![Развернуть на EdgeOne Pages](https://cdnstatic.tencentcs.com/edgeone/pages/deploy.svg)](https://edgeone.ai/pages/new?template=https://github.com/iAJue/moekoemusic&install-command=npm%20install&output-directory=dist&root-directory=.%2F&build-command=npm%20run%20build&env=VITE_APP_API_URL) Укажите свой API-адрес в переменной окружения (VITE_APP_API_URL) ## ⚙️ Разработка 1. Клонируйте репозиторий ```sh git clone --recurse-submodules https://github.com/iAJue/MoeKoeMusic.git ``` 2. Перейдите в директорию и установите зависимости ```sh cd MoeKoeMusic npm run install-all ``` 3. Запустите режим разработки ```sh npm run dev ``` 4. Сборка проекта ```sh npm run build ``` 5. Компиляция - Windows: ```sh npm run electron:build:win ``` - Linux: ```sh npm run electron:build:linux ``` - macOS: ```sh npm run electron:build:macos ``` Больше команд в файле `package.json` в секции `scripts` ## 👷‍♂️ Сборка клиента Если на странице Release нет подходящего пакета для вашего устройства, вы можете собрать клиент самостоятельно. 1. Установите [Node.js](https://nodejs.org/en/) версии >= 18.0.0. 2. Клонируйте репозиторий: `git clone https://github.com/iAJue/MoeKoeMusic.git` 3. Установите зависимости: `npm install` 4. Скомпилируйте API-сервер - Windows: ```sh npm run build:api:win ``` - Linux: ```sh npm run build:api:linux ``` - macOS: ```sh npm run build:api:macos ``` 5. Выберите команду для сборки. Результат будет в директории `/dist_electron`. Подробнее: [electron-builder](https://www.electron.build/cli) #### 1. macOS - Универсальный пакет (Intel и Apple Silicon): ``` npm run electron:build -- --mac --universal ``` - Только Intel: ``` npm run electron:build -- --mac --x64 ``` - Только Apple Silicon: ``` npm run electron:build -- --mac --arm64 ``` #### 2. Windows - NSIS-установщик (для большинства пользователей): ``` npm run electron:build -- --win ``` - EXE + Squirrel: ``` npm run electron:build -- --win --ia32 --x64 --arm64 --target squirrel ``` - Портативная версия (без установки): ``` npm run electron:build -- --win --portable ``` #### 3. Linux - AppImage (для большинства дистрибутивов): ``` npm run electron:build -- --linux ``` - snap (Ubuntu и совместимые): ``` npm run electron:build -- --linux --target snap ``` - deb (Debian/Ubuntu): ``` npm run electron:build -- --linux --target deb ``` - rpm (Red Hat/Fedora): ``` npm run electron:build -- --linux --target rpm ``` - ARM64: ``` npm run build:api:linux-aarch64 npm run electron:build:linux-aarch64 ``` #### 4. Все платформы сразу ``` npm run electron:build -- -mwl ``` #### 5. Дополнительные настройки Вы можете добавить другие опции для настройки сборки, например, указать архитектуру x64 или arm64. ## ⭐ Поддержать проект Если проект оказался полезным, поставьте Star! Ваша поддержка мотивирует нас продолжать развитие. [![GitHub stars](https://img.shields.io/github/stars/iAJue/MoeKoeMusic.svg?style=social&label=Star)](https://github.com/iAJue/MoeKoeMusic) ## ✅ Обратная связь По любым вопросам или предложениям создавайте issue или pull request. ## ⚠️ Отказ от ответственности 0. Это сторонний клиент для Kugou, не официальное приложение. Для полного функционала используйте официальный клиент. 1. Проект предназначен только для обучения. Уважайте авторские права и не используйте его в коммерческих или незаконных целях! 2. При использовании проекта могут создаваться данные, защищённые авторским правом. Проект не владеет этими данными. Во избежание нарушения авторских прав удаляйте такие данные в течение 24 часов. 3. Пользователь несёт ответственность за любой ущерб, возникший в результате использования проекта. 4. Запрещено использовать проект в нарушение местного законодательства. 5. Музыкальные платформы заслуживают поддержки. Уважайте авторские права, поддерживайте легальный контент. 6. Проект предназначен только для исследования технических возможностей. Коммерческое сотрудничество и пожертвования не принимаются. 7. Если правообладатели считают проект неуместным, свяжитесь с нами для изменения или удаления. ## 📜 Лицензия Проект предназначен только для личного изучения. Коммерческое и незаконное использование запрещено. Лицензия: [GNU General Public License v2.0 (GPL-2.0)](https://github.com/iAJue/MoeKoeMusic/blob/main/LICENSE) ## 👍 Источники вдохновения Исходный код API: [MakcRe/KuGouMusicApi](https://github.com/MakcRe/KuGouMusicApi) - [Apple Music](https://music.apple.com) - [YouTube Music](https://music.youtube.com) - [YesPlayMusic](https://github.com/qier222/YesPlayMusic) - [Kugou Music](https://kugou.com/) ## 🖼️ Скриншоты ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/2.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/3.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/4.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/5.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/6.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/7.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/8.png) ## 🗓️ Star History [![Star History Chart](https://api.star-history.com/svg?repos=iAJue/MoeKoeMusic&type=Date)](https://www.star-history.com/#iAJue/MoeKoeMusic&Date) ================================================ FILE: docs/README_tw.md ================================================ > **注意**: 此繁體中文文檔可能更新不及時,最新內容請參考[簡體中文版本](https://github.com/iAJue/MoeKoeMusic/README.md)。

Logo

MoeKoe Music

一款開源簡潔高顏值的酷狗協力廠商用戶端
🌎 GitHub 倉庫  |   📦️ 下載安裝包  |   💬 訪問部落格  |   🏠 項目主頁

🇨🇳 简体中文  |   🇨🇳 繁体中文  |   🇯🇵 日本語  |   🇺🇸 English  |   🇰🇷 한국어  |   🇷🇺 Русский

![images](https://github.com/iAJue/MoeKoeMusic/raw/main/images/1.png) ## ❤️ 前言 早在10年前後的樣子,那會在用網頁版QQ的時候我就已經開始使用酷狗音樂了(也是十來年的老粉了),所以這些年收藏的歌曲全部都在上面. 後來我也嘗試開始使用網易雲或QQ音樂,也嘗試把酷狗的歌單導入進去,但是效果都不盡人意. 我聽的大多是日漫OP,好多歌曲都沒辦法找到. 兜兜轉轉最後還是回到酷狗,但是在Mac端的酷狗,時常可能會出現不能播放的情况,雖說介面沒什麼功能,但也挺好的. 在網友的安利下,我現在一直是在酷狗的[概念版](https://t1.kugou.com/d2tBza3CSV2)上聽歌,並且是市面上為數不多能免費聽VIP歌曲的音樂播放軟體了,力推. 我在我的個人介紹頁面說我特別喜歡聽歌,尤其是日漫OP. 怎麼證明呢? (之前我網頁版歌單也年久失修了)那就自己開發一個音樂播放機. ## ✨ 特性 - ✅ 使用Vue.js全家桶開發 - 🔴 酷狗帳號登入(掃碼/手機/帳號登入) - 📃 支持歌詞顯示 - 📻 每日推薦歌曲 - 🚫🤝 無任何社交功能 - 🔗 官方服務器直連,無任何協力廠商API - ✔️ 每日自動領取VIP,登入就是VIP - 🎨 主題色切換 - 👋 啟動問候語 - ⚙️ 多平臺支持 - 🛠 更多特性開發中 ## 📢 Todo List - [x] 📺 支持MV播放 - [x] 🌚 Light/Dark Mode 自動切換 - [x] 👆 支持Touch Bar - [x] 🖥️ 支持PWA,可在Chrome/Edge裏點擊地址欄右邊的 ➕ 安裝到電腦 - [ ] 🎧 支持Mpris - [x] ⌨️ 全域快速鍵 - [x] 🤟 多語言支持 - [x] 📻 案頭歌詞 - [x] ⚙️ 系統架構優化 - [x] 🎶 歌曲、歌單/收藏、取消 更新日誌請查看[Commits](https://github.com/iAJue/MoeKoeMusic/commits/main/) ## 📦️ 安裝 ### 1. 用戶端安裝 訪問本項目的 [Releases](https://github.com/iAJue/MoeKoeMusic/releases) 頁面下載安裝包。 ### 2. WEB端安裝(docker) * 注意:部署後請開放伺服器對應埠才可使用,或者使用反向代理實現功能變數名稱訪問。 1. 方式一:快速啟動(推薦) ``` git clone https://github.com/iAJue/MoeKoeMusic.git cd MoeKoeMusic git submodule update --init --recursive docker compose up -d & ``` 2. ~~方式二:使用docker-compose一鍵安裝 (映像暫未上傳官方)~~ ``` docker run -d --name MoeKoeMusic -p 8080:8080 -p 6521:6521 -e PORT=6521 -e platform=lite iajue/moekoe-music:latest ``` 3. 方式三:寶塔容器編排 * 遠端映像,版本可能會落後於官方 ``` version: '3.3' services: moekoe-music: # 映像地址 image: registry.cn-wulanchabu.aliyuncs.com/youngxj/moekoe-music:latest container_name: moekoe-music # 容器名 restart: unless-stopped # 自動重啟 build: context: . dockerfile: Dockerfile environment: - PORT=6521 - platform=lite ports: # 埠映射 - "8080:8080" # 前端服務 - "6521:6521" # 介面服務 ``` 複製上面的內容,貼上到寶塔面板的容器編排裡面,編排名稱為MoeKoeMusic,點擊部署即可。 ### 3. 一鍵部署 [![使用 EdgeOne Pages 部署](https://cdnstatic.tencentcs.com/edgeone/pages/deploy.svg)](https://edgeone.ai/pages/new?template=https://github.com/iAJue/moekoemusic&install-command=npm%20install&output-directory=dist&root-directory=.%2F&build-command=npm%20run%20build&env=VITE_APP_API_URL) 需在環境變數(VITE_APP_API_URL)中填寫自己的API地址 ## ⚙️ 開發 1.尅隆本倉庫 ```sh git clone --recurse-submodules https://github.com/iAJue/MoeKoeMusic.git ``` 2.進入目錄並安裝依賴 ```sh cd MoeKoeMusic npm run install-all ``` 3.啟動開發者模式 ```sh npm run dev ``` 4.打包項目 ```sh npm run build ``` 5.編譯項目 - Windows: ```sh npm run electron:build:win [默認NSIS安裝包] ``` - Linux: ```sh npm run electron:build:linux [默認AppImage格式] ``` - macOS: ```sh npm run electron:build:macos [默認雙架構] ``` 更多命令請查看`package.json`檔案`scripts` ## 👷‍♂️ 編譯用戶端 如果在Release頁面沒有找到適合你的設備的安裝包的話,你可以根據下麵的步驟來打包自己的用戶端。 1.安裝[Node.js](https://nodejs.org/en/),並確保`Node.js`版本>= 18.0.0。 2.使用`git clone https://github.com/iAJue/MoeKoeMusic.git `尅隆本倉庫到本地。 3.使用`npm install`安裝項目依賴。 4.編譯API服務端 - Windows: ```sh npm run build:api:win ``` - Linux: ```sh npm run build:api:linux ``` - macOS: ```sh npm run build:api:macos ``` 5.選擇下列的命令來打包適合的你的安裝包,打包出來的檔案在`/dist_electron`目錄下。 瞭解更多資訊可訪問[electron-builder檔案](https://www.electron.build/cli) #### 1. 打包macOS平臺 -通用的macOS包(Intel和Apple Silicon雙架構): ``` npm run electron:build -- --mac --universal ``` -僅Intel架構: ``` npm run electron:build -- --mac --x64 ``` -僅Apple Silicon架構: ``` npm run electron:build -- --mac --arm64 ``` #### 2. 打包Windows平臺 -默認NSIS安裝包(適合大多數Windows用戶): ``` npm run electron:build -- --win ``` -為Windows創建EXE檔案和Squirrel安裝包: ``` npm run electron:build -- --win --ia32 --x64 --arm64 --target squirrel ``` ---ia32為32比特Windows架構。 ---x64為64比特Windows架構。 ---arm64為ARM Windows架構(Surface等設備)。 -為Windows生成可擕式的EXE檔案(免安裝): ``` npm run electron:build -- --win --portable ``` #### 3. 打包Linux平臺 -默認AppImage格式(適用於大多數Linux發行版本): ``` npm run electron:build -- --linux ``` - snap(適用於Ubuntu和支持snap的發行版本): ``` npm run electron:build -- --linux --target snap ``` - deb(適用於Debian/Ubuntu系列): ``` npm run electron:build -- --linux --target deb ``` - rpm(適用於Red Hat/Fedora系列): ``` npm run electron:build -- --linux --target rpm ``` #### 4. 打包所有平臺 如果需要同時生成Windows、macOS和Linux的安裝包,可以使用以下命令: ``` npm run electron:build -- -mwl ``` #### 5. 自定義編譯設定 您可以根據需要添加其他選項來進一步自定義打包,例如指定x64和arm64架構,或選擇不同的目標格式。 ## ⭐ 支持項目 如果您覺得這個項目對您有幫助,歡迎給我們一個 Star!您的支持是我們持續改進的動力。 [![GitHub stars](https://img.shields.io/github/stars/iAJue/MoeKoeMusic.svg?style=social&label=Star)](https://github.com/iAJue/MoeKoeMusic) ## ☑️ 反饋 如有任何問題或建議,歡迎提交issue或pull request。 ## ⚠️ 免責聲明 0. 本程式是酷狗協力廠商用戶端,並非酷狗官方,需要更完善的功能請下載官方用戶端體驗. 1. 本項目僅供學習使用,請尊重版權,請勿利用此項目從事商業行為及非法用途! 2. 使用本項目的過程中可能會產生版權數據。 對於這些版權數據,本項目不擁有它們的所有權。 為了避免侵權,使用者務必在24小時內清除使用本項目的過程中所產生的版權數據。 3. 由於使用本項目產生的包括由於本協定或由於使用或無法使用本項目而引起的任何性質的任何直接、間接、特殊、偶然或結果性損害(包括但不限於因商譽損失、停工、電腦故障或故障引起的損害賠償,或任何及所有其他商業損害或損失)由使用者負責。 1. 禁止在違反當地法律法規的情况下使用本項目。 對於使用者在明知或不知當地法律法規不允許的情况下使用本項目所造成的任何違法違規行為由使用者承擔,本項目不承擔由此造成的任何直接、間接、特殊、偶然或結果性責任。 2. 音樂平臺不易,請尊重版權,支持正版。 3. 本項目僅用於對科技可行性的探索及研究,不接受任何商業(包括但不限於廣告等)合作及捐贈。 4. 如果官方音樂平臺覺得本項目不妥,可聯系本項目更改或移除。 ## 📜 開源許可 本項目僅供個人學習研究使用,禁止用於商業及非法用途。 基於[MIT license](https://opensource.org/licenses/MIT)許可進行開源。 ## 👍 靈感來源 API原始程式碼來自[MakcRe/KuGouMusicApi](https://github.com/MakcRe/KuGouMusicApi) - [Apple Music](https://music.apple.com) - [YouTube Music](https://music.youtube.com) - [YesPlayMusic](https://github.com/qier222/YesPlayMusic) - [酷狗音樂](https://kugou.com/) ## 🖼️ 截圖 ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/2.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/3.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/4.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/5.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/6.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/7.png) ![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/8.png) ## 🗓️ Star 歷史 [![Star History Chart](https://api.star-history.com/svg?repos=iAJue/MoeKoeMusic&type=Date)](https://www.star-history.com/#iAJue/MoeKoeMusic&Date) ================================================ FILE: electron/appServices.js ================================================ import { app, ipcMain, BrowserWindow, screen, Tray, Menu, TouchBar, globalShortcut, dialog, shell, nativeImage } from 'electron'; import path from 'path'; import { spawn } from 'child_process'; import log from 'electron-log'; import Store from 'electron-store'; import { fileURLToPath } from 'url'; import isDev from 'electron-is-dev'; import fs from 'fs'; import { exec } from 'child_process'; import { checkForUpdates } from './services/updater.js'; import { Notification } from 'electron'; import extensionManager from './extensions/extensionManager.js'; import { t } from './language/i18n.js'; import { bindExternalLinkHandler } from './services/externalLinkHandler.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const store = new Store(); const { TouchBarLabel, TouchBarButton, TouchBarGroup, TouchBarSpacer } = TouchBar; let mainWindow = null; let apiProcess = null; let tray = null; // 创建主窗口 export function createWindow() { const savedConfig = store.get('settings'); const useNativeTitleBar = savedConfig?.nativeTitleBar === 'on' ? true : false; const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize; const windowWidth = Math.min(1200, screenWidth * 0.8); const windowHeight = Math.min(938, screenHeight * 0.9); const lastWindowState = store.get('windowState') || {}; let x = lastWindowState.x; let y = lastWindowState.y; let width = lastWindowState.width || windowWidth; let height = lastWindowState.height || windowHeight; width = Math.min(width, screenWidth); height = Math.min(height, screenHeight); const isValidPosition = x !== undefined && y !== undefined && x >= 0 && x <= screenWidth && y >= 0 && y <= screenHeight; if (!isValidPosition) { x = Math.floor((screenWidth - width) / 2); y = Math.floor((screenHeight - height) / 2); } mainWindow = new BrowserWindow({ width: width, height: height, x: x, y: y, minWidth: 890, minHeight: 750, show: savedConfig?.startMinimized === 'on' ? false : true, frame: useNativeTitleBar, titleBarStyle: useNativeTitleBar ? 'default' : 'hiddenInset', autoHideMenuBar: true, webPreferences: { preload: path.join(__dirname, 'preload.cjs'), contextIsolation: true, nodeIntegration: false, sandbox: false, webSecurity: false, // 禁用 CORS、同源策略 allowRunningInsecureContent: true, // 允许混合内容 zoomFactor: 1.0 }, icon: getIconPath('icon.ico') }); bindExternalLinkHandler(mainWindow); if (store.get('maximize')) { mainWindow.maximize(); } if (isDev) { mainWindow.loadURL('http://localhost:8080'); mainWindow.webContents.openDevTools(); } else { if (savedConfig?.networkMode == 'devnet') { //开发网 mainWindow.loadURL('http://localhost:8080'); } else if (savedConfig?.networkMode == 'testnet') { //测试网 mainWindow.loadURL('https://app.testnet.music.moekoe.cn'); } else { //主网 mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); } } mainWindow.webContents.once('dom-ready', () => { extensionManager.loadChromeExtensions(); }); mainWindow.webContents.on('dom-ready', () => { console.log('DOM Ready'); }); mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { console.error('Failed to load:', errorCode, errorDescription); }); mainWindow.once('ready-to-show', () => { if (savedConfig?.startMinimized === 'on') { mainWindow.hide(); } }); mainWindow.webContents.on('did-finish-load', () => { console.log('Page Loaded Successfully'); mainWindow.webContents.insertCSS('::-webkit-scrollbar { display: none; }'); if (!store.get('disclaimerAccepted')) { mainWindow.webContents.send('show-disclaimer'); } mainWindow.webContents.send('version', app.getVersion()); }); mainWindow.on('close', (event) => { const savedConfig = store.get('settings'); if (savedConfig?.minimizeToTray === 'off') { app.isQuitting = true; app.quit(); } if (!app.isQuitting) { event.preventDefault(); mainWindow.hide(); } }); if (process.platform === 'win32') { setThumbarButtons(mainWindow); } if (savedConfig?.desktopLyrics === 'on') { createLyricsWindow(); } return mainWindow; } let lyricsWindow; export function createLyricsWindow() { const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize; const windowWidth = Math.floor(screenWidth * 0.7); const windowHeight = 200; const savedLyricsPosition = store.get('lyricsWindowPosition') || {}; const savedLyricsSize = store.get('lyricsWindowSize') || { width: windowWidth, height: windowHeight }; let x = savedLyricsPosition.x; let y = savedLyricsPosition.y; let width = savedLyricsSize.width || windowWidth; let height = savedLyricsSize.height || windowHeight; // 限制窗口尺寸不超过屏幕 width = Math.min(width, screenWidth); height = Math.min(height, screenHeight); // 检查位置是否有效 const isValidPosition = x !== undefined && y !== undefined && x >= 0 && x <= screenWidth && y >= 0 && y <= screenHeight; // 如果位置无效,设置默认位置 if (!isValidPosition) { x = Math.floor((screenWidth - width) / 2); y = screenHeight - height; } lyricsWindow = new BrowserWindow({ width: width, height: height, x: x, y: y, minWidth: 800, minHeight: 200, alwaysOnTop: true, frame: false, transparent: true, resizable: true, skipTaskbar: true, hasShadow: false, webPreferences: { preload: path.join(__dirname, 'preload.cjs'), contextIsolation: true, nodeIntegration: false, sandbox: false, webSecurity: false, // 禁用 CORS、同源策略 allowRunningInsecureContent: true, // 允许混合内容 backgroundThrottling: false, zoomFactor: 1.0 } }); lyricsWindow.on('resize', () => { const [width, height] = lyricsWindow.getSize(); store.set('lyricsWindowSize', { width, height }); }); mainWindow.lyricsWindow = lyricsWindow; lyricsWindow.on('closed', () => { mainWindow.lyricsWindow = null; }); if (isDev) { lyricsWindow.loadURL('http://localhost:8080/#/lyrics'); lyricsWindow.webContents.openDevTools({ mode: 'detach' }); } else { lyricsWindow.loadFile(path.join(__dirname, '../dist/index.html'), { hash: 'lyrics' }); } // 设置窗口置顶级别 lyricsWindow.setAlwaysOnTop(true, 'screen-saver'); lyricsWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); // 允许窗口透明 lyricsWindow.setBackgroundColor('#00000000'); } export function createMvWindow() { const { screenWidth, screenHeight } = screen.getPrimaryDisplay().workAreaSize; return new BrowserWindow({ width: Math.min(screenWidth * 0.8, 1280), height: Math.min(screenHeight * 0.8, 720), frame: false, transparent: true, show: false, titleBarStyle: 'hiddenInset', autoHideMenuBar: true, backgroundColor: '#00000000', webPreferences: { preload: path.join(__dirname, 'preload.cjs'), contextIsolation: true, nodeIntegration: false, sandbox: false, webSecurity: false, // 禁用 CORS、同源策略 allowRunningInsecureContent: true, // 允许混合内容 zoomFactor: 1.0, devTools: isDev }, icon: getIconPath('icon.ico') }); } const getIconPath = (iconName, subPath = '') => path.join( isDev ? __dirname + '/../build/icons' : process.resourcesPath + '/icons', subPath, iconName ); export function getTray() { return tray; } // 创建托盘图标及菜单 export function createTray(mainWindow, title = '') { if (tray && title) { tray.setToolTip(title); return tray; } let trayIconName if (process.platform === 'linux') { trayIconName = 'linux-icon.png' } else if (process.platform === 'darwin') { trayIconName = 'tray-icon.png' } else { trayIconName = 'tray-icon.ico' } tray = new Tray(getIconPath(trayIconName)); tray.setToolTip('MoeKoe Music'); const contextMenu = Menu.buildFromTemplate([ { label: t('project-home'), icon: getIconPath('home.png', 'menu'), click: () => { shell.openExternal('https://Music.MoeKoe.cn'); } }, { label: t('report-bug'), icon: getIconPath('bug.png', 'menu'), click: () => { shell.openExternal('https://github.com/iAJue/MoeKoeMusic/issues'); } }, { label: t('prev-track'), icon: getIconPath('prev.png', 'menu'), accelerator: 'Alt+CommandOrControl+Left', click: () => { mainWindow.webContents.send('play-previous-track'); } }, { label: t('pause'), accelerator: 'Alt+CommandOrControl+Space', icon: getIconPath('play.png', 'menu'), click: () => { mainWindow.webContents.send('toggle-play-pause'); } }, { label: t('next-track'), accelerator: 'Alt+CommandOrControl+Right', icon: getIconPath('next.png', 'menu'), click: () => { mainWindow.webContents.send('play-next-track'); } }, { label: t('check-updates'), icon: getIconPath('update.png', 'menu'), click: () => { checkForUpdates(false); } }, { label: t('restart-app'), icon: getIconPath('restart.png', 'menu'), click: () => { app.relaunch(); app.isQuitting = true; app.quit(); } }, { label: t('show-hide'), accelerator: 'CmdOrCtrl+Shift+S', icon: getIconPath('show.png', 'menu'), click: () => { if (mainWindow) { if (mainWindow.isVisible()) { mainWindow.hide(); } else { mainWindow.show(); } } } }, { label: t('quit'), accelerator: 'CmdOrCtrl+Q', icon: getIconPath('quit.png', 'menu'), click: () => { app.isQuitting = true; app.quit(); } } ]); switch (process.platform) { case 'linux': tray.setContextMenu(contextMenu); break; default: tray.on('right-click', () => { tray.popUpContextMenu(contextMenu); }); } tray.on('click', () => { if (!mainWindow.isVisible()) { mainWindow.show(); } else if (!mainWindow.isFocused()) { mainWindow.show(); mainWindow.focus(); } else { mainWindow.hide(); //大概率永远不会执行 } }); tray.on('double-click', () => { mainWindow.show(); }); return tray; } // 创建 TouchBar export function createTouchBar(mainWindow) { const ICON_SIZE = 16; let isPlaying = false; const iconPath = (iconName) => { const originalIcon = nativeImage.createFromPath( getIconPath(`${iconName}.png`) ); // 调整图标大小 return originalIcon.resize({ width: ICON_SIZE, height: ICON_SIZE, }); }; const prevButton = new TouchBarButton({ icon: iconPath("prev"), iconPosition: "center", click: () => { mainWindow.webContents.send("play-previous-track"); }, }); const playPauseButton = new TouchBarButton({ icon: iconPath(isPlaying ? "pause" : "play"), iconPosition: "center", click: () => { isPlaying = !isPlaying; playPauseButton.icon = iconPath(isPlaying ? "pause" : "play"); mainWindow.webContents.send("toggle-play-pause"); }, }); const nextButton = new TouchBarButton({ icon: iconPath("next"), iconPosition: "center", click: () => { mainWindow.webContents.send("play-next-track"); }, }); // 歌词 const lyricsLabel = new TouchBarLabel({ label: t('no-lyrics'), textColor: "#FFFFFF", }); const touchBar = new TouchBar({ items: [ prevButton, new TouchBarSpacer({ size: "small" }), playPauseButton, new TouchBarSpacer({ size: "small" }), nextButton, new TouchBarSpacer({ size: "flexible" }), lyricsLabel, new TouchBarSpacer({ size: "flexible" }), ], }); mainWindow.setTouchBar(touchBar); // 监听播放状态变化 ipcMain.on("play-pause-action", (event, playing) => { isPlaying = playing; playPauseButton.icon = iconPath(isPlaying ? "pause" : "play"); }); // 监听歌词更新 ipcMain.on("update-current-lyrics", (event, currentLyric) => { if (currentLyric) { lyricsLabel.label = currentLyric; } }); return touchBar; } // 启动 API 服务器 export function startApiServer() { return new Promise((resolve, reject) => { let apiPath = ''; if (isDev) { return resolve(); // apiPath = path.join(__dirname, '../api/app_api'); } else { switch (process.platform) { case 'win32': apiPath = path.join(process.resourcesPath, '../api', 'app_win.exe'); break; case 'darwin': apiPath = path.join(process.resourcesPath, '../api', 'app_macos'); break; case 'linux': apiPath = path.join(process.resourcesPath, '../api', 'app_linux'); break; default: reject(new Error(`Unsupported platform: ${process.platform}`)); return; } } log.info(`API路径: ${apiPath}`); if (!fs.existsSync(apiPath)) { const error = new Error(`API可执行文件未找到:${apiPath}`); log.error(error.message); reject(error); return; } // 启动 API 服务器进程 const savedConfig = store.get('settings') || {}; const proxy = savedConfig?.proxy; const proxyUrl = savedConfig?.proxyUrl; const dataSource = savedConfig?.dataSource || 'concept'; const Args = []; if (dataSource === 'concept') { Args.push('--platform=lite'); log.info('API data source: concept (lite mode)'); } if (proxy === 'on' && proxyUrl) { const proxyAddress = String(proxyUrl).trim(); if (proxyAddress) { Args.push(`--proxy=${proxyAddress}`); log.info(`API proxy enabled: ${proxyAddress}`); } } Args.push('--port=6521'); apiProcess = spawn(apiPath, Args, { windowsHide: true }); apiProcess.stdout.on('data', (data) => { log.info(`API输出: ${data}`); if (data.toString().includes('running')) { console.log('API服务器已启动'); resolve(); } }); apiProcess.stderr.on('data', (data) => { log.error(`API 错误: ${data}`); reject(data); }); apiProcess.on('close', (code) => { log.info(`API 关闭,退出码: ${code}`); }); apiProcess.on('error', (error) => { log.error('启动 API 失败:', error); reject(error); }); }); } // 停止 API 服务器 export function stopApiServer() { if (apiProcess) { process.kill(apiProcess.pid, 'SIGKILL'); apiProcess = null; } } // 注册快捷键 export function registerShortcut() { try { const settings = store.get('settings'); globalShortcut.unregisterAll(); let clickFunc = () => { app.isQuitting = true; }; if (process.platform === 'darwin') { app.on('before-quit', clickFunc); } else { clickFunc = () => { app.isQuitting = true; app.quit(); }; if (settings?.shortcuts?.quitApp) { globalShortcut.register(settings?.shortcuts?.quitApp, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('CmdOrCtrl+Q', clickFunc); } } clickFunc = () => { if (mainWindow) { if (mainWindow.isVisible()) { mainWindow.hide(); } else { mainWindow.show(); } } } if (settings?.shortcuts?.mainWindow) { globalShortcut.register(settings?.shortcuts?.mainWindow, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('CmdOrCtrl+Shift+S', clickFunc); } clickFunc = () => mainWindow.webContents.send('play-previous-track'); if (settings?.shortcuts?.prevTrack) { globalShortcut.register(settings?.shortcuts?.prevTrack, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('Alt+CommandOrControl+Left', clickFunc); } clickFunc = () => mainWindow.webContents.send('play-next-track'); if (settings?.shortcuts?.nextTrack) { globalShortcut.register(settings?.shortcuts?.nextTrack, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('Alt+CommandOrControl+Right', clickFunc); } clickFunc = () => mainWindow.webContents.send('volume-up'); if (settings?.shortcuts?.volumeUp) { globalShortcut.register(settings?.shortcuts?.volumeUp, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('Alt+CommandOrControl+Up', clickFunc); } clickFunc = () => mainWindow.webContents.send('volume-down'); if (settings?.shortcuts?.volumeDown) { globalShortcut.register(settings?.shortcuts?.volumeDown, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('Alt+CommandOrControl+Down', clickFunc); } clickFunc = () => mainWindow.webContents.send('toggle-play-pause'); if (settings?.shortcuts?.playPause) { globalShortcut.register(settings?.shortcuts?.playPause, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('Alt+CommandOrControl+Space', clickFunc); } clickFunc = () => mainWindow.webContents.send('toggle-mute'); if (settings?.shortcuts?.mute) { globalShortcut.register(settings?.shortcuts?.mute, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('Alt+CommandOrControl+M', clickFunc); } clickFunc = () => mainWindow.webContents.send('toggle-like'); if (settings?.shortcuts?.like) { globalShortcut.register(settings?.shortcuts?.like, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('Alt+CommandOrControl+L', clickFunc); } clickFunc = () => mainWindow.webContents.send('toggle-mode'); if (settings?.shortcuts?.mode) { globalShortcut.register(settings?.shortcuts?.mode, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('Alt+CommandOrControl+P', clickFunc); } clickFunc = () => { if (mainWindow.lyricsWindow) { mainWindow.lyricsWindow.close(); mainWindow.lyricsWindow = null; new Notification({ title: t('desktop-lyrics-closed'), body: t('this-time-only'), icon: getIconPath('logo.png') }).show(); } else { createLyricsWindow(); } } if (settings?.shortcuts?.toggleDesktopLyrics) { globalShortcut.register(settings.shortcuts.toggleDesktopLyrics, clickFunc); } else if (!settings?.shortcuts) { globalShortcut.register('Alt+Ctrl+D', clickFunc); } } catch { dialog.showMessageBox({ type: 'error', title: t('hint'), message: t('shortcut-failed'), buttons: [t('ok')] }); } } // 播放启动问候语 export function playStartupSound() { const savedConfig = store.get('settings'); if (!savedConfig || (savedConfig['greetings'] !== 'on' && savedConfig['greetings'] !== 'null')) { return; } const audioFiles = [ '/assets/sound/yise-jp.mp3', '/assets/sound/qiqi-jp.mp3', '/assets/sound/qiqi-zh.mp3' ]; const randomIndex = Math.floor(Math.random() * audioFiles.length); const soundPath = isDev ? path.join(__dirname, '..', 'public', audioFiles[randomIndex]) : path.join(process.resourcesPath, 'public', audioFiles[randomIndex]); try { switch (process.platform) { case 'win32': const escapedPath = soundPath.replace(/'/g, "''"); exec(`powershell -c "Add-Type -AssemblyName PresentationCore; $player = New-Object System.Windows.Media.MediaPlayer; $player.Open('${escapedPath}'); $player.Play(); Start-Sleep -s 3; $player.Stop()"`); break; case 'darwin': exec(`afplay "${soundPath}"`); break; case 'linux': exec(`paplay "${soundPath}"`, (error) => { if (error) { exec(`play "${soundPath}"`); } }); break; } } catch (error) { log.error('播放启动问候语失败:', error); } } // 设置任务栏缩略图工具栏 export function setThumbarButtons(mainWindow, isPlaying = false) { const buttons = [ { tooltip: t('prev-track'), icon: getIconPath('prev.png'), click: () => { mainWindow.webContents.send('play-previous-track'); setThumbarButtons(mainWindow, true); } }, { tooltip: t('pause'), icon: getIconPath('pause.png'), click: () => { mainWindow.webContents.send('toggle-play-pause'); setThumbarButtons(mainWindow, false); } }, { tooltip: t('next-track'), icon: getIconPath('next.png'), click: () => { mainWindow.webContents.send('play-next-track'); setThumbarButtons(mainWindow, true); } } ]; if (!isPlaying) { buttons[1] = { tooltip: t('play'), icon: getIconPath('play.png'), click: () => { mainWindow.webContents.send('toggle-play-pause'); setThumbarButtons(mainWindow, true); } }; } mainWindow.setThumbarButtons(buttons); } // 处理自定义协议相关 let hash = ""; let listid = ""; let protocolMainWindow = null; // 注册自定义协议 export function registerProtocolHandler(mainWindow) { const PROTOCOL = "moekoe"; // 保存mainWindow引用 if (mainWindow) { protocolMainWindow = mainWindow; } // 注册协议 app.setAsDefaultProtocolClient(PROTOCOL, process.execPath); // 处理启动参数 handleArgv(process.argv); // 处理第二个实例的启动参数 app.on('second-instance', (event, commandLine) => { if (protocolMainWindow) { if (protocolMainWindow.isMinimized()) protocolMainWindow.restore(); protocolMainWindow.show(); protocolMainWindow.focus(); handleArgv(commandLine); } }); // 在macOS平台特别处理open-url事件 if (process.platform === 'darwin') { app.on('open-url', (event, urlStr) => { event.preventDefault(); handleUrl(urlStr); }); } return { getHash: () => hash, handleProtocolArgv: handleArgv }; } // 处理命令行参数 function handleArgv(argv) { const PROTOCOL = "moekoe"; const prefix = `${PROTOCOL}:`; const url = argv.find(arg => arg.startsWith(prefix)); if (url) handleUrl(url); } // 处理URL function handleUrl(url) { const urlObj = new URL(url); // 提取所有参数并更新全局变量 hash = urlObj.searchParams.get("hash") || ""; listid = urlObj.searchParams.get("listid") || ""; // 根据路径和参数决定发送什么数据到渲染进程 if (protocolMainWindow && protocolMainWindow.webContents) { // 将所有参数打包发送 protocolMainWindow.webContents.send('url-params', { hash, listid, urlPath: urlObj.pathname.substring(1) // 去掉前导斜杠 }); } } // 如果有从URL启动的hash参数,在页面加载完成后发送 export function sendHashAfterLoad(mainWindow) { if (mainWindow) { protocolMainWindow = mainWindow; } if ((hash || listid) && protocolMainWindow) { protocolMainWindow.webContents.on('did-finish-load', () => { setTimeout(() => { protocolMainWindow.webContents.send('url-params', { hash, listid, urlPath: 'share' }); }, 1000); }); } } ================================================ FILE: electron/extensions/extensionIPC.js ================================================ import { ipcMain, shell, BrowserWindow, dialog } from 'electron'; import path from 'path'; import fs from 'fs'; import log from 'electron-log'; import { fileURLToPath } from 'url'; import extensionManager from './extensionManager.js'; import { bindExternalLinkHandler } from '../services/externalLinkHandler.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // 获取插件图标数据 function getExtensionIconData(extension, extensionPath) { if (extension.manifest?.icons) { const icons = extension.manifest.icons; const iconSizes = Object.keys(icons); if (iconSizes.length > 0) { const iconSize = iconSizes[0]; const iconPath = icons[iconSize]; const fullIconPath = path.join(extensionPath, iconPath); try { if (fs.existsSync(fullIconPath)) { const iconData = fs.readFileSync(fullIconPath); const ext = path.extname(iconPath).toLowerCase(); let mimeType = 'image/png'; if (ext === '.jpg' || ext === '.jpeg') { mimeType = 'image/jpeg'; } return `data:${mimeType};base64,${iconData.toString('base64')}`; } } catch (error) { log.error('读取插件图标失败:', error); } } } return null; } /** * 注册插件相关的 IPC 处理程序 */ export function registerExtensionIPC() { // 获取插件列表 ipcMain.handle('get-extensions', () => { try { const loadedExtensions = extensionManager.getLoadedExtensions(); const scannedExtensions = extensionManager.scanExtensions(); const extensions = loadedExtensions.map(ext => { const scannedExt = scannedExtensions.find(scanned => scanned.name === ext.name); let iconData = null; const manifestAuthor = ext.manifest?.author ?? scannedExt?.manifest?.author; const authorName = typeof manifestAuthor === 'string' ? manifestAuthor : (manifestAuthor?.name || ''); const authorUrl = typeof manifestAuthor === 'object' ? (manifestAuthor?.url || '') : ''; if (scannedExt?.path) { iconData = getExtensionIconData(ext, scannedExt.path); } return { id: ext.id, pluginId: ext.manifest?.plugin_id || scannedExt?.manifest?.plugin_id || '', name: ext.name, directory: scannedExt?.directory || '', version: ext.version, enabled: true, description: ext.manifest?.description || '', author: authorName, authorUrl: authorUrl, permissions: ext.manifest?.permissions || [], iconData: iconData, moeKoeAdapted: ext.manifest?.moekoe === true || scannedExt?.manifest?.moekoe === true, minversion: ext.manifest?.minversion || scannedExt?.manifest?.minversion || '' }; }); return { success: true, extensions: extensions }; } catch (error) { log.error('获取插件列表失败:', error); return { success: false, error: error.message, extensions: [] }; } }); // 获取详细插件信息 ipcMain.handle('get-extensions-detailed', () => { try { return extensionManager.scanExtensions(); } catch (error) { log.error('获取详细插件信息失败:', error); return []; } }); // 重新加载插件 ipcMain.handle('reload-extensions', () => { try { const result = extensionManager.reloadExtensions(); return result; } catch (error) { log.error('重新加载插件失败:', error); return { success: false, message: error.message }; } }); // 打开插件目录 ipcMain.handle('open-extensions-dir', () => { try { const extensionsDir = extensionManager.getExtensionsDirectory(); shell.openPath(extensionsDir); return { success: true, path: extensionsDir }; } catch (error) { log.error('打开插件目录失败:', error); return { success: false, message: error.message }; } }); // 打开插件弹窗 ipcMain.handle('open-extension-popup', (event, extensionId, extensionName) => { try { // 创建新的弹窗窗口 const popupWindow = new BrowserWindow({ width: 400, height: 600, webPreferences: { preload: path.join(__dirname, '../preload.cjs'), nodeIntegration: false, contextIsolation: true, enableRemoteModule: false, sandbox: false, webSecurity: false // 允许加载插件内容 }, title: extensionName || '插件弹窗', resizable: true, minimizable: true, maximizable: false, alwaysOnTop: false, show: false, autoHideMenuBar: true, // 隐藏菜单栏 menuBarVisible: false // 不显示菜单栏 }); // 完全移除菜单栏 popupWindow.setMenuBarVisibility(false); popupWindow.removeMenu(); bindExternalLinkHandler( popupWindow, (url) => /^(https?:|mailto:|tel:)/i.test(url) ); // 构建插件弹窗URL const popupUrl = `chrome-extension://${extensionId}/popup.html`; popupWindow.loadURL(popupUrl).then(() => { popupWindow.show(); }).catch((error) => { log.error('加载插件弹窗失败:', error); popupWindow.close(); }); return { success: true, extensionId }; } catch (error) { log.error('打开插件弹窗失败:', error); return { success: false, message: error.message }; } }); // 安装插件 ipcMain.handle('install-extension', async (event, extensionPath) => { try { const result = await extensionManager.installExtension(extensionPath); return result; } catch (error) { log.error('手动安装插件失败:', error); return { success: false, message: error.message }; } }); // 卸载插件 ipcMain.handle('uninstall-extension', (event, extensionId, extensionDir) => { try { const result = extensionManager.uninstallExtension(extensionId, extensionDir); return result; } catch (error) { log.error('卸载插件失败:', error); return { success: false, message: error.message }; } }); // 验证插件清单 ipcMain.handle('validate-extension', async (event, extensionPath) => { try { const manifestPath = path.join(extensionPath, 'manifest.json'); const validation = extensionManager.validateManifest(manifestPath); return validation; } catch (error) { log.error('验证插件失败:', error); return { valid: false, error: error.message }; } }); // 获取插件目录路径 ipcMain.handle('get-extensions-directory', () => { try { return { success: true, path: extensionManager.getExtensionsDirectory() }; } catch (error) { log.error('获取插件目录路径失败:', error); return { success: false, message: error.message }; } }); // 从zip安装插件 ipcMain.handle('install-plugin-from-zip', async (event, zipPath) => { try { const result = await extensionManager.installPluginFromZip(zipPath); return result; } catch (error) { log.error('安装插件失败:', error); return { success: false, message: error.message }; } }); // 从URL安装插件 ipcMain.handle('install-plugin-from-url', async (event, payload = {}) => { try { const result = await extensionManager.installPluginFromUrl( payload.downloadUrl, payload.extensionId, payload.extensionDir ); return result; } catch (error) { log.error('Failed to install remote plugin:', error); return { success: false, message: error.message }; } }); // 显示文件选择对话框 ipcMain.handle('show-open-dialog', async (event, options) => { try { const result = await dialog.showOpenDialog({ ...options, properties: [...(options.properties || [])], filters: [...(options.filters || [])] }); if (!result.canceled && result.filePaths.length > 0) { return { success: true, filePath: result.filePaths[0] }; } return { success: false, message: '未选择文件' }; } catch (error) { log.error('打开文件对话框失败:', error); return { success: false, message: error.message }; } }); // 确保插件目录存在 ipcMain.handle('ensure-extensions-directory', () => { try { const path = extensionManager.ensureExtensionsDirectory(); return { success: true, path }; } catch (error) { log.error('创建插件目录失败:', error); return { success: false, message: error.message }; } }); log.info('插件 IPC 处理程序已注册'); } /** * 注销插件相关的 IPC 处理程序 */ export function unregisterExtensionIPC() { const channels = [ 'get-extensions', 'get-extensions-detailed', 'reload-extensions', 'open-extensions-dir', 'open-extension-popup', 'install-extension', 'uninstall-extension', 'validate-extension', 'get-extensions-directory', 'ensure-extensions-directory', 'install-plugin-from-zip', 'install-plugin-from-url', 'show-open-dialog' ]; channels.forEach(channel => { ipcMain.removeHandler(channel); }); log.info('插件 IPC 处理程序已注销'); } export default { registerExtensionIPC, unregisterExtensionIPC }; ================================================ FILE: electron/extensions/extensionManager.js ================================================ import { session, app } from 'electron'; import path from 'path'; import fs from 'fs'; import log from 'electron-log'; import { fileURLToPath } from 'url'; import isDev from 'electron-is-dev'; import AdmZip from 'adm-zip'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Chrome 插件管理 - 根据环境选择正确的路径 const EXTENSIONS_DIR = !isDev ? path.join(app.getPath('userData'), 'extensions') : path.join(__dirname, '../../plugins/extensions'); /** * 加载 Chrome 插件 */ export function loadChromeExtensions() { if (!fs.existsSync(EXTENSIONS_DIR)) { fs.mkdirSync(EXTENSIONS_DIR, { recursive: true }); log.info('创建插件目录:', EXTENSIONS_DIR); } try { const extensionDirs = fs.readdirSync(EXTENSIONS_DIR, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); for (const extensionDir of extensionDirs) { const extensionPath = path.join(EXTENSIONS_DIR, extensionDir); const manifestPath = path.join(extensionPath, 'manifest.json'); if (fs.existsSync(manifestPath)) { try { const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); // 验证 manifest 格式 if (manifest.manifest_version && manifest.name && manifest.version) { session.defaultSession.loadExtension(extensionPath, { allowFileAccess: true }).then((extension) => { log.info(`成功加载插件: ${manifest.name} (${extension.id})`); }).catch((error) => { log.error(`加载插件失败 ${extensionDir}:`, error); }); } else { log.warn(`插件 ${extensionDir} 的 manifest.json 格式不正确`); } } catch (error) { log.error(`解析插件 ${extensionDir} 的 manifest.json 失败:`, error); } } else { log.warn(`插件目录 ${extensionDir} 缺少 manifest.json 文件`); } } } catch (error) { log.error('扫描插件目录失败:', error); } } /** * 卸载所有插件 */ export function unloadChromeExtensions() { try { const extensions = session.defaultSession.getAllExtensions(); extensions.forEach(extension => { session.defaultSession.removeExtension(extension.id); log.info(`卸载插件: ${extension.name}`); }); } catch (error) { log.error('卸载插件失败:', error); } } /** * 获取已加载的插件列表 */ export function getLoadedExtensions() { try { return session.defaultSession.getAllExtensions(); } catch (error) { log.error('获取插件列表失败:', error); return []; } } /** * 安装单个插件 * @param {string} extensionPath 插件路径 */ export async function installExtension(extensionPath) { try { const extension = await session.defaultSession.loadExtension(extensionPath, { allowFileAccess: true }); log.info(`手动安装插件成功: ${extension.name}`); return { success: true, extension: { id: extension.id, name: extension.name } }; } catch (error) { log.error('手动安装插件失败:', error); return { success: false, message: error.message }; } } /** * 卸载单个插件 * @param {string} extensionId 插件ID */ export function uninstallExtension(extensionId, extensionDir = '') { try { let removedFromSession = false; let removedFiles = false; let targetDirPath = ''; targetDirPath = path.join(EXTENSIONS_DIR, path.basename(extensionDir.trim())); try { session.defaultSession.removeExtension(extensionId); removedFromSession = true; log.info(`卸载插件会话: ${extensionId}`); } catch (error) { log.warn(`卸载插件会话失败 ${extensionId}:`, error); } if (targetDirPath && fs.existsSync(targetDirPath)) { fs.rmSync(targetDirPath, { recursive: true, force: true }); removedFiles = true; log.info(`删除插件目录: ${targetDirPath}`); } if (!removedFromSession && !removedFiles) { return { success: false, message: '未找到可卸载的插件会话或目录' }; } return { success: true, removedFromSession, removedFiles, path: targetDirPath || '' }; } catch (error) { log.error('卸载插件失败:', error); return { success: false, message: error.message }; } } /** * 重新加载所有插件 */ export function reloadExtensions() { try { unloadChromeExtensions(); loadChromeExtensions(); return { success: true, message: '插件重新加载成功' }; } catch (error) { log.error('重新加载插件失败:', error); return { success: false, message: error.message }; } } /** * 获取插件目录路径 */ export function getExtensionsDirectory() { return EXTENSIONS_DIR; } /** * 检查插件目录是否存在 */ export function ensureExtensionsDirectory() { if (!fs.existsSync(EXTENSIONS_DIR)) { fs.mkdirSync(EXTENSIONS_DIR, { recursive: true }); log.info('创建插件目录:', EXTENSIONS_DIR); } return EXTENSIONS_DIR; } /** * 验证插件清单文件 * @param {string} manifestPath manifest.json 文件路径 */ export function validateManifest(manifestPath) { try { if (!fs.existsSync(manifestPath)) { return { valid: false, error: 'manifest.json 文件不存在' }; } const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); // 检查必需字段 const requiredFields = ['manifest_version', 'name', 'version']; for (const field of requiredFields) { if (!manifest[field]) { return { valid: false, error: `缺少必需字段: ${field}` }; } } // 检查 manifest 版本 if (manifest.manifest_version !== 3) { return { valid: false, error: '仅支持 Manifest V3 格式' }; } return { valid: true, manifest }; } catch (error) { return { valid: false, error: `解析 manifest.json 失败: ${error.message}` }; } } /** * 获取插件详细信息 * @param {string} extensionDir 插件目录名 */ export function getExtensionInfo(extensionDir) { const extensionPath = path.join(EXTENSIONS_DIR, extensionDir); const manifestPath = path.join(extensionPath, 'manifest.json'); const validation = validateManifest(manifestPath); if (!validation.valid) { return { error: validation.error }; } const manifest = validation.manifest; const stats = fs.statSync(extensionPath); return { name: manifest.name, version: manifest.version, description: manifest.description || '', author: manifest.author || '', permissions: manifest.permissions || [], path: extensionPath, size: getDirectorySize(extensionPath), lastModified: stats.mtime, manifest: manifest }; } /** * 获取目录大小 * @param {string} dirPath 目录路径 */ function getDirectorySize(dirPath) { let totalSize = 0; try { const files = fs.readdirSync(dirPath); for (const file of files) { const filePath = path.join(dirPath, file); const stats = fs.statSync(filePath); if (stats.isDirectory()) { totalSize += getDirectorySize(filePath); } else { totalSize += stats.size; } } } catch (error) { log.error('计算目录大小失败:', error); } return totalSize; } /** * 从zip文件安装插件 * @param {string} zipPath zip文件路径 * @returns {Promise<{success: boolean, message: string}>} */ export async function installPluginFromZip(zipPath) { try { const zip = new AdmZip(zipPath); const zipEntries = zip.getEntries(); // 确保插件目录存在 ensureExtensionsDirectory(); // 查找包含 manifest.json 的第一级目录 let pluginEntry = null; let manifestEntry = null; for (const entry of zipEntries) { const parts = entry.entryName.split('/'); if (parts.length === 2 && parts[1] === 'manifest.json') { manifestEntry = entry; pluginEntry = parts[0]; break; } } if (!manifestEntry || !pluginEntry) { return { success: false, message: '无效的插件包格式:未找到 manifest.json' }; } // 验证 manifest const manifestContent = zip.readAsText(manifestEntry); try { const manifest = JSON.parse(manifestContent); // 直接验证 manifest 对象而不是文件路径 if (!manifest.manifest_version || !manifest.name || !manifest.version) { return { success: false, message: '清单文件缺少必需字段' }; } if (manifest.manifest_version !== 3) { return { success: false, message: '仅支持 Manifest V3 格式' }; } } catch (error) { return { success: false, message: `manifest.json 解析失败: ${error.message}` }; } // 获取真实的插件目录名(去除可能的 -main 后缀) const pluginName = pluginEntry.replace(/-main$/, ''); const targetDir = path.join(EXTENSIONS_DIR, pluginName); // 如果目标目录已存在,先删除 if (fs.existsSync(targetDir)) { fs.rmSync(targetDir, { recursive: true, force: true }); } // 创建目标目录 fs.mkdirSync(targetDir, { recursive: true }); // 解压所有文件,保持目录结构 for (const entry of zipEntries) { const entryName = entry.entryName; // 检查是否属于目标插件目录 if (entryName.startsWith(pluginEntry + '/')) { // 计算相对路径 const relativePath = entryName.substring(pluginEntry.length + 1); if (relativePath) { // 跳过空路径 const targetPath = path.join(targetDir, relativePath); if (entry.isDirectory) { // 创建目录 fs.mkdirSync(targetPath, { recursive: true }); } else { // 解压文件 const targetDirPath = path.dirname(targetPath); fs.mkdirSync(targetDirPath, { recursive: true }); fs.writeFileSync(targetPath, entry.getData()); } } } } // 加载新插件 const result = await installExtension(targetDir); if (!result.success) { // 如果加载失败,清理已解压的文件 if (fs.existsSync(targetDir)) { fs.rmSync(targetDir, { recursive: true, force: true }); } return { success: false, message: '插件加载失败:' + result.message }; } return { success: true, message: `插件安装成功`, extension: result.extension }; } catch (error) { log.error('安装插件失败:', error); return { success: false, message: '安装插件失败:' + error.message }; } } /** * Download a zip package and install or update a plugin from a remote URL. * @param {string} downloadUrl * @param {string} extensionId * @param {string} extensionDir * @returns {Promise<{success: boolean, message: string}>} */ export async function installPluginFromUrl(downloadUrl, extensionId = '', extensionDir = '') { const tempZipPath = path.join(app.getPath('temp'), `moekoe-plugin-${Date.now()}.zip`); try { if (!downloadUrl || typeof downloadUrl !== 'string') { return { success: false, message: 'Invalid plugin download url' }; } const response = await fetch(downloadUrl); if (!response.ok) { return { success: false, message: `Failed to download plugin package: ${response.status} ${response.statusText || ''}`.trim() }; } const arrayBuffer = await response.arrayBuffer(); const zipBuffer = Buffer.from(arrayBuffer); if (!isZipBuffer(zipBuffer)) { return { success: false, message: '下载内容不是有效的 zip 插件包' }; } fs.writeFileSync(tempZipPath, zipBuffer); if (extensionId || extensionDir) { const uninstallResult = uninstallExtension(extensionId, extensionDir); if (!uninstallResult.success) { log.warn('Failed to remove existing plugin before update:', uninstallResult.message); } } return await installPluginFromZip(tempZipPath); } catch (error) { log.error('Failed to install plugin from url:', error); return { success: false, message: error.message }; } finally { if (fs.existsSync(tempZipPath)) { fs.rmSync(tempZipPath, { force: true }); } } } function isZipBuffer(buffer) { return Boolean(buffer) && buffer.length >= 4 && buffer[0] === 0x50 && buffer[1] === 0x4b && ( (buffer[2] === 0x03 && buffer[3] === 0x04) || (buffer[2] === 0x05 && buffer[3] === 0x06) || (buffer[2] === 0x07 && buffer[3] === 0x08) ); } /** * 格式化文件大小 * @param {number} bytes 字节数 */ export function formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } /** * 扫描并获取所有插件信息 */ export function scanExtensions() { ensureExtensionsDirectory(); try { const extensionDirs = fs.readdirSync(EXTENSIONS_DIR, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); const extensions = []; for (const extensionDir of extensionDirs) { const info = getExtensionInfo(extensionDir); if (!info.error) { extensions.push({ ...info, directory: extensionDir, installed: isExtensionInstalled(info.name) }); } else { log.warn(`插件 ${extensionDir} 信息获取失败:`, info.error); } } return extensions; } catch (error) { log.error('扫描插件失败:', error); return []; } } /** * 检查插件是否已安装 * @param {string} extensionName 插件名称 */ function isExtensionInstalled(extensionName) { try { const loadedExtensions = getLoadedExtensions(); return loadedExtensions.some(ext => ext.name === extensionName); } catch (error) { return false; } } // 默认导出所有功能 export default { loadChromeExtensions, unloadChromeExtensions, getLoadedExtensions, installExtension, uninstallExtension, reloadExtensions, getExtensionsDirectory, ensureExtensionsDirectory, validateManifest, getExtensionInfo, formatFileSize, scanExtensions, installPluginFromZip, installPluginFromUrl }; ================================================ FILE: electron/extensions/extensions.js ================================================ // 插件系统统一入口文件 import extensionManager from './extensionManager.js'; import { registerExtensionIPC, unregisterExtensionIPC } from './extensionIPC.js'; import log from 'electron-log'; /** * 初始化插件系统 */ export function initializeExtensions() { try { // 确保插件目录存在 extensionManager.ensureExtensionsDirectory(); // 注册 IPC 处理程序 registerExtensionIPC(); // 加载插件 extensionManager.loadChromeExtensions(); return { success: true }; } catch (error) { log.error('插件系统初始化失败:', error); return { success: false, error: error.message }; } } /** * 清理插件系统 */ export function cleanupExtensions() { try { // 卸载所有插件 extensionManager.unloadChromeExtensions(); // 注销 IPC 处理程序 unregisterExtensionIPC(); return { success: true }; } catch (error) { log.error('插件系统清理失败:', error); return { success: false, error: error.message }; } } /** * 重启插件系统 */ export function restartExtensions() { try { const cleanupResult = cleanupExtensions(); if (!cleanupResult.success) { return cleanupResult; } const initResult = initializeExtensions(); return initResult; } catch (error) { log.error('重启插件系统失败:', error); return { success: false, error: error.message }; } } export { extensionManager, registerExtensionIPC, unregisterExtensionIPC }; export default { initializeExtensions, cleanupExtensions, restartExtensions, extensionManager, registerExtensionIPC, unregisterExtensionIPC }; ================================================ FILE: electron/language/i18n.js ================================================ import Store from 'electron-store'; import { app } from 'electron'; const store = new Store(); const translations = { 'zh-CN': { 'project-home': '项目主页', 'report-bug': '反馈bug', 'prev-track': '上一首', 'pause': '暂停', 'play': '播放', 'next-track': '下一首', 'check-updates': '检查更新', 'restart-app': '重启应用', 'show-hide': '显示/隐藏', 'quit': '退出程序', 'no-lyrics': '暂无歌词', 'desktop-lyrics-closed': '桌面歌词已关闭', 'this-time-only': '仅本次生效', 'now-playing': '正在播放:', 'error': '错误', 'init-error': '初始化应用时发生错误。', 'api-error': 'API 服务启动失败,请检查!', 'ok': '确定', 'shortcut-failed': '快捷键注册失败,请重新尝试', 'hint': '提示', 'update-timeout': '更新服务器连接超时,请检查网络', 'update-failed': '更新检查失败,请重试', 'new-version': '发现新版本', 'new-version-msg': '发现新版本 {version}\n\n{notes}', 'no-release-notes': '暂无更新说明', 'update-now': '立即更新', 'later': '稍后提醒', 'update-hint': '更新提示', 'already-latest': '当前已是最新版本', 'update-ready': '更新就绪', 'update-ready-msg': '新版本已下载完成,立即安装?', 'install-now': '现在安装', 'install-later': '稍后安装', 'non-windows-update': '非 Windows 平台暂不支持在线更新,请前往官网或应用市场下载最新版本。' }, 'zh-TW': { 'project-home': '專案首頁', 'report-bug': '回報問題', 'prev-track': '上一首', 'pause': '暫停', 'play': '播放', 'next-track': '下一首', 'check-updates': '檢查更新', 'restart-app': '重新啟動', 'show-hide': '顯示/隱藏', 'quit': '結束程式', 'no-lyrics': '暫無歌詞', 'desktop-lyrics-closed': '桌面歌詞已關閉', 'this-time-only': '僅本次生效', 'now-playing': '正在播放:', 'error': '錯誤', 'init-error': '初始化應用時發生錯誤。', 'api-error': 'API 服務啟動失敗,請檢查!', 'ok': '確定', 'shortcut-failed': '快捷鍵註冊失敗,請重新嘗試', 'hint': '提示', 'update-timeout': '更新伺服器連接逾時,請檢查網絡', 'update-failed': '更新檢查失敗,請重試', 'new-version': '發現新版本', 'new-version-msg': '發現新版本 {version}\n\n{notes}', 'no-release-notes': '暫無更新說明', 'update-now': '立即更新', 'later': '稍後提醒', 'update-hint': '更新提示', 'already-latest': '當前已是最新版本', 'update-ready': '更新就緒', 'update-ready-msg': '新版本已下載完成,立即安裝?', 'install-now': '現在安裝', 'install-later': '稍後安裝', 'non-windows-update': '非 Windows 平台暫不支持線上更新,請前往官網或應用市場下載最新版本。' }, 'en': { 'project-home': 'Project Homepage', 'report-bug': 'Report Bug', 'prev-track': 'Previous', 'pause': 'Pause', 'play': 'Play', 'next-track': 'Next', 'check-updates': 'Check Updates', 'restart-app': 'Restart App', 'show-hide': 'Show/Hide', 'quit': 'Quit', 'no-lyrics': 'No Lyrics', 'desktop-lyrics-closed': 'Desktop Lyrics Closed', 'this-time-only': 'This session only', 'now-playing': 'Now Playing: ', 'error': 'Error', 'init-error': 'Error initializing app.', 'api-error': 'API service failed to start!', 'ok': 'OK', 'shortcut-failed': 'Shortcut registration failed', 'hint': 'Notice', 'update-timeout': 'Update server connection timeout', 'update-failed': 'Update check failed', 'new-version': 'New Version Available', 'new-version-msg': 'New version {version} available\n\n{notes}', 'no-release-notes': 'No release notes', 'update-now': 'Update Now', 'later': 'Later', 'update-hint': 'Update', 'already-latest': 'Already up to date', 'update-ready': 'Update Ready', 'update-ready-msg': 'Update downloaded. Install now?', 'install-now': 'Install Now', 'install-later': 'Later', 'non-windows-update': 'Auto-update is only available on Windows. Please download from the official website.' }, 'ru': { 'project-home': 'Страница проекта', 'report-bug': 'Сообщить об ошибке', 'prev-track': 'Предыдущий', 'pause': 'Пауза', 'play': 'Воспроизвести', 'next-track': 'Следующий', 'check-updates': 'Проверить обновления', 'restart-app': 'Перезапустить', 'show-hide': 'Показать/Скрыть', 'quit': 'Выход', 'no-lyrics': 'Нет текста', 'desktop-lyrics-closed': 'Текст на рабочем столе скрыт', 'this-time-only': 'Только для этого сеанса', 'now-playing': 'Сейчас играет: ', 'error': 'Ошибка', 'init-error': 'Ошибка инициализации приложения.', 'api-error': 'Не удалось запустить API!', 'ok': 'ОК', 'shortcut-failed': 'Не удалось зарегистрировать горячие клавиши', 'hint': 'Уведомление', 'update-timeout': 'Таймаут подключения к серверу обновлений', 'update-failed': 'Ошибка проверки обновлений', 'new-version': 'Доступна новая версия', 'new-version-msg': 'Доступна версия {version}\n\n{notes}', 'no-release-notes': 'Нет описания изменений', 'update-now': 'Обновить', 'later': 'Позже', 'update-hint': 'Обновление', 'already-latest': 'Установлена последняя версия', 'update-ready': 'Обновление готово', 'update-ready-msg': 'Обновление загружено. Установить сейчас?', 'install-now': 'Установить', 'install-later': 'Позже', 'non-windows-update': 'Автообновление доступно только на Windows. Скачайте с официального сайта.' }, 'ja': { 'project-home': 'プロジェクトホーム', 'report-bug': 'バグ報告', 'prev-track': '前の曲', 'pause': '一時停止', 'play': '再生', 'next-track': '次の曲', 'check-updates': '更新を確認', 'restart-app': '再起動', 'show-hide': '表示/非表示', 'quit': '終了', 'no-lyrics': '歌詞なし', 'desktop-lyrics-closed': 'デスクトップ歌詞を閉じました', 'this-time-only': '今回のみ有効', 'now-playing': '再生中:', 'error': 'エラー', 'init-error': 'アプリの初期化エラー。', 'api-error': 'APIサービスの起動に失敗しました!', 'ok': 'OK', 'shortcut-failed': 'ショートカット登録に失敗しました', 'hint': '通知', 'update-timeout': '更新サーバー接続タイムアウト', 'update-failed': '更新確認に失敗しました', 'new-version': '新しいバージョンが利用可能', 'new-version-msg': 'バージョン {version} が利用可能です\n\n{notes}', 'no-release-notes': 'リリースノートなし', 'update-now': '今すぐ更新', 'later': '後で', 'update-hint': '更新', 'already-latest': '最新バージョンです', 'update-ready': '更新準備完了', 'update-ready-msg': '更新がダウンロードされました。今すぐインストールしますか?', 'install-now': '今すぐインストール', 'install-later': '後で', 'non-windows-update': '自動更新はWindowsでのみ利用可能です。公式サイトからダウンロードしてください。' }, 'ko': { 'project-home': '프로젝트 홈', 'report-bug': '버그 신고', 'prev-track': '이전', 'pause': '일시정지', 'play': '재생', 'next-track': '다음', 'check-updates': '업데이트 확인', 'restart-app': '재시작', 'show-hide': '표시/숨기기', 'quit': '종료', 'no-lyrics': '가사 없음', 'desktop-lyrics-closed': '바탕화면 가사 닫힘', 'this-time-only': '이번 세션만', 'now-playing': '재생 중: ', 'error': '오류', 'init-error': '앱 초기화 오류.', 'api-error': 'API 서비스 시작 실패!', 'ok': '확인', 'shortcut-failed': '단축키 등록 실패', 'hint': '알림', 'update-timeout': '업데이트 서버 연결 시간 초과', 'update-failed': '업데이트 확인 실패', 'new-version': '새 버전 사용 가능', 'new-version-msg': '버전 {version} 사용 가능\n\n{notes}', 'no-release-notes': '릴리스 노트 없음', 'update-now': '지금 업데이트', 'later': '나중에', 'update-hint': '업데이트', 'already-latest': '최신 버전입니다', 'update-ready': '업데이트 준비 완료', 'update-ready-msg': '업데이트가 다운로드되었습니다. 지금 설치하시겠습니까?', 'install-now': '지금 설치', 'install-later': '나중에', 'non-windows-update': '자동 업데이트는 Windows에서만 사용 가능합니다. 공식 웹사이트에서 다운로드하세요.' } }; function getLocale() { // First check user settings const settings = store.get('settings'); if (settings?.language) { return settings.language; } // Otherwise use system language const systemLang = app.getLocale(); if (systemLang.startsWith('zh')) { return systemLang === 'zh-TW' || systemLang === 'zh-HK' ? 'zh-TW' : 'zh-CN'; } if (systemLang.startsWith('ru')) return 'ru'; if (systemLang.startsWith('ja')) return 'ja'; if (systemLang.startsWith('ko')) return 'ko'; if (systemLang.startsWith('en')) return 'en'; return 'zh-CN'; // fallback } export function t(key) { const locale = getLocale(); return translations[locale]?.[key] || translations['zh-CN']?.[key] || key; } export default { t }; ================================================ FILE: electron/main.js ================================================ import { app, ipcMain, globalShortcut, dialog, Notification, shell, session, powerSaveBlocker, nativeImage } from 'electron'; import { createWindow, createTray, createTouchBar, startApiServer, stopApiServer, registerShortcut, playStartupSound, createLyricsWindow, setThumbarButtons, registerProtocolHandler, sendHashAfterLoad, getTray, createMvWindow } from './appServices.js'; import { initializeExtensions, cleanupExtensions } from './extensions/extensions.js'; import { setupAutoUpdater } from './services/updater.js'; import apiService from './services/apiService.js'; import statusBarLyricsService from './services/statusBarLyricsService.js'; import Store from 'electron-store'; import path from 'path'; import { fileURLToPath } from 'url'; import { t } from './language/i18n.js'; let mainWindow = null; let blockerId = null; const store = new Store(); const __dirname = path.dirname(fileURLToPath(import.meta.url)); const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); process.exit(0); } else { let protocolHandler; app.on('second-instance', (event, commandLine) => { if (!protocolHandler) { protocolHandler = registerProtocolHandler(null); } if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.show(); mainWindow.focus(); } protocolHandler.handleProtocolArgv(commandLine); }); } app.on('ready', () => { startApiServer().then(() => { try { mainWindow = createWindow(); createTray(mainWindow); // 初始化状态栏歌词服务 statusBarLyricsService.init(mainWindow, store, getTray, createTray); if (process.platform === "darwin" && store.get('settings')?.touchBar == 'on') createTouchBar(mainWindow); playStartupSound(); registerShortcut(); setupAutoUpdater(mainWindow); apiService.init(mainWindow); registerProtocolHandler(mainWindow); sendHashAfterLoad(mainWindow); initializeExtensions(); } catch (error) { console.log('初始化应用时发生错误:', error); createTray(null); dialog.showMessageBox({ type: 'error', title: t('error'), message: t('init-error'), buttons: [t('ok')] }).then(result => { if (result.response === 0) { app.isQuitting = true; app.quit(); } }); } }).catch((error) => { console.log('API 服务启动失败:', error); createTray(null); dialog.showMessageBox({ type: 'error', title: t('error'), message: t('api-error'), buttons: [t('ok')] }).then(result => { if (result.response === 0) { app.isQuitting = true; app.quit(); } return; }); }); }); const settings = store.get('settings'); if (settings?.gpuAcceleration === 'on') { app.disableHardwareAcceleration(); app.commandLine.appendSwitch('enable-transparent-visuals'); app.commandLine.appendSwitch('disable-gpu-compositing'); } if (settings?.preventAppSuspension === 'on') { blockerId = powerSaveBlocker.start('prevent-display-sleep'); } if (settings?.highDpi === 'on') { app.commandLine.appendSwitch('high-dpi-support', '1'); app.commandLine.appendSwitch('force-device-scale-factor', settings?.dpiScale || '1'); } if (settings?.apiMode === 'on') { apiService.start(); } // 即将退出 app.on('before-quit', () => { if (mainWindow && !mainWindow.isMaximized()) { const windowBounds = mainWindow.getBounds(); store.set('windowState', windowBounds); } if (blockerId !== null) { powerSaveBlocker.stop(blockerId); } // 清理状态栏歌词服务 statusBarLyricsService.cleanup(); stopApiServer(); apiService.stop(); cleanupExtensions(); }); // 关闭所有窗口 app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.isQuitting = true; app.quit(); // 非 macOS 系统上关闭所有窗口后退出应用 } }); // 图标被点击 app.on('activate', () => { if (mainWindow && !mainWindow.isVisible()) { mainWindow.show(); } else if (!mainWindow) { mainWindow = createWindow(); } }); // 处理未捕获的异常 process.on('uncaughtException', (error) => { console.error('Unhandled Exception:', error); }); // 监听渲染进程发送的免责声明结果 ipcMain.on('disclaimer-response', (event, accepted) => { if (accepted) { store.set('disclaimerAccepted', true); } else { app.quit(); } }); ipcMain.on('window-control', (event, action) => { switch (action) { case 'close': if (store.get('settings')?.minimizeToTray === 'off') { app.isQuitting = true; app.quit(); } else { mainWindow.close(); } break; case 'minimize': mainWindow.minimize(); break; case 'maximize': if (mainWindow.isMaximized()) { mainWindow.unmaximize(); store.set('maximize', false); } else { mainWindow.maximize(); store.set('maximize', true); } break; } }); app.on('will-quit', () => { globalShortcut.unregisterAll(); }); ipcMain.on('save-settings', (event, settings) => { store.set('settings', settings); if (['on', 'off'].includes(settings?.autoStart)) { app.setLoginItemSettings({ openAtLogin: settings?.autoStart === 'on', path: app.getPath('exe'), }); } }); ipcMain.on('clear-settings', (event) => { store.clear(); session.defaultSession.clearCache(); session.defaultSession.clearStorageData(); const userDataPath = app.getPath('userData'); shell.openPath(userDataPath); }); ipcMain.on('custom-shortcut', (event) => { registerShortcut(); }); ipcMain.on('lyrics-data', (event, lyricsData) => { const lyricsWindow = mainWindow?.lyricsWindow; if (lyricsWindow) { lyricsWindow.webContents.send('lyrics-data', lyricsData); } // 状态栏歌词功能服务处理(仅支持Mac系统) if (process.platform === 'darwin') { statusBarLyricsService.handleLyricsData(lyricsData); } }); ipcMain.on('server-lyrics', (event, lyricsData) => { apiService.updateLyrics(lyricsData); }); // 监听桌面歌词操作 ipcMain.on('desktop-lyrics-action', (event, action) => { switch (action) { case 'previous-song': mainWindow.webContents.send('play-previous-track'); break; case 'next-song': mainWindow.webContents.send('play-next-track'); break; case 'toggle-play': mainWindow.webContents.send('toggle-play-pause'); break; case 'close-lyrics': const lyricsWindow = mainWindow.lyricsWindow; if (lyricsWindow) { lyricsWindow.close(); new Notification({ title: t('desktop-lyrics-closed'), body: t('this-time-only'), icon: path.join(__dirname, '../build/icons/logo.png') }).show(); mainWindow.lyricsWindow = null; } break; case 'display-lyrics': if (!mainWindow.lyricsWindow) createLyricsWindow(); break; } }); ipcMain.on('set-ignore-mouse-events', (event, ignore) => { const lyricsWindow = mainWindow.lyricsWindow; if (lyricsWindow) { lyricsWindow.setIgnoreMouseEvents(ignore, { forward: true }); } }); ipcMain.on('window-drag', (event, { mouseX, mouseY }) => { const lyricsWindow = mainWindow.lyricsWindow; if (!lyricsWindow) return lyricsWindow.setPosition(mouseX, mouseY) store.set('lyricsWindowPosition', { x: mouseX, y: mouseY }); }) ipcMain.on('play-pause-action', (event, playing, currentTime) => { const lyricsWindow = mainWindow.lyricsWindow; if (lyricsWindow) { lyricsWindow.webContents.send('playing-status', playing); } apiService.updatePlayerState({ isPlaying: playing, currentTime: currentTime }); setThumbarButtons(mainWindow, playing); }) ipcMain.on('open-url', (event, url) => { shell.openExternal(url); }) ipcMain.on('set-tray-title', (event, title) => { createTray(mainWindow, t('now-playing') + title); mainWindow.setTitle(title); }) ipcMain.handle('open-mv-window', (e, url) => { return (async () => { const mvWindow = createMvWindow(); try { await mvWindow.loadURL(url); mvWindow.show(); return true; } catch (error) { console.error('[open-mv-window] loadURL failed:', url, error); try { mvWindow.close(); } catch {} throw error; } })(); }); ================================================ FILE: electron/preload.cjs ================================================ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electron', { ipcRenderer: { send: (channel, ...args) => ipcRenderer.send(channel, ...args), on: (channel, listener) => ipcRenderer.on(channel, listener), once: (channel, listener) => ipcRenderer.once(channel, listener), removeListener: (channel, listener) => ipcRenderer.removeListener(channel, listener), removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel) }, platform: process.platform }); // 添加插件管理 API contextBridge.exposeInMainWorld('electronAPI', { // 插件管理 getExtensions: () => ipcRenderer.invoke('get-extensions'), getExtensionsDetailed: () => ipcRenderer.invoke('get-extensions-detailed'), reloadExtensions: () => ipcRenderer.invoke('reload-extensions'), openExtensionsDir: () => ipcRenderer.invoke('open-extensions-dir'), openExtensionPopup: (extensionId, extensionName) => ipcRenderer.invoke('open-extension-popup', extensionId, extensionName), installExtension: (extensionPath) => ipcRenderer.invoke('install-extension', extensionPath), uninstallExtension: (extensionId, extensionDir) => ipcRenderer.invoke('uninstall-extension', extensionId, extensionDir), validateExtension: (extensionPath) => ipcRenderer.invoke('validate-extension', extensionPath), getExtensionsDirectory: () => ipcRenderer.invoke('get-extensions-directory'), ensureExtensionsDirectory: () => ipcRenderer.invoke('ensure-extensions-directory'), installPluginFromZip: (zipPath) => ipcRenderer.invoke('install-plugin-from-zip', zipPath), installPluginFromUrl: (downloadUrl, extensionId = '', extensionDir = '') => ipcRenderer.invoke('install-plugin-from-url', { downloadUrl, extensionId, extensionDir, }), showOpenDialog: (options) => ipcRenderer.invoke('show-open-dialog', options), openMvWindow: (url) => ipcRenderer.invoke('open-mv-window', url), }); ================================================ FILE: electron/services/apiService.js ================================================ import { WebSocketServer } from 'ws'; class ApiService { constructor() { this.wsServer = null; this.clients = new Set(); this.currentLyrics = null; this.isPlaying = false; this.currentTime = 0; this.mainWindow = null; } init(mainWindow) { this.mainWindow = mainWindow; } // 启动 WebSocket 服务器 start() { if (this.wsServer) return; this.wsServer = new WebSocketServer({ port: 6520 }); this.wsServer.on('connection', (ws) => { this.clients.add(ws); //发送欢迎信息 ws.send(JSON.stringify({ type: 'welcome', data: '感谢接入MoeKoe Music,文档地址:https://music.moekoe.cn/' })); // 发送当前歌词 if (this.currentLyrics) { ws.send(JSON.stringify({ type: 'lyrics', data: this.currentLyrics })); } // 发送当前播放状态 ws.send(JSON.stringify({ type: 'playerState', data: { isPlaying: this.isPlaying, currentTime: this.currentTime } })); // 处理来自客户端的消息 ws.on('message', (message) => { try { const data = JSON.parse(message); console.log(data); if (data.type === 'control') { this.handleControlCommand(data.data); } } catch (e) { console.error('无效的 WebSocket 消息', e); } }); ws.on('close', () => { this.clients.delete(ws); console.log('WebSocket 客户端已断开连接'); }); }); console.log('WebSocket server running at ws://127.0.0.1:6520'); } stop() { if (this.wsServer) { for (const client of this.clients) { client.close(); } this.clients.clear(); this.wsServer.close(); this.wsServer = null; console.log('WebSocket 服务器已停止'); } } // 广播到所有客户端 broadcastToClients(data) { if(!this.wsServer) return; const message = JSON.stringify(data); for (const client of this.clients) { if (client.readyState === 1) { client.send(message); } } } handleControlCommand(data) { if (!this.mainWindow) return; switch (data.command) { case 'toggle': // 切换播放状态 this.mainWindow.webContents.send('toggle-play-pause'); break; case 'next': // 下一首 this.mainWindow.webContents.send('play-next-track'); break; case 'prev': // 上一首 this.mainWindow.webContents.send('play-previous-track'); break; } } // 更新歌词数据 updateLyrics(lyricsData) { this.currentLyrics = lyricsData; this.broadcastToClients({ type: 'lyrics', data: lyricsData }); } // 更新播放状态 updatePlayerState(state) { this.isPlaying = state.isPlaying; this.currentTime = state.currentTime; this.broadcastToClients({ type: 'playerState', data: state }); } } const apiService = new ApiService(); export default apiService; ================================================ FILE: electron/services/externalLinkHandler.js ================================================ import { shell } from 'electron'; export function shouldOpenExternally(targetUrl, currentUrl = '') { try { const target = new URL(targetUrl); if (target.protocol === 'mailto:' || target.protocol === 'tel:') { return true; } if (target.protocol !== 'http:' && target.protocol !== 'https:') { return false; } if (!currentUrl) { return true; } const current = new URL(currentUrl); return target.origin !== current.origin; } catch { return false; } } export function bindExternalLinkHandler(win, openExternalPredicate = shouldOpenExternally) { const { webContents } = win; webContents.setWindowOpenHandler(({ url }) => { if (openExternalPredicate(url, webContents.getURL())) { shell.openExternal(url); } return { action: 'deny' }; }); webContents.on('will-navigate', (event, url) => { if (openExternalPredicate(url, webContents.getURL())) { event.preventDefault(); shell.openExternal(url); } }); } ================================================ FILE: electron/services/statusBarLyricsService.js ================================================ import { ipcMain, nativeImage } from 'electron'; class StatusBarLyricsService { constructor() { this.mainWindow = null; this.store = null; this.tray = null; this.clearLyricsTimeout = null; this.lastStatusBarLyric = ''; this.lastTrayUpdateTime = 0; this.lastTrayImageHash = ''; this.TRAY_UPDATE_THROTTLE = 30; // 30ms 节流 this.getTrayCallback = null; this.createTrayCallback = null; } init(mainWindow, store, getTrayCallback, createTrayCallback) { this.mainWindow = mainWindow; this.store = store; this.getTrayCallback = getTrayCallback; this.createTrayCallback = createTrayCallback; if (process.platform === 'darwin') { this.registerListeners(); this.initializeOnStartup(); // 自动初始化 } } // 判断状态栏歌词是否开启 isStatusBarLyricsEnabled() { if (process.platform !== 'darwin') return false; const settings = this.store.get('settings') || {}; return settings.statusBarLyrics === 'on'; } registerListeners() { // 监听渲染进程生成的图片并更新 Tray ipcMain.on('update-statusbar-image', (event, dataUrl) => { this.handleUpdateImage(dataUrl); }); } // 处理歌词数据 (供 main.js 调用) handleLyricsData(lyricsData) { if (!this.isStatusBarLyricsEnabled()) { this.handleDisabledState(); return; } const currentLyric = lyricsData?.currentLyric || ''; if (currentLyric) { // 有歌词:清除防抖定时器,立即更新 if (this.clearLyricsTimeout) { clearTimeout(this.clearLyricsTimeout); this.clearLyricsTimeout = null; } if (currentLyric !== this.lastStatusBarLyric) { if (this.mainWindow?.webContents) { this.mainWindow.webContents.send('generate-statusbar-image', currentLyric); } this.lastStatusBarLyric = currentLyric; } } else { // 无歌词 (间奏):启动 5秒 防抖 if (!this.clearLyricsTimeout && this.lastStatusBarLyric !== '') { this.clearLyricsTimeout = setTimeout(() => { // 再次检查设置,确保这段时间没被关闭 if (this.isStatusBarLyricsEnabled()) { if (this.mainWindow?.webContents) { this.mainWindow.webContents.send('generate-statusbar-image', ''); // 发送空字符触发占位符 } this.lastStatusBarLyric = ''; } this.clearLyricsTimeout = null; }, 5000); } } } // 处理功能关闭时的状态清理 handleDisabledState() { if (this.lastStatusBarLyric !== '') { if (this.clearLyricsTimeout) { clearTimeout(this.clearLyricsTimeout); this.clearLyricsTimeout = null; } const tray = this.getTrayCallback ? this.getTrayCallback() : null; if (tray && !tray.isDestroyed()) { tray.setTitle(''); // 清除文字 tray.setImage(nativeImage.createEmpty()); // 清除图片 // 如果需要恢复原始 Tray 图标,这里可能需要重新调用 createTray if (this.createTrayCallback) { this.createTrayCallback(this.mainWindow); } this.lastStatusBarLyric = ''; } } } // 处理图片更新 (更新 Tray) handleUpdateImage(dataUrl) { // 节流 const now = Date.now(); if (now - this.lastTrayUpdateTime < this.TRAY_UPDATE_THROTTLE) return; // Tray 检查 const tray = this.getTrayCallback ? this.getTrayCallback() : null; if (!tray || tray.isDestroyed()) return; if (!dataUrl) return; // 哈希去重 const imageHash = dataUrl.slice(-100); if (imageHash === this.lastTrayImageHash) return; this.lastTrayUpdateTime = now; this.lastTrayImageHash = imageHash; try { const base64Data = dataUrl.replace(/^data:image\/png;base64,/, ""); const buffer = Buffer.from(base64Data, 'base64'); const image = nativeImage.createEmpty(); // 逻辑尺寸 200x22, 实际 Buffer 400x44 (@2x) image.addRepresentation({ scaleFactor: 2.0, width: 200, height: 22, buffer: buffer }); image.setTemplateImage(true); if (tray && !tray.isDestroyed()) { tray.setImage(image); tray.setTitle(''); // 确保不显示文字 } } catch (e) { console.error('[StatusBarService] Failed to set tray image:', e); } } // 应用启动时初始化状态栏歌词(私有方法) initializeOnStartup() { if (!this.isStatusBarLyricsEnabled()) { return; } // 等待窗口准备好后触发渲染 this.mainWindow.webContents.once('did-finish-load', () => { setTimeout(() => { if (this.mainWindow?.webContents) { console.log('[StatusBarLyricsService] 启动时主动触发状态栏歌词渲染'); this.mainWindow.webContents.send('generate-statusbar-image', ''); } }, 1000); }); } // 清理资源(应用退出时调用) cleanup() { // 清理定时器 if (this.clearLyricsTimeout) { clearTimeout(this.clearLyricsTimeout); this.clearLyricsTimeout = null; } // 清理 Tray(防止退出后闪烁) const tray = this.getTrayCallback ? this.getTrayCallback() : null; if (tray && !tray.isDestroyed()) { try { tray.setImage(nativeImage.createEmpty()); tray.setTitle(''); tray.destroy(); } catch (e) { console.error('[StatusBarLyricsService] Error cleaning up tray:', e); } } } } export default new StatusBarLyricsService(); ================================================ FILE: electron/services/updater.js ================================================ import { app, dialog } from 'electron'; import electronUpdater from 'electron-updater'; const { autoUpdater } = electronUpdater; import Store from 'electron-store'; import { t } from '../language/i18n.js'; const store = new Store(); autoUpdater.autoDownload = false; // 自动下载更新 autoUpdater.autoInstallOnAppQuit = false; // 自动安装更新 // 开发环境模拟打包状态 Object.defineProperty(app, 'isPackaged', { get() { return true; } }); // 设置更新服务器地址 export function setupAutoUpdater(mainWindow) { autoUpdater.setFeedURL({ provider: 'github', owner: 'iAJue', repo: 'MoeKoeMusic', releaseType: 'release' }); autoUpdater.channel = 'latest'; // 检查更新错误 autoUpdater.on('error', (error) => { console.error('Update check failed:', error.message); dialog.showMessageBox({ type: 'error', message: error.message.includes('ETIMEDOUT') ? t('update-timeout') : t('update-failed') }); }); // 检查到新版本 autoUpdater.on('update-available', (info) => { const notes = info.releaseNotes?.replace(/<[^>]*>/g, '') || t('no-release-notes'); const msg = t('new-version-msg').replace('{version}', info.version).replace('{notes}', notes); dialog.showMessageBox({ type: 'info', title: t('new-version'), message: msg, buttons: [t('update-now'), t('later')], cancelId: 1 }).then(result => { if (result.response === 0) { autoUpdater.downloadUpdate(); } }); }); // 当前已是最新版本 autoUpdater.on('update-not-available', () => { const settings = store.get('settings') || {}; if (!settings.silentCheck) { dialog.showMessageBox({ type: 'info', title: t('update-hint'), message: t('already-latest'), buttons: [t('ok')] }); } }); // 更新下载进度 autoUpdater.on('download-progress', (progressObj) => { mainWindow.setProgressBar(progressObj.percent / 100); mainWindow.webContents.send('update-progress', progressObj); }); // 更新下载完成 autoUpdater.on('update-downloaded', () => { mainWindow.setProgressBar(-1); dialog.showMessageBox({ type: 'info', title: t('update-ready'), message: t('update-ready-msg'), buttons: [t('install-now'), t('install-later')], cancelId: 1 }).then(result => { if (result.response === 0) { autoUpdater.quitAndInstall(false, true); } }); }); } // 检查更新 export function checkForUpdates(silent = false) { if (process.platform !== 'win32') { if (!silent) { dialog.showMessageBox({ type: 'info', title: t('update-hint'), message: t('non-windows-update'), buttons: [t('ok')] }); } return; } const settings = store.get('settings') || {}; if (silent) { settings.silentCheck = true; store.set('settings', settings); } else { settings.silentCheck = false; store.set('settings', settings); } autoUpdater.checkForUpdates().catch(error => { console.error('Update check error:', error); }); } ================================================ FILE: index.html ================================================ MoeKoe 萌音
================================================ FILE: nginx.conf ================================================ worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 8080; server_name localhost; location / { root /app/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:6521/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } } ================================================ FILE: package.json ================================================ { "name": "moekoemusic", "version": "1.6.1", "homepage": "https://github.com/iAJue/MoeKoeMusic", "main": "electron/main.js", "scripts": { "install-all": "npm install && cd api && npm install", "serve": "vite", "build": "vite build", "build:docker": "cross-env VITE_APP_API_URL=/api vite build", "preview": "vite preview", "electron:serve": "cross-env NODE_ENV=development electron .", "api": "node api/app.js --platform=lite --port=6521", "dev": "npm run api & npm run serve & npm run electron:serve", "build:api:win": "npm run --prefix ./api pkgwin", "build:api:linux": "npm run --prefix ./api pkglinux", "build:api:linux-arm64": "npm run --prefix ./api pkglinux-arm64", "build:api:macos": "npm run --prefix ./api pkgmacos", "electron:build:win": "run-s build:api:win \"electron:build -- --win --publish never\"", "electron:build:linux": "run-s build:api:linux \"electron:build -- --linux --publish never\"", "electron:build:linux-arm64": "run-s build:api:linux-arm64 \"electron:build -- --linux --publish never\"", "electron:build:macos": "run-s build:api:macos \"electron:build -- --mac --x64 --arm64 --publish never\"", "electron:build:macos:universal": "run-s build:api:macos \"electron:build -- --mac --universal --publish never\"", "electron:build:macos:x64": "run-s build:api:macos \"electron:build -- --mac --x64 --publish never\"", "electron:build:macos:arm64": "run-s build:api:macos \"electron:build -- --mac --arm64 --publish never\"", "electron:build": "cross-env NODE_ENV=production electron-builder" }, "engines": { "node": ">=20" }, "build": { "publish": { "provider": "github", "owner": "iAJue", "repo": "MoeKoeMusic" }, "compression": "maximum", "extraResources": [ { "from": "build/icons/", "to": "icons/", "filter": [ "**/*" ] } ], "extraFiles": [ { "from": "api/bin", "to": "api/", "filter": [ "**/*" ] }, { "from": "public/assets", "to": "assets" } ], "appId": "cn.MoeKoe.Music", "productName": "MoeKoe Music", "copyright": "© 2024 MoeKoe", "directories": { "output": "dist_electron" }, "files": [ "dist/**/*", "electron/**/*", "package.json" ], "icon": "build/icons/icon", "protocols": [ { "name": "MoeKoe Music Protocol", "schemes": [ "moekoe" ] } ], "win": { "icon": "build/icons/icon.ico", "target": [ { "target": "nsis", "arch": [ "x64", "ia32" ] } ], "artifactName": "MoeKoe_Music_Setup_v${version}.${ext}" }, "nsis": { "oneClick": false, "allowToChangeInstallationDirectory": true, "perMachine": true, "installerIcon": "build/icons/icon.ico", "uninstallerIcon": "build/icons/icon.ico", "installerHeaderIcon": "build/icons/icon.ico", "createDesktopShortcut": true, "createStartMenuShortcut": true, "shortcutName": "MoeKoe Music", "include": "build/installer.nsh", "license": "build/license.txt", "language": "2052", "warningsAsErrors": true }, "mac": { "x64ArchFiles": "*", "icon": "build/icons/icon.icns", "target": [ "dmg" ], "identity": null, "artifactName": "${productName}-${arch}.${ext}", "protocols": [ { "name": "MoeKoe Music Protocol", "schemes": [ "moekoe" ] } ] }, "dmg": { "sign": false }, "linux": { "icon": "build/icons/linux_256x256.png", "target": [ "AppImage", "deb" ], "category": "Utility", "artifactName": "${productName}.${ext}" } }, "type": "module", "author": { "name": "MoeJue", "email": "MoeJue@qq.com" }, "blog": "MoeJue.cn", "license": "MIT", "description": "MoeKoe Music", "devDependencies": { "@types/node": "^24.10.1", "@vitejs/plugin-vue": "^6.0.2", "@vue/compiler-sfc": "^3.5.12", "cross-env": "^7.0.3", "electron": "^39.0.0", "electron-builder": "^25.1.8", "esbuild": "0.25.11", "npm-run-all": "^4.1.5", "vite": "^7.2.6", "vite-plugin-pwa": "^1.2.0", "vue-loader": "^17.3.1", "wait-on": "^8.0.1" }, "dependencies": { "adm-zip": "^0.5.16", "electron-is-dev": "^3.0.1", "electron-log": "^5.2.0", "electron-store": "^10.0.0", "electron-updater": "^6.6.2", "music-metadata": "^11.7.3", "pinia": "^3.0.0", "pinia-plugin-persistedstate": "^4.1.1", "tree-kill": "^1.2.2", "vue": "^3.5.12", "vue-i18n": "^10.0.4", "vue-router": "^4.4.5", "vue3-virtual-scroller": "^0.2.3", "ws": "^8.18.1" } } ================================================ FILE: src/App.vue ================================================ ================================================ FILE: src/assets/style/PlayerControl.css ================================================ #lyrics-container { height: 75vh; overflow: hidden; display: flex; justify-content: center; padding: 20px; border-radius: 8px; margin-top: 80px; position: relative; width: 60%; } #lyrics { font-size: 24px; line-height: 1.8; white-space: pre-wrap; color: #666; transition: transform 0.6s ease; } .line-group { margin-bottom: 12px; } .line { border: 10px; display: block; border-radius: 10px; padding: 0 10px; width: fit-content; transition: background-color .3s ease-in; } .line.left { text-align: left; } .line.center { text-align: center; margin: 0 auto; } .line.translated { color: #e3e3e3b1; } .line::after { content: '\f04b'; font-family: 'Font Awesome 6 Free'; font-weight: 900; float: right; display: block; opacity: 0; color: #e3e3e392; margin-left: 10px; transition: opacity .3s ease-in; } .line:hover { cursor: pointer; background-color: #e3e3e33b; } .line:hover::after { opacity: 1; } .char { display: inline-block; color: transparent; background: linear-gradient(to right, var(--primary-color) 50%, #e3e3e3e8 50%); background-size: 200% 100%; background-position: 100%; -webkit-background-clip: text; background-clip: text; transition: background-position 0.6s ease; } .highlight { background-position: 0%; } .player-container { display: flex; flex-direction: column; align-items: center; width: 100%; position: fixed; bottom: 0px; background: #FFF; box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); border-radius: 10px; background: rgba(255, 255, 255, .7); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); z-index: 1; } .player-bar { display: flex; align-items: center; padding: 10px; width: 100%; max-width: 880px; } .album-art { width: 60px; height: 60px; border-radius: 5px; margin-right: 15px; background-color: #ddd; display: flex; justify-content: center; align-items: center; cursor: pointer; } .album-art img { width: 64px; height: 64px; border-radius: 5px; } .album-art i { font-size: 24px; color: #666; } .song-info { flex: 0 1 300px; cursor: pointer; margin-right: auto; min-width: 0; max-width: 300px; } .song-title-row { display: inline-flex; align-items: center; gap: 8px; max-width: 300px; min-width: 0; vertical-align: top; margin-bottom: 5px; } .song-title-row .song-title { margin-bottom: 0; } .song-title { font-weight: bold; margin-bottom: 5px; flex: 0 1 auto; min-width: 0; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .quality-badge { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid; border-radius: 999px; font-size: 11px; background: transparent; color: var(--color-primary); border-color: var(--color-primary); background-color: color-mix(in srgb, var(--color-primary) 8%, transparent); } .quality-badge.clickable { cursor: pointer; } .quality-menu-wrapper { position: relative; flex-shrink: 0; transform: translateY(-2px); } .quality-menu { position: absolute; bottom: calc(100% + 8px); left: 0; min-width: 110px; padding: 6px; border-radius: 10px; background: rgba(255, 255, 255, 0.96); box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12); border: 1px solid rgba(0, 0, 0, 0.06); z-index: 10; } .quality-menu-item { width: 100%; border: none; background: transparent; text-align: left; border-radius: 8px; padding: 8px 10px; font-size: 13px; color: #303133; cursor: pointer; } .quality-menu-item:hover, .quality-menu-item.active { background: var(--color-primary-light); color: var(--color-primary); } .quality-menu-item.disabled { color: #909399; cursor: default; } .fa-volume-up { width: 10px; } .artist { font-size: 0.9em; color: #c3c3c3; width: auto; max-width: 300px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .controls { display: flex; align-items: center; justify-content: center; flex-grow: 1; } .control-btn { background: none; border: none; font-size: 24px; cursor: pointer; padding: 0 10px; color: #333; } .progress-bar { width: 100%; height: 6px; background-color: #ddd; position: relative; border-radius: 5px; overflow: visible; cursor: pointer; } .progress-bar::before { content: ''; position: absolute; top: -10px; left: 0; right: 0; bottom: -10px; cursor: pointer; } .progress { width: 30%; height: 100%; background-color: var(--primary-color); position: absolute; transition: width 0.2s ease; } .progress-handle { width: 12px; height: 12px; background-color: #fff; border: 2px solid var(--primary-color); border-radius: 50%; position: absolute; top: 50%; transform: translate(-50%, -50%); opacity: 0; transition: opacity 0.2s ease; z-index: 2; cursor: grab; pointer-events: auto; } .progress-bar:active .progress-handle { transform: translate(-50%, -50%) scale(1.2); cursor: grabbing; } .progress-handle.dragging, .progress-bar:hover .progress-handle { opacity: 1; } .progress-bar:hover .progress { background-color: var(--primary-color); opacity: 0.8; } .extra-controls { display: flex; align-items: center; margin-left: auto; gap: 4px; } .extra-btn { background: none; border: none; font-size: 16px; cursor: pointer; padding: 0 8px; color: #666; } .volume-control { display: flex; align-items: center; gap: 10px; } .volume-control i { font-size: 18px; color: #666; } .volume-slider { width: 100px; height: 6px; background-color: #ddd; border-radius: 3px; position: relative; overflow: hidden; cursor: pointer; } .volume-progress { height: 100%; background-color: var(--primary-color); position: absolute; left: 0; top: 0; border-radius: 3px; } /* 全屏歌词界面样式 */ .lyrics-bg { position: fixed; bottom: 0; left: 0; right: 0; top: 0; z-index: 99; background-repeat: no-repeat; background-position: center; background-size: cover; } .lyrics-screen { position: fixed; bottom: 0; left: 0; right: 0; top: 0; backdrop-filter: blur(18px); color: white; display: flex; justify-content: space-between; padding: 20px; background: #1616169c; } .close-btn { position: absolute; top: 30px; right: 100px; font-size: 24px; cursor: pointer; color: white; z-index: 99; text-align: right; width: 100%; } .left-section { display: flex; flex-direction: column; align-items: center; width: 40%; margin-top: 135px; margin-left: 20px; } .album-art-container { cursor: pointer; transition: transform 0.3s ease; position: relative; } .album-art-container:hover { transform: scale(1.02); } /* 普通封面模式 */ .album-art-large { position: relative; animation: fadeInScale 0.6s ease-out; } .album-art-large img { width: 400px; height: 400px; border-radius: 10px; z-index: 9; transition: all 0.3s ease; } /* 唱片播放器模式 */ .vinyl-player { position: relative; width: 450px; height: 450px; display: flex; align-items: center; justify-content: center; animation: fadeInScale 0.6s ease-out; } /* 淡入缩放动画 */ @keyframes fadeInScale { 0% { opacity: 0; transform: scale(0.8); } 100% { opacity: 1; transform: scale(1); } } /* 唱片圆盘 */ .vinyl-disc { position: relative; width: 400px; height: 400px; border-radius: 50%; background: radial-gradient(circle at center, #1a1a1a 0%, #1a1a1a 35%, #2a2a2a 35%, #2a2a2a 36%, #1a1a1a 36%, #1a1a1a 100%); box-shadow: 0 0 30px rgba(0, 0, 0, 0.8), inset 0 0 20px rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; } .vinyl-disc::before { content: ''; position: absolute; width: 100%; height: 100%; border-radius: 50%; background: repeating-radial-gradient(circle at center, transparent 0px, transparent 2px, rgba(255, 255, 255, 0.03) 2px, rgba(255, 255, 255, 0.03) 4px ); } /* .vinyl-disc::after { content: ''; position: absolute; width: 80px; height: 80px; border-radius: 50%; background: radial-gradient(circle, #8B4513 0%, #654321 50%, #3d2817 100%); box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5); z-index: 10; } */ /* 唱片封面 */ .vinyl-cover { width: 240px; height: 240px; border-radius: 50%; object-fit: cover; z-index: 5; } /* 唱片旋转动画 - 始终应用动画 */ .vinyl-disc { animation: vinylRotate 5s linear infinite; animation-play-state: paused; } .vinyl-disc.rotating { animation-play-state: running; } @keyframes vinylRotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* 磁头 */ .tonearm { position: absolute; top: -50px; right: 20px; width: 160px; height: 198px; background: url('../assets/images/head.png') no-repeat center; background-size: contain; transform-origin: 85% 15%; transform: rotate(-30deg); transition: transform 0.6s ease-in-out; z-index: 15; pointer-events: none; animation: tonearmSlideIn 0.8s ease-out; } /* 磁头滑入动画 */ @keyframes tonearmSlideIn { 0% { opacity: 0; transform: rotate(-30deg) translateX(50px); } 100% { opacity: 1; transform: rotate(-30deg) translateX(0); } } /* 磁头播放状态 */ .tonearm.playing { transform: rotate(0deg); } .song-details { text-align: center; margin-top: 20px; } .player-controls { display: flex; gap: 15px; margin-top: 20px; } .progress-bar-container { display: flex; align-items: center; width: 100%; margin: 10px 0; } .current-time, .duration { color: white; font-size: 12px; } .progress-bar { flex-grow: 1; height: 2px; background-color: #555; border-radius: 5px; margin: 0 10px; position: relative; } .progress { width: 50%; height: 100%; background-color: var(--primary-color); border-radius: 5px; } .player-controls { display: flex; justify-content: center; align-items: center; gap: 15px; margin-top: 10px; } .player-controls .control-btn { color: #fff; } .fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; } .volume-control { display: flex; align-items: center; position: relative; } .volume-slider { width: 100px; margin-left: 10px; position: relative; } .volume-slider input[type="range"] { width: 100%; -webkit-appearance: none; appearance: none; height: 5px; background: transparent; position: relative; z-index: 1; pointer-events: none; /* 禁止对 input 的点击事件 */ } .volume-slider input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 12px; height: 12px; border-radius: 50%; background: #4899f8; cursor: pointer; pointer-events: auto; /* 允许 thumb 的拖动事件 */ } .volume-slider input[type="range"]::-moz-range-thumb { width: 12px; height: 12px; border-radius: 50%; background: #4899f8; cursor: pointer; } .album-art-large .miku { position: absolute; height: 419px; width: 401px; } .album-art-large .miku2 { position: absolute; height: 452px; width: 404px; } .album-art-large .miku3 { position: absolute; height: 563px; } .no-lyrics { color: var(--primary-color); margin: auto; font-size: 2em; } .loop-icon { position: relative; display: inline-block; } .loop-icon sup { position: absolute; font-size: 0.6em; } /* 全屏歌词界面的进度条样式 */ .lyrics-screen .progress-bar { flex-grow: 1; height: 3px; background-color: rgba(255, 255, 255, 0.3); border-radius: 5px; margin: 0 10px; position: relative; overflow: visible; } .lyrics-screen .progress-bar::before { content: ''; position: absolute; top: -12px; left: 0; right: 0; bottom: -12px; cursor: pointer; } .progress-handle.dragging { opacity: 1; } .time-tooltip { position: absolute; top: -25px; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.7); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; pointer-events: none; z-index: 10; } /* 歌词界面的时间提示样式 */ .lyrics-screen .time-tooltip { backdrop-filter: blur(4px); } /* 高潮点的样式 */ .climax-point { position: absolute; width: 6px; height: 6px; border-radius: 50%; top: 67%; transform: translate(-50%, -50%); z-index: 1; pointer-events: none; } /* 普通界面的高潮点样式 */ .player-container .climax-point { background-color: var(--primary-color); box-shadow: 0 0 4px var(--primary-color); } /* 歌词界面的高潮点样式 */ .lyrics-screen .climax-point { background-color: #ff69b4; box-shadow: 0 0 4px #ff69b4; } /* 收藏按钮 */ .like-btn:hover { opacity: 1; transform: scale(1.1); } .like-btn.active { color: #ff4081; opacity: 1; } .playback-speed { position: relative; display: inline-block; } .speed-menu { position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: var(--background-color); border: 1px solid var(--background-color); border-radius: 4px; margin-bottom: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); z-index: 1000; } .speed-option { padding: 6px 16px; cursor: pointer; white-space: nowrap; color: var(--text-color); transition: background-color 0.2s; } .speed-option:hover { background-color: var(--secondary-color); } .speed-option.active { background-color: var(--primary-color); color: var(--primary-text-color); } .speed-menu::after { content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border: 6px solid transparent; border-top-color: var(--background-color);; } .close-btn { cursor: pointer; font-size: 24px; color: #fff; opacity: 0.8; transition: opacity 0.3s; } .close-btn:hover { opacity: 1; } .lyrics-mode-btn { cursor: pointer; font-size: 20px; color: #fff; opacity: 0.8; transition: opacity 0.3s; position: absolute; right: 20px; top: 10px; } .lyrics-mode-btn:hover { opacity: 1; } .line.romanized { font-size: 0.9em; opacity: 0.9; margin-top: 5px; color: #e3e3e3b1; } .slide-up-enter-active { transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); } .slide-up-leave-active { transition: all 0.3s cubic-bezier(0.4, 0, 0.6, 1); } .slide-up-enter-from { transform: translateY(100%); opacity: 0; } .slide-up-leave-to { transform: translateY(100%); opacity: 0; } .slide-up-enter-to, .slide-up-leave-from { transform: translateY(0); opacity: 1; } /* 封面模式切换过渡动画 */ .cover-fade-enter-active { transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); } .cover-fade-leave-active { transition: all 0.3s cubic-bezier(0.4, 0, 1, 1); } .cover-fade-enter-from { opacity: 0; transform: scale(0.9) translateY(20px); } .cover-fade-leave-to { opacity: 0; transform: scale(0.9) translateY(-20px); } .cover-fade-enter-to, .cover-fade-leave-from { opacity: 1; transform: scale(1) translateY(0); } ================================================ FILE: src/assets/themes/dark.css ================================================ /* 暗黑模式样式 */ html.dark { background-color: #121212; color: #e1e1e1; filter: brightness(0.8); mix-blend-mode: multiply; } html.dark body { background-color: #121212; } html.dark .settings-page { background-color: #121212; } html.dark .settings-sidebar { background-color: #1a1a1a; border-right-color: #333; } html.dark .sidebar-item { color: #e1e1e1; } html.dark .sidebar-item:hover:not(.active) { background-color: #2a2a2a; } html.dark .sidebar-item.active { background-color: rgba(255, 105, 180, 0.15); color: var(--primary-color); } html.dark .setting-section h3 { color: #e1e1e1; border-bottom-color: #333; } html.dark .setting-card { background-color: #1d1d1d; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } html.dark .setting-card:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } html.dark .setting-card-header { color: #e1e1e1; } html.dark .setting-card-value { background-color: #2a2a2a; color: #e1e1e1; border: 1px solid #333; } html.dark .refresh-hint { color: #ff6b6b; } html.dark .modal-content { background-color: #1d1d1d; box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4); } html.dark .modal-content h3 { color: #e1e1e1; } html.dark .modal-content li { background-color: #2a2a2a; color: #e1e1e1; } html.dark .modal-content li:hover { background-color: #363636; } html.dark .modal-content button { background-color: #2a2a2a; color: #e1e1e1; } html.dark .modal-content button:hover { background-color: #363636; } /* 播放器控件 */ html.dark .player-container { background-color: rgba(24, 24, 24) !important; border-top: 1px solid #333 !important; } html.dark .player-container .control-button { color: #e1e1e1 !important; } html.dark .player-container .control-button:hover { color: var(--primary-color) !important; } html.dark .player-container .song-info { color: #e1e1e1 !important; } html.dark .player-container .song-title { color: #e1e1e1 !important; } html.dark .player-container .song-artist, html.dark .extension-item h4 { color: #999 !important; } html.dark .player-container .progress-bar { background-color: #4a4a4a !important; } html.dark .player-container .progress-bar .loaded { background-color: #666 !important; } /* 卡片和列表项 */ html.dark .playlist-item, html.dark .song-item { background-color: #1d1d1d; border-color: #333; } /* 输入框 */ html.dark input { color: #e1e1e1; border-color: #333; background: black; } /* 滚动条 */ html.dark ::-webkit-scrollbar-track { background-color: #2a2a2a; } html.dark ::-webkit-scrollbar-thumb { background-color: #4a4a4a; } /* 头部导航 */ html.dark header { background-color: rgba(24, 24, 24) !important; border-bottom: 1px solid #333; } html.dark header .logo { color: #e1e1e1; } html.dark header .nav-item { color: #e1e1e1; } html.dark header .nav-item:hover, html.dark header .nav-item.active { color: var(--primary-color); } html.dark header .window-controls { color: #e1e1e1; } html.dark header .window-controls span:hover { background-color: #363636; } html.dark header .search-box { background-color: #2a2a2a; border-color: #333; } html.dark header .search-box input { color: #e1e1e1; } html.dark header button { background-color: transparent !important; border: none !important; color: #999 !important; } html.dark header .nav-arrow:disabled i { color: #353535 !important; } html.dark header .search-box input::placeholder { color: #999; } html.dark header .user-avatar { border-color: #333; } html.dark .primary-btn , html.dark .more-btn-container .more-btn { border: none !important; } /* 按钮 */ html.dark button { background-color: #2a2a2a !important; color: #e1e1e1 !important; border: 1px solid #373434 !important; } html.dark button:hover { background-color: #363636 !important; } /* 链接 */ html.dark a { color: #e1e1e1; } /* 主内容区 */ html.dark main { background-color: #121212; } /* 音乐卡片 */ html.dark .music-card { background-color: #1d1d1d; border-color: #333; border-radius: 5px; } html.dark .music-card .title { color: #e1e1e1; } html.dark .music-card .artist { color: #999; } html.dark .music-card:hover { background-color: #2a2a2a; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); } html.dark .music-card .play-count { color: #999; } html.dark .music-card .description { color: #999; } /* PlaylistDetail 页面样式 */ html.dark .detail-page .header .description { color: #777; } html.dark .playlist-info .description { color: #999; } html.dark .track-list .li { border-bottom: none; color: #999; } html.dark .track-list .li:hover { background-color: #2a2a2a; } html.dark .playlist-info .meta { color: #999; } html.dark .song-list { background-color: #121212; } html.dark .song-item:hover { background-color: #2a2a2a; } html.dark .song-item .song-name { color: #e1e1e1; } html.dark .song-item .album-name, html.dark .song-item .song-album, html.dark .song-item .song-duration, html.dark .song-item .song-actions { color: #999; } html.dark .song-item .song-actions button { color: #e1e1e1; } html.dark .more-btn-container .dropdown-menu { background-color: #1d1d1d; border: none; color: #e1e1e1; } html.dark .more-btn-container .dropdown-menu li:hover { background-color: #2a2a2a; } html.dark .song-item .song-actions button:hover { color: var(--primary-color); background-color: #363636; } html.dark .sq-icon { color: #2f74a5; } html.dark .vip-icon { color: #b86222; } html.dark .context-menu, html.dark .context-menu ul { background-color: #1d1d1d; border:none; border-radius: 5px; color: #999; } html.dark .context-menu li:hover { background-color: #2a2a2a !important; } html.dark .controls .control-btn, html.dark .player-controls .control-btn, html.dark .extra-controls .extra-btn { background-color: transparent !important; color: #999 !important; border: none!important; } html.dark .search-bar input, html.dark .search-input { background-color: #2a2a2a !important; color: #e1e1e1 !important; border: 1px solid #373434 !important; } html.dark .profile-menu { background-color: #333333; border:none; border-radius: 5px; } html.dark .profile-menu li a{ color: #bcbcbc; } html.dark .profile-menu li a:hover { background-color: #484848 !important; } html.dark .queue-popup{ background-color: #1d1d1d; color: #e1e1e1; } html.dark .queue-popup h3{ color: #e1e1e1; } html.dark .queue-popup li { border: none; } html.dark .queue-popup .queue-play-btn { background-color: transparent !important; border: none !important; } html.dark .modal-content{ color: #e1e1e1; } html.dark .search-results{ background-color: #121212; color: #e1e1e1; } html.dark .search-results li{ border: none; } html.dark .search-results li:hover{ background-color: #2a2a2a; } html.dark .skeleton-grid .skeleton-card { background-color: #1d1d1d; } html.dark .skeleton-grid .skeleton-title, html.dark .skeleton-grid .skeleton-text, html.dark .skeleton-grid .skeleton-image { background-color: #2a2a2a; } html.dark .skeleton-loader .skeleton-item { background-color: #1d1d1d; } html.dark .skeleton-loader .skeleton-cover, html.dark .skeleton-loader .skeleton-line { background-color: #2a2a2a; } .radio-card .decorative-box { box-shadow: -5px -5px 10px rgb(163 163 163 / 0%), 5px 5px 10px rgba(255, 255, 255, 0.1), inset 2px 2px 5px rgb(0 0 0 / 18%), inset -2px -2px 5px rgba(255, 255, 255, 0.05) } html.dark .radio-card .radio-content .radio-title, html.dark .ranking-title, html.dark .ranking-description, html.dark .radio-subtitle { color: #e1e1e1; } html.dark .radio-disc { box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2), inset 0 0 20px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(255, 255, 255, 0.8); padding: 2px; } html.dark .ranking-container .rank-selector { background: #2a2a2a; } html.dark .ranking-container .rank-chip { background: #2a2a2a; border:1px solid #333; } html.dark .ranking-container .rank-chip:hover { background: #363636; } html.dark .ranking-container .ranking-item { background: #2a2a2a!important; } html.dark .ranking-container .song-list { scrollbar-color: #2a2a2a #1d1d1d; background:linear-gradient(to right, rgba(100, 61, 73, 0.133), transparent)!important; scrollbar-width: thin; } html.dark .ranking-container .song-list .song-item { margin-bottom: 2px; } html.dark .queue-play-btn{ border: none !important; background-color: rgba(0, 0, 0, 0.0) !important; } html.dark .playlist-select-list li{ background-color: inherit; } html.dark .tab-button{ background-color: #121212!important; border: none!important; } html.dark .search-tabs{ border-bottom: 1px solid #333; } html.dark .playlist-card , html.dark .album-card , html.dark .artist-card{ background-color: #1d1d1d; } html.dark .playlist-card .playlist-info .playlist-name , html.dark .album-card .album-info .album-name , html.dark .music-card .album-info, html.dark .artist-info .artist-name { color: #e1e1e1; } html.dark .playlist-card .playlist-info .playlist-description { color: #999; } html.dark .playlist-card .playlist-meta .meta-item , html.dark .album-card .album-meta .meta-item, html.dark .artist-card .artist-counts .count-item { background-color: #1d1d1d; border: 1px solid #333; } html.dark .artist-card .artist-avatar { background: #1d1d1d; } html.dark .grid-skeleton .skeleton-grid .skeleton-grid-card { background: #1d1d1d; } html.dark .skeleton-grid-card .skeleton-avatar , html.dark .skeleton-grid-card .skeleton-line,html.dark .skeleton-grid-card .skeleton-cover { background: #2a2a2a; } html.dark .skeleton-container .song-skeleton .skeleton-cover, html.dark .skeleton-container .song-skeleton .skeleton-line { background: #1d1d1d; } html.dark .result-item { border: none; } html.dark .result-item:hover { background-color: #2a2a2a; } html.dark .modal { background-color: #1d1d1d; } html.dark .settings-page .modal{ background-color: rgba(0, 0, 0, 0.6); } html.dark .shortcut-modal .shortcut-modal-content { background-color: #1d1d1d; color: #e1e1e1; } html.dark .shortcut-modal .shortcut-input{ background-color: #2a2a2a; } html.dark .shortcut-modal .shortcut-item { border-bottom: 1px solid #333; } html.dark .scale-slider-container , html.dark .compatibility-option{ background-color: #2a2a2a; } html.dark .track-list-header-row{ color: #999; } html.dark .batch-actions-menu{ background-color: #1d1d1d; border: none; color: #999; } html.dark .batch-actions-menu li:hover{ background-color: #2a2a2a; border-radius: 5px; } /* 登录页面暗黑模式样式 */ html.dark .login-page { background-color: #121212; } html.dark .login-container { background-color: #1d1d1d; border: 1px solid #333; box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3); } html.dark h2 { color: #e1e1e1; } html.dark .form-input { background-color: #2a2a2a; border-color: #444; color: #e1e1e1; } html.dark .form-input:focus { border-color: var(--primary-color); background-color: #333; } html.dark .clear-button { color: #777; } html.dark .clear-button:hover { color: #aaa; background-color: rgba(255, 255, 255, 0.1); } html.dark .segmented-control { border-color: #444; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } html.dark .segmented-button { background: #2a2a2a; color: #aaa; } html.dark .segmented-button:not(:last-child)::after { background-color: #444; } html.dark .segmented-button:hover:not(.active) { background-color: #333; color: #e1e1e1; } html.dark .segmented-button.active { box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); } html.dark .disclaimer { background-color: #2a2a2a; border-top-color: #444; color: #aaa; } html.dark .qr-login p { color: #aaa; } html.dark .qr-code { border-color: #444; background-color: #2a2a2a; box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); } html.dark .empty-container, html.dark .extension-item { background-color: #2a2a2a; border-color: #444; box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); } html.dark .empty-text { color: #aaa; } html.dark .register-link { color: #aaa; } html.dark .register-link a:hover { background-color: rgba(255, 255, 255, 0.1); } html.dark .primary-button, html.dark .append-button { box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); } html.dark .primary-button:hover:not(:disabled), html.dark .append-button:hover:not(:disabled) { box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4); } html.dark .primary-button:active:not(:disabled) { box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); } html.dark .sort-selector{ border: 1px solid #444; } html.dark .section-content{ color: #777; } ================================================ FILE: src/components/AlbumGrid.vue ================================================ ================================================ FILE: src/components/ArtistGrid.vue ================================================ ================================================ FILE: src/components/BirthdayEasterEgg.vue ================================================ ================================================ FILE: src/components/ContextMenu.vue ================================================ ================================================ FILE: src/components/CustomModal.vue ================================================ ================================================ FILE: src/components/Disclaimer.vue ================================================ ================================================ FILE: src/components/ExtensionManager.vue ================================================ ================================================ FILE: src/components/Header.vue ================================================ ================================================ FILE: src/components/MessageNotification.vue ================================================ ================================================ FILE: src/components/PlayerControl.vue ================================================ ================================================ FILE: src/components/PlaylistGrid.vue ================================================ ================================================ FILE: src/components/PlaylistSelectModal.vue ================================================ ================================================ FILE: src/components/QueueList.vue ================================================ ================================================ FILE: src/components/StatusBarLyrics.vue ================================================ ================================================ FILE: src/components/TitleBar.vue ================================================ ================================================ FILE: src/components/player/AudioController.js ================================================ import { ref } from 'vue'; export default function useAudioController({ onSongEnd, updateCurrentTime }) { const audio = new Audio(); // 设置 crossOrigin 以支持 Web Audio API 跨域访问 audio.crossOrigin = 'anonymous'; const playing = ref(false); const isMuted = ref(false); const volume = ref(66); const playbackRate = ref(1.0); // Web Audio API 用于动态增益 const audioContext = ref(null); const sourceNode = ref(null); const gainNode = ref(null); const currentLoudnessGain = ref(1.0); // 当前响度增益系数 const loudnessNormalizationEnabled = ref(false); // 响度规格化开关,默认关闭 const webAudioInitialized = ref(false); // 标记 Web Audio 是否已初始化 // 初始化 Web Audio API - 只在启用响度规格化时调用,并且应该在用户交互时调用 const initWebAudio = () => { try { // 只有启用时才初始化 Web Audio API if (!loudnessNormalizationEnabled.value) { console.log('[AudioController] 响度规格化未启用,使用原生音频播放'); return false; } if (!audioContext.value) { audioContext.value = new (window.AudioContext || window.webkitAudioContext)(); console.log('[AudioController] Web Audio API 初始化成功'); console.log('[AudioController] AudioContext 初始状态:', audioContext.value.state); // 立即创建音频图连接 try { sourceNode.value = audioContext.value.createMediaElementSource(audio); gainNode.value = audioContext.value.createGain(); sourceNode.value.connect(gainNode.value); gainNode.value.connect(audioContext.value.destination); // 设置初始增益 gainNode.value.gain.setValueAtTime(currentLoudnessGain.value, audioContext.value.currentTime); webAudioInitialized.value = true; console.log('[AudioController] Web Audio 音频图创建完成'); console.log('[AudioController] 初始增益值:', gainNode.value.gain.value); } catch (sourceError) { console.error('[AudioController] 创建音频源失败(可能是CORS问题):', sourceError); // 清理已创建的资源 if (audioContext.value) { audioContext.value.close(); audioContext.value = null; } webAudioInitialized.value = false; console.warn('[AudioController] 由于CORS限制,响度规格化已禁用,使用原生播放'); return false; } } return true; } catch (error) { console.error('[AudioController] Web Audio API 初始化失败:', error); webAudioInitialized.value = false; return false; } }; // 应用响度规格化 const applyLoudnessNormalization = (loudnessData) => { // 如果 Web Audio 未初始化,不做任何处理 if (!webAudioInitialized.value || !loudnessNormalizationEnabled.value) { console.log('[AudioController] Web Audio 未启用,跳过响度规格化'); return; } console.log('[AudioController] 开始应用响度规格化, loudnessData:', loudnessData); if (!loudnessData) { console.log('[AudioController] 歌曲无响度规格化数据,使用默认增益'); currentLoudnessGain.value = 1.0; // 更新 gainNode if (gainNode.value && audioContext.value) { gainNode.value.gain.setValueAtTime(1.0, audioContext.value.currentTime); console.log('[AudioController] 重置音频增益为 1.0, 当前增益值:', gainNode.value.gain.value); } return; } try { const { volume, volumeGain, volumePeak } = loudnessData; // 响度规格化算法 // volume: LUFS 值 (例如 -11.4 表示音频响度为 -11.4 LUFS) // volumeGain: 建议的增益调整值 (dB) // volumePeak: 峰值 (0-1) // 目标响度为 -14 LUFS (Spotify 标准) const targetLoudness = -14.0; const loudnessAdjustment = targetLoudness - volume; // 计算增益系数 (dB 转线性) // gain = 10^(dB/20) let gainAdjustment = Math.pow(10, loudnessAdjustment / 20); // 应用 volumeGain (如果 API 已经提供了增益建议) if (volumeGain !== 0) { gainAdjustment *= Math.pow(10, volumeGain / 20); } // 防止削波: 如果应用增益后峰值会超过 1.0,则限制增益 if (volumePeak > 0 && volumePeak * gainAdjustment > 0.95) { gainAdjustment = 0.95 / volumePeak; console.log('[AudioController] 限制增益以防止削波'); } // 限制增益范围 (0.1 到 3.0,即 -20dB 到 +9.5dB) currentLoudnessGain.value = Math.max(0.1, Math.min(3.0, gainAdjustment)); console.log('[AudioController] 响度规格化:', { volume: volume + ' LUFS', volumeGain: volumeGain + ' dB', volumePeak, adjustment: loudnessAdjustment.toFixed(2) + ' dB', finalGain: (20 * Math.log10(currentLoudnessGain.value)).toFixed(2) + ' dB', gainMultiplier: currentLoudnessGain.value.toFixed(3) }); // 应用新的增益 if (gainNode.value && audioContext.value) { gainNode.value.gain.setValueAtTime(currentLoudnessGain.value, audioContext.value.currentTime); console.log('[AudioController] 增益已应用, 当前增益值:', gainNode.value.gain.value); } } catch (error) { console.error('[AudioController] 应用响度规格化失败:', error); currentLoudnessGain.value = 1.0; // 发生错误时也要重置增益 if (gainNode.value && audioContext.value) { gainNode.value.gain.setValueAtTime(1.0, audioContext.value.currentTime); } } }; // 确保 AudioContext 处于运行状态(如果未初始化则先初始化,然后恢复) const ensureAudioContextRunning = async () => { // 如果启用了响度规格化但还未初始化 Web Audio,先初始化 if (loudnessNormalizationEnabled.value && !webAudioInitialized.value) { console.log('[AudioController] 首次播放,初始化 Web Audio API...'); if (!initWebAudio()) { console.warn('[AudioController] Web Audio API 初始化失败,使用原生播放'); return; } } // 如果已初始化,确保 AudioContext 处于运行状态 if (webAudioInitialized.value && audioContext.value) { console.log('[AudioController] 检查 AudioContext 状态:', audioContext.value.state); if (audioContext.value.state === 'suspended') { console.log('[AudioController] AudioContext 处于 suspended,尝试恢复...'); try { await audioContext.value.resume(); console.log('[AudioController] AudioContext 已恢复为:', audioContext.value.state); } catch (error) { console.error('[AudioController] 恢复 AudioContext 失败:', error); } } else { console.log('[AudioController] AudioContext 状态正常:', audioContext.value.state); } // 验证音频图连接 if (gainNode.value) { console.log('[AudioController] 当前增益节点值:', gainNode.value.gain.value); } } }; // 切换响度规格化 const toggleLoudnessNormalization = (enabled) => { const previousState = loudnessNormalizationEnabled.value; loudnessNormalizationEnabled.value = enabled; // 保存到 settings const settings = JSON.parse(localStorage.getItem('settings') || '{}'); settings.loudnessNormalization = enabled ? 'on' : 'off'; localStorage.setItem('settings', JSON.stringify(settings)); // 如果 Web Audio 已经初始化,只能调整增益,无法完全禁用 if (webAudioInitialized.value) { if (gainNode.value && audioContext.value) { const newGain = enabled ? currentLoudnessGain.value : 1.0; gainNode.value.gain.setValueAtTime(newGain, audioContext.value.currentTime); console.log('[AudioController] 响度规格化', enabled ? '已启用' : '已禁用', ', 增益:', newGain); } } else if (enabled && !previousState) { // 如果之前未启用,现在要启用,需要初始化 Web Audio console.warn('[AudioController] 启用响度规格化需要刷新页面才能生效'); // 尝试初始化(但可能已经太晚了,audio 元素可能已经在使用中) // initWebAudio(); } console.log('[AudioController] 响度规格化开关变更:', enabled ? '开启' : '关闭'); }; // 初始化音频设置 const initAudio = () => { const savedVolume = localStorage.getItem('player_volume'); if (savedVolume !== null) volume.value = parseFloat(savedVolume); isMuted.value = volume.value === 0; audio.volume = volume.value / 100; audio.muted = isMuted.value; // 初始化播放速度 const savedSpeed = localStorage.getItem('player_speed'); if (savedSpeed !== null) { playbackRate.value = parseFloat(savedSpeed); audio.playbackRate = playbackRate.value; } // 检查是否启用响度规格化,但不立即初始化 Web Audio // Web Audio 将在首次播放时初始化,以确保在用户手势上下文中 const savedSettings = JSON.parse(localStorage.getItem('settings') || '{}'); const savedNormalization = savedSettings.loudnessNormalization || 'off'; loudnessNormalizationEnabled.value = savedNormalization === 'on'; audio.addEventListener('ended', onSongEnd); audio.addEventListener('pause', handleAudioEvent); audio.addEventListener('play', handleAudioEvent); audio.addEventListener('timeupdate', updateCurrentTime); console.log('[AudioController] 初始化完成,音量设置为:', audio.volume, 'volume值:', volume.value, '播放速度:', audio.playbackRate); console.log('[AudioController] 响度规格化状态:', loudnessNormalizationEnabled.value ? '已启用(将在首次播放时初始化)' : '未启用'); }; // 处理播放/暂停事件 const handleAudioEvent = (event) => { if (event.type === 'play') { playing.value = true; } else if (event.type === 'pause') { playing.value = false; } console.log(`[AudioController] ${event.type}事件: playing=${playing.value}`); if (typeof window !== 'undefined' && typeof window.electron !== 'undefined') { window.electron.ipcRenderer.send('play-pause-action', playing.value, audio.currentTime); } }; // 切换播放/暂停 const togglePlayPause = async () => { console.log(`[AudioController] 切换播放状态: playing=${playing.value}, src=${audio.src}`); if (playing.value) { audio.pause(); playing.value = false; } else { try { // 在播放前确保 AudioContext 处于 running 状态(如果已启用) await ensureAudioContextRunning(); await audio.play(); playing.value = true; } catch (error) { console.error('[AudioController] 播放失败:', error); return false; } } return true; }; // 切换静音 const toggleMute = () => { isMuted.value = !isMuted.value; audio.muted = isMuted.value; console.log(`[AudioController] 切换静音: muted=${isMuted.value}`); if (isMuted.value) { volume.value = 0; } else { volume.value = audio.volume * 100; } localStorage.setItem('player_volume', volume.value); }; // 修改音量 const changeVolume = () => { audio.volume = volume.value / 100; localStorage.setItem('player_volume', volume.value); isMuted.value = volume.value === 0; audio.muted = isMuted.value; console.log(`[AudioController] 修改音量: volume=${volume.value}, audio.volume=${audio.volume}, muted=${isMuted.value}`); }; // 设置进度 const setCurrentTime = (time) => { audio.currentTime = time; console.log(`[AudioController] 设置进度: time=${time}`); }; // 设置播放速度 const setPlaybackRate = (speed) => { playbackRate.value = speed; audio.playbackRate = speed; localStorage.setItem('player_speed', speed); console.log('[AudioController] 设置播放速度:', speed); }; // 销毁时清理 const destroy = () => { console.log('[AudioController] 销毁音频控制器'); audio.pause(); audio.load(); audio.removeEventListener('play', handleAudioEvent); audio.removeEventListener('ended', onSongEnd); audio.removeEventListener('pause', handleAudioEvent); audio.removeEventListener('timeupdate', updateCurrentTime); // 清理 Web Audio 资源 if (webAudioInitialized.value) { if (sourceNode.value) { sourceNode.value.disconnect(); } if (gainNode.value) { gainNode.value.disconnect(); } if (audioContext.value) { audioContext.value.close(); } } }; return { audio, playing, isMuted, volume, playbackRate, initAudio, togglePlayPause, toggleMute, changeVolume, setCurrentTime, setPlaybackRate, destroy, // 响度规格化相关 applyLoudnessNormalization, ensureAudioContextRunning, toggleLoudnessNormalization, loudnessNormalizationEnabled, currentLoudnessGain, webAudioInitialized }; } ================================================ FILE: src/components/player/Helpers.js ================================================ import { ref } from 'vue'; import { get } from '../../utils/request'; import { MoeAuthStore } from '../../stores/store'; export function useHelpers(t) { const isInputFocused = ref(false); // 环境检测 const isElectron = () => { return typeof window !== 'undefined' && typeof window.electron !== 'undefined'; }; // 音量滚轮处理 const handleVolumeScroll = (event, volume, changeVolume) => { event.preventDefault(); const delta = Math.sign(event.deltaY) * -1; volume.value = Math.min(Math.max(volume.value + delta * 10, 0), 100); changeVolume(); }; // 检查输入框焦点 const checkFocus = () => { isInputFocused.value = ['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName); }; // 键盘快捷键处理 const handleKeyDown = (event, handlers, isInputFocused) => { if(isInputFocused) return; switch (event.code) { case 'Space': event.preventDefault(); handlers.togglePlayPause(); break; case 'ArrowLeft': handlers.playSongFromQueue('previous'); break; case 'ArrowRight': handlers.playSongFromQueue('next'); break; case 'Escape': if(handlers.showLyrics){ handlers.toggleLyrics(); } break; } }; // 桌面歌词控制 const desktopLyrics = () => { if (!isElectron()) return; let savedConfig = JSON.parse(localStorage.getItem('settings')) || {}; if(!savedConfig?.desktopLyrics) savedConfig.desktopLyrics = 'off'; let action = savedConfig?.desktopLyrics === 'off' ? 'display-lyrics' : 'close-lyrics'; window.electron.ipcRenderer.send('desktop-lyrics-action', action); savedConfig.desktopLyrics = action === 'display-lyrics' ? 'on' : 'off'; localStorage.setItem('settings', JSON.stringify(savedConfig)); }; // 节流函数 const throttle = (func, delay) => { let lastTime = 0; return function (...args) { const now = Date.now(); if (now - lastTime >= delay) { lastTime = now; func.apply(this, args); } }; }; // 获取VIP const getVip = async () => { if (typeof MoeAuthStore !== 'function') return; const MoeAuth = MoeAuthStore(); if (!MoeAuth.isAuthenticated) return; const todayKey = new Date().toISOString().split('T')[0]; const lastVipDate = localStorage.getItem('lastVipRequestDate'); if (lastVipDate === todayKey) { return; } try { await get('/youth/day/vip',{ receive_day: todayKey }); await new Promise(resolve => setTimeout(resolve, 500)); await get('/youth/day/vip/upgrade'); } catch (error) { console.error('领取VIP失败:', error); } localStorage.setItem('lastVipRequestDate', todayKey); }; return { isInputFocused, isElectron, handleVolumeScroll, checkFocus, handleKeyDown, desktopLyrics, throttle, getVip }; } ================================================ FILE: src/components/player/LyricsHandler.js ================================================ import { ref, nextTick } from 'vue'; import { get } from '../../utils/request'; export default function useLyricsHandler(t) { const lyricsData = ref([]); const originalLyrics = ref(''); const showLyrics = ref(false); const scrollAmount = ref(null); const SongTips = ref(t('zan-wu-ge-ci')); const lyricsMode = ref('translation'); // 'translation' 翻译模式 或 'romanization' 音译模式 let currentLineIndex = 0; // 显示/隐藏歌词 const toggleLyrics = (hash, currentTime) => { showLyrics.value = !showLyrics.value; SongTips.value = t('huo-qu-ge-ci-zhong'); // 如果显示歌词,滚动到当前播放行 if (!lyricsData.value.length && hash) getLyrics(hash); else if (showLyrics.value) { nextTick(() => { // 从全局 audio 对象获取当前播放时间 const currentLineIndex = getCurrentLineIndex(currentTime); if (currentLineIndex !== -1) scrollToCurrentLine(currentLineIndex); else centerFirstLine(); }); } return showLyrics.value; }; // 获取歌词 const getLyrics = async (hash) => { try { const settings = JSON.parse(localStorage.getItem('settings') || '{}'); if (!showLyrics.value && (settings?.desktopLyrics !== 'on' && settings?.apiMode !== 'on' && settings?.statusBarLyrics !== 'on' && settings?.touchBar !== 'on')) { return; } console.log('[LyricsHandler] 请求歌词……'); const lyricSearchResponse = await get(`/search/lyric?hash=${hash}`); if (lyricSearchResponse.status !== 200 || lyricSearchResponse.candidates.length === 0) { SongTips.value = t('zan-wu-ge-ci'); return false; } // 明确指定使用KRC格式 const lyricResponse = await get(`/lyric?id=${lyricSearchResponse.candidates[0].id}&accesskey=${lyricSearchResponse.candidates[0].accesskey}&fmt=krc&decode=true`); if (lyricResponse.status !== 200) { SongTips.value = t('huo-qu-ge-ci-shi-bai'); return false; } parseLyrics(lyricResponse.decodeContent, settings?.lyricsTranslation === 'on'); originalLyrics.value = lyricResponse.decodeContent; centerFirstLine(); return true; } catch (error) { SongTips.value = t('huo-qu-ge-ci-shi-bai'); } }; // 解析歌词 const parseLyrics = (text, parseTranslation = true) => { let translationLyrics = []; let romanizationLyrics = []; const lines = text.split('\n'); try { const languageLine = lines.find(line => line.match(/\[language:(.*)\]/)); if (parseTranslation && languageLine) { const languageCode = languageLine.slice(10, -2); if (languageCode) { try { // 确保 languageCode 是有效的 Base64 编码 // 替换可能导致 Base64 解码失败的字符 const cleanedCode = languageCode.replace(/[^A-Za-z0-9+/=]/g, ''); // 添加缺失的填充字符 const paddedCode = cleanedCode.padEnd(cleanedCode.length + (4 - cleanedCode.length % 4) % 4, '='); const decodedData = decodeURIComponent(escape(atob(paddedCode))); const languageData = JSON.parse(decodedData); // 获取翻译歌词 (type === 1) const translation = languageData?.content?.find(section => section.type === 1); if (translation?.lyricContent) { translationLyrics = translation.lyricContent; } // 获取音译歌词 (type === 0) const romanization = languageData?.content?.find(section => section.type === 0); if (romanization?.lyricContent) { romanizationLyrics = romanization.lyricContent; } } catch (decodeError) { console.warn('[LyricsHandler] Base64 解码失败:', decodeError); } } } } catch (error) { console.warn('[LyricsHandler] 解析翻译歌词失败!'); } const parsedLyrics = []; const charRegex = /<(\d+),(\d+),\d+>([^<]+)/g; lines.forEach(line => { // 匹配主时间标签 [start,duration] const lineMatch = line.match(/^\[(\d+),(\d+)\](.*)/); if (!lineMatch) return; const start = parseInt(lineMatch[1]); const lyricContent = lineMatch[3]; const characters = []; // 解析字符级时间标签 text let charMatch; while ((charMatch = charRegex.exec(lyricContent)) !== null) { const text = charMatch[3]; const charDuration = parseInt(charMatch[2]); const charStart = start + parseInt(charMatch[1]); // 直接使用文本组,不拆分 characters.push({ char: text, startTime: charStart, endTime: charStart + charDuration, highlighted: false }); } // 如果没有找到字符级时间标签,使用行级时间标签进行等分 if (characters.length === 0) { const duration = parseInt(lineMatch[2]); const lyric = lyricContent.replace(/<.*?>/g, ''); if (lyric.trim()) { for (let index = 0; index < lyric.length; index++) { characters.push({ char: lyric[index], startTime: start + (index * duration) / lyric.length, endTime: start + ((index + 1) * duration) / lyric.length, highlighted: false }); } } } // 保存有效歌词行 if (characters.length > 0) { parsedLyrics.push({ characters }); } }); // 添加翻译歌词 if (translationLyrics.length) { parsedLyrics.forEach((line, index) => { if (translationLyrics[index] && translationLyrics[index][0]) { line.translated = translationLyrics[index][0]; } }); } // 添加音译歌词 if (romanizationLyrics.length) { parsedLyrics.forEach((line, index) => { if (romanizationLyrics[index]) { // 将音译歌词数组合并为一个字符串 line.romanized = romanizationLyrics[index].join(''); } }); } lyricsData.value = parsedLyrics; }; // 切换歌词显示模式(翻译/音译) const toggleLyricsMode = () => { lyricsMode.value = lyricsMode.value === 'translation' ? 'romanization' : 'translation'; return lyricsMode.value; }; // 居中显示第一行歌词 const centerFirstLine = () => { const lyricsContainer = document.getElementById('lyrics-container'); if (!lyricsContainer) return; const containerHeight = lyricsContainer.offsetHeight; const lyricsElement = document.getElementById('lyrics'); if (!lyricsElement) return; const lyricsHeight = lyricsElement.offsetHeight; scrollAmount.value = (containerHeight - lyricsHeight) / 2; }; // 滚动到当前歌词行 const scrollToCurrentLine = (lineIndex) => { if (currentLineIndex === lineIndex) return; currentLineIndex = lineIndex; const lyricsContainer = document.getElementById('lyrics-container'); if (!lyricsContainer) return false; const containerHeight = lyricsContainer.offsetHeight; const lineElement = document.querySelectorAll('.line-group')[lineIndex]; if (lineElement) { const lineHeight = lineElement.offsetHeight; scrollAmount.value = -lineElement.offsetTop + (containerHeight / 2) - (lineHeight / 2); } }; // 高亮当前字符 const highlightCurrentChar = (currentTime, scroll = true) => { const currentTimeMs = currentTime * 1000; let currentActiveLineIndex = -1; lyricsData.value.forEach((lineData, lineIndex) => { let isLineActive = false; let hasHighlightedChar = false; lineData.characters.forEach((charData) => { // 更精确的时间判断 if (currentTimeMs >= charData.startTime && currentTimeMs <= charData.endTime) { if (!charData.highlighted) { charData.highlighted = true; hasHighlightedChar = true; } isLineActive = true; } else if (currentTimeMs > charData.endTime) { // 已经播放过的字符保持高亮 if (!charData.highlighted) { charData.highlighted = true; } } else { // 还未播放的字符取消高亮 charData.highlighted = false; } }); // 如果当前行有活跃字符,记录为当前行 if (isLineActive) { currentActiveLineIndex = lineIndex; } // 处理滚动 if (scroll && hasHighlightedChar) { scrollToCurrentLine(lineIndex); } }); // 如果没有找到活跃行,尝试找到最接近的行 if (currentActiveLineIndex === -1 && lyricsData.value.length > 0) { for (let i = 0; i < lyricsData.value.length; i++) { const lineData = lyricsData.value[i]; const firstChar = lineData.characters[0]; const lastChar = lineData.characters[lineData.characters.length - 1]; if (firstChar && lastChar && currentTimeMs >= firstChar.startTime && currentTimeMs <= lastChar.endTime) { currentActiveLineIndex = i; break; } } } }; // 重置歌词高亮状态 const resetLyricsHighlight = (currentTime) => { if (!lyricsData.value) return; const currentTimeMs = currentTime * 1000; let currentActiveLineIndex = -1; lyricsData.value.forEach((lineData, lineIndex) => { let isCurrentLine = false; lineData.characters.forEach(charData => { // 更精确的时间判断 if (currentTimeMs >= charData.startTime && currentTimeMs <= charData.endTime) { charData.highlighted = true; isCurrentLine = true; } else if (currentTimeMs > charData.endTime) { // 已经播放过的字符保持高亮 charData.highlighted = true; } else { // 还未播放的字符取消高亮 charData.highlighted = false; } }); if (isCurrentLine) { currentActiveLineIndex = lineIndex; scrollToCurrentLine(lineIndex); } }); }; // 获取当前播放行索引 const getCurrentLineIndex = (currentTime) => { if (!lyricsData.value || lyricsData.value.length === 0) return -1; const currentTimeMs = currentTime * 1000; for (let index = 0; index < lyricsData.value.length; index++) { const lineData = lyricsData.value[index]; const nextLineData = lyricsData.value[index + 1]; const firstChar = lineData.characters[0]; const nextFirstChar = nextLineData?.characters[0]; if ( firstChar && nextFirstChar && currentTimeMs >= firstChar.startTime && currentTimeMs <= nextFirstChar.startTime ) return index + 1; } return lyricsData.value.length - 1; }; // 获取当前行歌词文本 const getCurrentLineText = (currentTime) => { if (!lyricsData.value || lyricsData.value.length === 0) return ""; for (const lineData of lyricsData.value) { const firstChar = lineData.characters[0]; const lastChar = lineData.characters[lineData.characters.length - 1]; if ( firstChar && lastChar && currentTime * 1000 >= firstChar.startTime && currentTime * 1000 <= lastChar.endTime ) { return lineData.characters.map((char) => char.char).join(""); } } return ""; }; return { lyricsData, originalLyrics, showLyrics, scrollAmount, SongTips, lyricsMode, toggleLyrics, getLyrics, highlightCurrentChar, resetLyricsHighlight, getCurrentLineText, scrollToCurrentLine, toggleLyricsMode }; } ================================================ FILE: src/components/player/MediaSession.js ================================================ export default function useMediaSession() { // 初始化媒体会话 const initMediaSession = (handlers) => { if (!("mediaSession" in navigator) || !navigator.mediaSession) return; // 基本播放控制 navigator.mediaSession.setActionHandler('play', handlers.togglePlayPause); navigator.mediaSession.setActionHandler('pause', handlers.togglePlayPause); navigator.mediaSession.setActionHandler('previoustrack', handlers.playPrevious); navigator.mediaSession.setActionHandler('nexttrack', handlers.playNext); // SMTC 时间轴控制 navigator.mediaSession.setActionHandler('seekbackward', (details) => { if (handlers.seekBackward) { const seekOffset = details.seekOffset || 10; // 默认快退10秒 handlers.seekBackward(seekOffset); } }); navigator.mediaSession.setActionHandler('seekforward', (details) => { if (handlers.seekForward) { const seekOffset = details.seekOffset || 10; // 默认快进10秒 handlers.seekForward(seekOffset); } }); navigator.mediaSession.setActionHandler('seekto', (details) => { if (handlers.seekTo && details.seekTime !== undefined) { handlers.seekTo(details.seekTime); } }); }; // 更新媒体会话信息 const changeMediaSession = (song) => { if (!("mediaSession" in navigator) || !navigator.mediaSession) return; const defaultArtwork = './assets/images/logo.png'; const checkImageAccessibility = (src) => { return new Promise((resolve) => { const img = new Image(); img.onload = () => resolve(src); img.onerror = () => resolve(defaultArtwork); img.src = src; }); }; const updateMediaSession = async () => { try { const artworkSrc = await checkImageAccessibility(song.img || defaultArtwork); navigator.mediaSession.metadata = new MediaMetadata({ title: song.name, artist: song.author, album: song.album, artwork: [{ src: artworkSrc }] }); } catch (error) { console.error("Failed to update media session metadata:", error); } }; updateMediaSession(); }; // 更新播放位置状态 const updatePositionState = (currentTime, duration, playbackRate = 1.0) => { if (!("mediaSession" in navigator) || !navigator.mediaSession) return; try { if (typeof currentTime === 'number' && typeof duration === 'number' && currentTime >= 0 && duration > 0 && currentTime <= duration) { navigator.mediaSession.setPositionState({ duration: duration, playbackRate: playbackRate, position: currentTime }); } } catch (error) { console.error("Failed to update position state:", error); } }; // 清除位置状态 const clearPositionState = () => { if (!("mediaSession" in navigator) || !navigator.mediaSession) return; try { navigator.mediaSession.setPositionState(null); } catch (error) { console.error("Failed to clear position state:", error); } }; return { initMediaSession, changeMediaSession, updatePositionState, clearPositionState }; } ================================================ FILE: src/components/player/PlaybackMode.js ================================================ import { ref, computed } from 'vue'; export default function usePlaybackMode(t, audio) { const playbackModes = ref([ { icon: 'fas fa-random', title: t('sui-ji-bo-fang') }, { icon: 'fas fa-refresh', title: t('lie-biao-xun-huan') }, { icon: '', title: t('dan-qu-xun-huan') } ]); const currentPlaybackModeIndex = ref(1); const currentPlaybackMode = computed(() => playbackModes.value[currentPlaybackModeIndex.value]); const playedSongsStack = ref([]); const currentStackIndex = ref(-1); // 初始化播放模式 const initPlaybackMode = () => { const savedMode = localStorage.getItem('player_playback_mode'); currentPlaybackModeIndex.value = savedMode !== null ? parseInt(savedMode, 10) : 1; audio.loop = currentPlaybackModeIndex.value === 2; console.log('[PlaybackMode] 初始化播放模式:', currentPlaybackModeIndex.value); }; // 切换播放模式 const togglePlaybackMode = () => { currentPlaybackModeIndex.value = (currentPlaybackModeIndex.value + 1) % playbackModes.value.length; audio.loop = currentPlaybackModeIndex.value === 2; playedSongsStack.value = []; currentStackIndex.value = -1; localStorage.setItem('player_playback_mode', currentPlaybackModeIndex.value.toString()); console.log('[PlaybackMode] 切换播放模式:', currentPlaybackModeIndex.value); }; return { playbackModes, currentPlaybackModeIndex, currentPlaybackMode, playedSongsStack, currentStackIndex, initPlaybackMode, togglePlaybackMode }; } ================================================ FILE: src/components/player/ProgressBar.js ================================================ import { ref } from 'vue'; import { get } from '../../utils/request'; export default function useProgressBar(audio, resetLyricsHighlight) { const progressWidth = ref(0); const isProgressDragging = ref(false); const isDraggingHandle = ref(false); const showTimeTooltip = ref(false); const tooltipPosition = ref(0); const tooltipTime = ref('0:00'); const activeProgressBar = ref(null); const climaxPoints = ref([]); // 格式化时间 const formatTime = (seconds) => { if (seconds > 1000) seconds = seconds / 1000; const minutes = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${minutes}:${secs.toString().padStart(2, '0')}`; }; // 获取歌曲高潮点 const getMusicHighlights = async (hash) => { try { const response = await get(`/song/climax?hash=${hash}`); if (response.status !== 1) { climaxPoints.value = []; return; } climaxPoints.value = response.data.map(point => ({ position: (parseInt(point.start_time) / 1000 / audio.duration) * 100, duration: parseInt(point.timelength) / 1000 })); } catch (error) { climaxPoints.value = []; } }; // 进度条拖动开始 const onProgressDragStart = (event) => { event.preventDefault(); const currentProgressBar = event.target.closest('.progress-bar'); if (!currentProgressBar) return; // 检查是否点击在小圆点上 const handle = event.target.closest('.progress-handle'); if (!handle) { if (!audio.duration) return; const rect = currentProgressBar.getBoundingClientRect(); const offsetX = Math.max(0, Math.min(event.clientX - rect.left, currentProgressBar.offsetWidth)); const percentage = (offsetX / currentProgressBar.offsetWidth) * 100; progressWidth.value = Math.max(0, Math.min(percentage, 100)); } isProgressDragging.value = true; isDraggingHandle.value = true; activeProgressBar.value = currentProgressBar; document.addEventListener('mousemove', onProgressDrag); document.addEventListener('mouseup', onProgressDragEnd); }; // 进度条拖动中 const onProgressDrag = (event) => { event.preventDefault(); if (isProgressDragging.value && activeProgressBar.value) { const rect = activeProgressBar.value.getBoundingClientRect(); const offsetX = Math.max(0, Math.min(event.clientX - rect.left, activeProgressBar.value.offsetWidth)); const percentage = (offsetX / activeProgressBar.value.offsetWidth) * 100; progressWidth.value = Math.max(0, Math.min(percentage, 100)); // 更新时间提示 tooltipPosition.value = offsetX; const time = (percentage / 100) * audio.duration; tooltipTime.value = formatTime(time); } }; // 进度条拖动结束 const onProgressDragEnd = (event) => { if (isProgressDragging.value && activeProgressBar.value) { const rect = activeProgressBar.value.getBoundingClientRect(); const offsetX = Math.max(0, Math.min(event.clientX - rect.left, activeProgressBar.value.offsetWidth)); const percentage = (offsetX / activeProgressBar.value.offsetWidth) * 100; const newTime = (percentage / 100) * audio.duration; audio.currentTime = Math.max(0, Math.min(newTime, audio.duration)); resetLyricsHighlight(audio.currentTime); } isProgressDragging.value = false; isDraggingHandle.value = false; showTimeTooltip.value = false; activeProgressBar.value = null; document.removeEventListener('mousemove', onProgressDrag); document.removeEventListener('mouseup', onProgressDragEnd); }; // 点击进度条更新进度 const updateProgressFromEvent = (event) => { if (isProgressDragging.value) return; // 如果正在拖动则不处理点击 const progressBar = event.target.closest('.progress-bar'); if (!progressBar || !audio.duration) return; const rect = progressBar.getBoundingClientRect(); const offsetX = Math.max(0, Math.min(event.clientX - rect.left, progressBar.offsetWidth)); const percentage = (offsetX / progressBar.offsetWidth) * 100; const newTime = (percentage / 100) * audio.duration; audio.currentTime = Math.max(0, Math.min(newTime, audio.duration)); progressWidth.value = percentage; resetLyricsHighlight(audio.currentTime); }; // 更新时间提示 const updateTimeTooltip = (event) => { const progressBar = event.target.closest('.progress-bar'); if (!progressBar || !audio.duration) return; const rect = progressBar.getBoundingClientRect(); const offsetX = Math.max(0, Math.min(event.clientX - rect.left, progressBar.offsetWidth)); const tooltipWidth = 50; if (offsetX < tooltipWidth / 2) { tooltipPosition.value = tooltipWidth / 2; } else if (offsetX > progressBar.offsetWidth - tooltipWidth / 2) { tooltipPosition.value = progressBar.offsetWidth - tooltipWidth / 2; } else { tooltipPosition.value = offsetX; } const percentage = (offsetX / progressBar.offsetWidth); const time = percentage * audio.duration; tooltipTime.value = formatTime(time); showTimeTooltip.value = true; }; // 隐藏时间提示 const hideTimeTooltip = () => { if (!isProgressDragging.value) { showTimeTooltip.value = false; } }; return { progressWidth, isProgressDragging, showTimeTooltip, tooltipPosition, tooltipTime, climaxPoints, formatTime, getMusicHighlights, onProgressDragStart, updateProgressFromEvent, updateTimeTooltip, hideTimeTooltip }; } ================================================ FILE: src/components/player/SongQueue.js ================================================ import { ref } from 'vue'; import { get } from '../../utils/request'; import { MoeAuthStore } from '../../stores/store'; const QUALITY_LEVELS = ['128', '320', 'flac', 'high', 'viper_atmos', 'viper_clear', 'viper_tape']; const QUALITY_LABELS = { '128': '标准', '320': '高品', flac: 'FLAC', high: 'Hi-Res', viper_atmos: '全景声', viper_clear: '超清', viper_tape: '母带' }; const normalizeQuality = (quality) => { return QUALITY_LEVELS.includes(quality) ? quality : '128'; }; const getFallbackChain = (quality) => QUALITY_LEVELS.slice(0, QUALITY_LEVELS.indexOf(normalizeQuality(quality)) + 1).reverse(); const getQualityLabel = (quality) => QUALITY_LABELS[quality] || ''; const getPrivilegeVariants = (response) => { const variants = []; for (const item of response?.data || []) { for (const variant of [item, ...(item?.relate_goods || [])]) { if (!variant?.hash || variant?.level === 0 || !QUALITY_LEVELS.includes(variant?.quality)) continue; variants.push(variant); } } return variants; }; const getQualityOptions = (response) => { const qualityOptions = new Map(); for (const variant of getPrivilegeVariants(response)) { if (qualityOptions.has(variant.quality)) continue; qualityOptions.set(variant.quality, { value: variant.quality, hash: variant.hash, label: getQualityLabel(variant.quality) }); } return [...qualityOptions.values()].sort((a, b) => QUALITY_LEVELS.indexOf(b.value) - QUALITY_LEVELS.indexOf(a.value)); }; const getPrivilegeCandidates = (qualityOptions, quality, originalHash) => { const candidatesByQuality = new Map(); for (const option of qualityOptions) { if (!candidatesByQuality.has(option.value)) { candidatesByQuality.set(option.value, { hash: option.hash, quality: option.value }); } } const fallbackChain = getFallbackChain(quality); const candidates = fallbackChain.map(itemQuality => candidatesByQuality.get(itemQuality)).filter(Boolean); return candidates.length > 0 ? candidates : fallbackChain.map(itemQuality => ({ hash: originalHash, quality: itemQuality })); }; export default function useSongQueue(t, musicQueueStore, queueList = null) { const currentSong = ref({ name: '', author: '', img: '', url: '', hash: '', playHash: '', resolvedQuality: '', qualityLabel: '', qualityOptions: [] }); const NextSong = ref([]); const timeoutId = ref(null); // 添加歌曲到队列并播放 const addSongToQueue = async (hash, name, img, author, isReset = true, qualityOverride = '', cachedQualityOptions = []) => { if(!hash) return { error: true }; const currentSongHash = currentSong.value.hash; if (typeof window !== 'undefined' && typeof window.electron !== 'undefined') { window.electron.ipcRenderer.send('set-tray-title', name + ' - ' + author); } try { clearTimeout(timeoutId.value); currentSong.value.author = author; currentSong.value.name = name; currentSong.value.img = img; currentSong.value.hash = hash; currentSong.value.playHash = hash; currentSong.value.resolvedQuality = ''; currentSong.value.qualityLabel = ''; currentSong.value.qualityOptions = []; console.log('[SongQueue] 获取歌曲:', hash, name); const settings = JSON.parse(localStorage.getItem('settings') || '{}'); const data = { hash: hash }; // 根据用户设置确定请求参数 const MoeAuth = typeof MoeAuthStore === 'function' ? MoeAuthStore() : { isAuthenticated: false }; const isAuth = !!MoeAuth.isAuthenticated; let response = null; let selectedCandidate = { hash, quality: '' }; let qualityOptions = []; if (!isAuth) { data.free_part = 1; response = await get('/song/url', data); } else { const q = normalizeQuality(qualityOverride || settings?.quality); const fallbackCandidates = getFallbackChain(q).map(itemQuality => ({ hash, quality: itemQuality })); let candidates = fallbackCandidates; qualityOptions = Array.isArray(cachedQualityOptions) ? cachedQualityOptions.map(option => ({ ...option })) : []; try { if (qualityOptions.length === 0) { const privilegeResponse = await get(`/privilege/lite`, { hash: hash }); qualityOptions = getQualityOptions(privilegeResponse); } candidates = getPrivilegeCandidates(qualityOptions, q, hash); } catch (error) { if (error.response?.data?.error?.includes('验证')) { throw error; } if (error.response?.data?.status == 2) { throw error; } console.error('[SongQueue] 获取歌曲详情失败,回退到原始哈希请求:', error); } for (const candidate of candidates) { try { const candidateResponse = await get('/song/url', { hash: candidate.hash, quality: candidate.quality }); if (candidateResponse.status !== 1) { response = candidateResponse; continue; } if (candidateResponse.extName == 'mp4') { console.log('[SongQueue] 歌曲格式为MP4,尝试获取下一档音质'); response = candidateResponse; continue; } if (!candidateResponse.url || !candidateResponse.url[0]) { response = candidateResponse; continue; } response = candidateResponse; selectedCandidate = candidate; break; } catch (error) { if (error.response?.data?.error?.includes('验证')) { throw error; } if (error.response?.data?.status == 2) { throw error; } console.error('[SongQueue] 获取候选音质失败:', error); } } } if (!response || response.status !== 1) { console.error('[SongQueue] 获取音乐URL失败:', response); currentSong.value.author = currentSong.value.name = t('huo-qu-yin-le-shi-bai'); if (response?.status == 3) { currentSong.value.name = t('gai-ge-qu-zan-wu-ban-quan'); } if (musicQueueStore.queue.length === 0) return { error: true }; currentSong.value.author = t('3-miao-hou-zi-dong-qie-huan-xia-yi-shou'); // 返回需要切换到下一首的标志,而不是直接调用playSongFromQueue return { error: true, shouldPlayNext: true }; } // 设置URL if (response.url && response.url[0]) { currentSong.value.url = response.url[0]; currentSong.value.playHash = selectedCandidate.hash || hash; currentSong.value.resolvedQuality = selectedCandidate.quality || ''; currentSong.value.qualityLabel = getQualityLabel(selectedCandidate.quality); currentSong.value.qualityOptions = qualityOptions.map(option => ({ ...option })); console.log('[SongQueue] 获取到音乐URL:', currentSong.value.url); } else { console.error('[SongQueue] 未获取到音乐URL'); currentSong.value.author = currentSong.value.name = t('huo-qu-yin-le-shi-bai'); return { error: true }; } // 创建歌曲对象 const song = { id: musicQueueStore.queue.length + 1, hash: hash, playHash: selectedCandidate.hash || hash, resolvedQuality: selectedCandidate.quality || '', qualityLabel: getQualityLabel(selectedCandidate.quality), qualityOptions: qualityOptions.map(option => ({ ...option })), name: name, img: img, author: author, timeLength: response.timeLength, url: response.url[0], // 响度规格化参数 loudnessNormalization: { volume: response.volume || 0, volumeGain: response.volume_gain || 0, volumePeak: response.volume_peak || 1 } }; // 根据是否需要重置播放位置 if (isReset) { localStorage.setItem('player_progress', 0); } // 更新队列 const existingSongIndex = musicQueueStore.queue.findIndex(song => song.hash === hash); if (existingSongIndex === -1) { const currentIndex = musicQueueStore.queue.findIndex(song => song.hash == currentSongHash); if (currentIndex !== -1) { musicQueueStore.queue.splice(currentIndex + 1, 0, song); } else { musicQueueStore.addSong(song); } } else { // 如果歌曲已存在,只更新当前歌曲的信息,不修改队列 currentSong.value = song; } // 返回歌曲对象 return { song }; } catch (error) { console.error('[SongQueue] 获取音乐地址出错:', error); currentSong.value.author = currentSong.value.name = t('huo-qu-yin-le-di-zhi-shi-bai'); if (error.response?.data?.error?.includes('验证')) { window.$modal.alert('账户风控,请稍候重试!'); return { error: true}; } if (error.response?.data?.status == 2) { window.$modal.alert(t('deng-lu-shi-xiao-qing-zhong-xin-deng-lu')); return { error: true}; } if (musicQueueStore.queue.length === 0) return { error: true }; currentSong.value.author = t('3-miao-hou-zi-dong-qie-huan-xia-yi-shou'); // 返回需要切换到下一首的标志,而不是直接调用playSongFromQueue return { error: true, shouldPlayNext: true }; } }; // 添加云盘歌曲到播放列表 const addCloudMusicToQueue = async (hash, name, author, timeLength, cover, isReset = true) => { const currentSongHash = currentSong.value.hash; if (typeof window !== 'undefined' && typeof window.electron !== 'undefined') { window.electron.ipcRenderer.send('set-tray-title', name + ' - ' + author); } try { clearTimeout(timeoutId.value); currentSong.value.author = author; currentSong.value.name = name; currentSong.value.hash = hash; currentSong.value.img = cover; currentSong.value.qualityLabel = ''; currentSong.value.qualityOptions = []; console.log('[SongQueue] 获取云盘歌曲:', hash, name); const response = await get('/user/cloud/url', { hash }); if (response.status !== 1) { console.error('[SongQueue] 获取云盘音乐URL失败:', response); currentSong.value.author = currentSong.value.name = t('huo-qu-yin-le-shi-bai'); if (musicQueueStore.queue.length === 0) return { error: true }; currentSong.value.author = t('3-miao-hou-zi-dong-qie-huan-xia-yi-shou'); // 返回需要切换到下一首的标志,而不是直接调用playSongFromQueue return { error: true, shouldPlayNext: true }; } // 设置URL if (response.data && response.data.url) { currentSong.value.url = response.data.url; console.log('[SongQueue] 获取到云盘音乐URL:', currentSong.value.url); } else { console.error('[SongQueue] 未获取到云盘音乐URL'); currentSong.value.author = currentSong.value.name = t('huo-qu-yin-le-shi-bai'); return { error: true }; } // 创建歌曲对象 const song = { id: musicQueueStore.queue.length + 1, hash: hash, name: name, author: author, img: cover, timeLength: timeLength || 0, url: response.data.url, isCloud: true }; // 根据是否需要重置播放位置 if (isReset) { localStorage.setItem('player_progress', 0); } // 更新队列 const existingSongIndex = musicQueueStore.queue.findIndex(song => song.hash === hash); if (existingSongIndex === -1) { const currentIndex = musicQueueStore.queue.findIndex(song => song.hash == currentSongHash); if (currentIndex !== -1) { musicQueueStore.queue.splice(currentIndex + 1, 0, song); } else { musicQueueStore.addSong(song); } } else { // 如果歌曲已存在,只更新当前歌曲的信息,不修改队列 currentSong.value = song; } // 返回歌曲对象 return { song }; } catch (error) { console.error('[SongQueue] 获取云盘音乐地址出错:', error); currentSong.value.author = currentSong.value.name = t('huo-qu-yin-le-di-zhi-shi-bai'); if (musicQueueStore.queue.length === 0) return { error: true }; currentSong.value.author = t('3-miao-hou-zi-dong-qie-huan-xia-yi-shou'); // 返回需要切换到下一首的标志,而不是直接调用playSongFromQueue return { error: true, shouldPlayNext: true }; } }; // 添加下一首 const addToNext = (hash, name, img, author, timeLength) => { const existingSongIndex = musicQueueStore.queue.findIndex(song => song.hash === hash); if (existingSongIndex !== -1 && typeof queueList?.value?.removeSongFromQueue === 'function') { queueList.value.removeSongFromQueue(existingSongIndex); } const currentIndex = musicQueueStore.queue.findIndex(song => song.hash === currentSong.value.hash); musicQueueStore.queue.splice(currentIndex !== -1 ? currentIndex + 1 : musicQueueStore.queue.length, 0, { id: musicQueueStore.queue.length + 1, hash: hash, name: name, img: img, author: author, timeLength: timeLength, }); NextSong.value.push({ id: musicQueueStore.queue.length + 1, hash: hash, name: name, img: img, author: author, timeLength: timeLength, }); }; // 获取歌单全部歌曲 const getPlaylistAllSongs = async (id) => { try { let allSongs = []; for (let page = 1; page <= 4; page++) { const url = `/playlist/track/all?id=${id}&pagesize=300&page=${page}`; const response = await get(url); if (response.status !== 1) { window.$modal.alert(t('huo-qu-ge-dan-shi-bai')); return; } if (Object.keys(response.data.info).length === 0) break; allSongs = allSongs.concat(response.data.info); if (response.data.info.length < 300) break; } return allSongs; } catch (error) { console.error(error); window.$modal.alert(t('huo-qu-ge-dan-shi-bai')); return null; } }; // 添加歌单到播放列表 const addPlaylistToQueue = async (info, append = false) => { let songs = []; if (!append) { musicQueueStore.clearQueue(); } else { songs = [...musicQueueStore.queue]; } const newSongs = info.map((song, index) => { return { id: songs.length + index + 1, hash: song.hash, name: song.name, img: song.cover?.replace("{size}", 480) || './assets/images/ico.png', author: song.author, timeLength: song.timelen }; }); if (append) { songs = [...songs, ...newSongs]; } else { songs = newSongs; } musicQueueStore.queue = songs; return songs; }; // 批量添加云盘歌曲到播放列表 const addCloudPlaylistToQueue = async (songs, append = false) => { let queueSongs = []; if (!append) { musicQueueStore.clearQueue(); } else { queueSongs = [...musicQueueStore.queue]; } const newSongs = songs.map((song, index) => { return { id: queueSongs.length + index + 1, hash: song.hash, name: song.name, author: song.author, timeLength: song.timelen || 0, url: song.url, isCloud: true }; }); if (append) { queueSongs = [...queueSongs, ...newSongs]; } else { queueSongs = newSongs; } musicQueueStore.queue = queueSongs; return queueSongs; }; // 添加本地音乐到队列并播放 const addLocalMusicToQueue = async (localItem, isReset = true) => { const currentSongHash = currentSong.value.hash; if (typeof window !== 'undefined' && typeof window.electron !== 'undefined') { window.electron.ipcRenderer.send('set-tray-title', (localItem.displayName || localItem.name) + ' - ' + (localItem.author || '未知艺术家')); } try { clearTimeout(timeoutId.value); // 设置当前歌曲信息 currentSong.value.author = localItem.author || '未知艺术家'; currentSong.value.name = localItem.displayName || localItem.name; currentSong.value.img = localItem.cover || './assets/images/ico.png'; currentSong.value.hash = `local_${localItem.name}_${localItem.file.size}_${localItem.file.lastModified}`; currentSong.value.qualityLabel = ''; currentSong.value.qualityOptions = []; // 创建本地文件的 URL const url = URL.createObjectURL(localItem.file); currentSong.value.url = url; console.log('[SongQueue] 创建本地音乐URL:', url); // 创建歌曲对象 const song = { // id: musicQueueStore.queue.length + 1, hash: currentSong.value.hash, name: currentSong.value.name, img: currentSong.value.img, author: currentSong.value.author, timeLength: localItem.timelen || (localItem.duration * 1000), url: url, isLocal: true }; // 根据是否需要重置播放位置 // if (isReset) { // localStorage.setItem('player_progress', 0); // } // // 更新队列 // const existingSongIndex = musicQueueStore.queue.findIndex(song => song.hash === currentSong.value.hash); // if (existingSongIndex === -1) { // const currentIndex = musicQueueStore.queue.findIndex(song => song.hash == currentSongHash); // if (currentIndex !== -1) { // musicQueueStore.queue.splice(currentIndex + 1, 0, song); // } else { // musicQueueStore.addSong(song); // } // } else { // // 如果歌曲已存在,只更新当前歌曲的信息,不修改队列 // currentSong.value = song; // } // 返回歌曲对象 return { song }; } catch (error) { console.error('[SongQueue] 获取本地音乐地址出错:', error); currentSong.value.author = currentSong.value.name = t('huo-qu-ben-di-yin-le-di-zhi-shi-bai'); // if (musicQueueStore.queue.length === 0) return { error: true }; currentSong.value.author = t('3-miao-hou-zi-dong-qie-huan-xia-yi-shou'); // 返回需要切换到下一首的标志,而不是直接调用playSongFromQueue return { error: true, shouldPlayNext: true }; } }; // 批量添加本地音乐到播放列表 const addLocalPlaylistToQueue = async (localSongs, append = false) => { console.log('[SongQueue] 添加本地播放列表:', localSongs.length, '首歌曲'); try { // if (!append) { // musicQueueStore.clearQueue(); // } const queueSongs = []; for (const item of localSongs) { const localSong = { // id: musicQueueStore.queue.length + queueSongs.length + 1, hash: `local_${item.name}_${item.file.size}_${item.file.lastModified}`, name: item.displayName || item.name, author: item.author || '未知艺术家', img: item.cover || './assets/images/ico.png', timeLength: item.timelen || (item.duration * 1000), isLocal: true, file: item.file }; queueSongs.push(localSong); } // 添加到队列 // if (append) { // musicQueueStore.queue = [...musicQueueStore.queue, ...queueSongs]; // } else { // musicQueueStore.queue = queueSongs; // } return queueSongs; } catch (error) { console.error('[SongQueue] 添加本地播放列表失败:', error); return []; } }; // 获取歌曲详情 const privilegeSong = async (hash) => { const response = await get(`/privilege/lite`,{hash:hash}); return response; } return { currentSong, NextSong, addSongToQueue, addCloudMusicToQueue, addLocalMusicToQueue, addLocalPlaylistToQueue, addToNext, getPlaylistAllSongs, addPlaylistToQueue, addCloudPlaylistToQueue, privilegeSong }; } ================================================ FILE: src/components/player/index.js ================================================ // 导出所有组件模块 import useAudioController from './AudioController'; import useLyricsHandler from './LyricsHandler'; import useProgressBar from './ProgressBar'; import usePlaybackMode from './PlaybackMode'; import useMediaSession from './MediaSession'; import useSongQueue from './SongQueue'; import { useHelpers } from './Helpers'; export { useAudioController, useLyricsHandler, useProgressBar, usePlaybackMode, useMediaSession, useSongQueue, useHelpers }; ================================================ FILE: src/language/en.json ================================================ { "tui-jian": "Recommendations", "tui-jian-ge-qu": "Recommended Tracks", "tui-jian-ge-dan": "Recommended Playlists", "fa-xian": "Discover", "yu-yan": "Language", "zhu-se-tiao": "Theme Color", "wai-guan": "Appearance", "native-title-bar": "Native Window Decorations", "sheng-yin": "Sound", "yin-zhi-xuan-ze": "Audio Quality", "qi-dong-wen-hou-yu": "Startup Greeting", "ge-ci": "Lyrics", "xian-shi-ge-ci-bei-jing": "Show Album Art as Lyrics Background", "xian-shi-zhuo-mian-ge-ci": "Show Desktop Lyrics", "ge-ci-zi-ti-da-xiao": "Fullscreen Lyrics Font Size", "guan-bi": "Disable", "guan-bi-an-niu": "Close", "shao-nv-fen": "Pink", "qian-se": "Light", "pu-tong-yin-zhi": "Standard Quality — 128 Kbps", "da-kai": "Enable", "zhong": "Medium", "kai-qi": "Enable", "xuan-ze-yu-yan": "Select Language", "xuan-ze-zhu-se-tiao": "Select Theme Color", "nan-nan-lan": "Sky Blue", "tou-ding-lv": "Mint Green", "xuan-ze-wai-guan": "Select Appearance", "zi-dong": "Auto", "shen-se": "Dark", "gao-yin-zhi-320kbps": "High Quality — 320 Kbps", "xiao": "Small", "da": "Large", "sou-suo-jie-guo": "Search Results", "shang-yi-ye": "Previous", "xia-yi-ye": "Next", "di": "Page", "ye": "", "gong": "of", "bo-fang": "Play", "ge-qu-lie-biao": "Track List", "deng-lu-ni-de-ku-gou-zhang-hao": "Log in to Your Kugou Account", "qing-shu-ru-shou-ji-hao": "Enter Phone Number", "qing-shu-ru-yan-zheng-ma": "Enter Verification Code", "fa-song-yan-zheng-ma": "Send Code", "li-ji-deng-lu": "Log In", "qing-shu-ru-deng-lu-you-xiang": "Enter Username", "qing-shu-ru-mi-ma": "Enter Password", "you-xiang-deng-lu": "Username Login", "er-wei-ma": "QR Code", "login-tips": "MoeKoe does not store your account information on any server. Your password is encrypted locally before being sent to Kugou's official servers. MoeKoe is not an official Kugou website — enter your credentials at your own risk. Not all accounts support password login.", "shi-yong-yan-zheng-ma-deng-lu": "Log in with SMS Code", "shou-ji-hao-deng-lu": "Phone Login", "sao-ma-deng-lu": "QR Code Login", "qing-shu-ru-shou-ji-hao-ma": "Enter Phone Number", "shou-ji-hao-ge-shi-cuo-wu": "Invalid Phone Number", "qing-shu-ru-you-xiang": "Enter Username", "you-xiang-ge-shi-cuo-wu": "Invalid Email Format", "qing-shi-yong-ku-gou-sao-miao-er-wei-ma-deng-lu": "Scan QR Code with the Kugou App", "deng-lu-cheng-gong": "Login Successful", "deng-lu-shi-bai": "Login Failed", "yan-zheng-ma-fa-song-shi-bai": "Failed to Send Code", "yan-zheng-ma-yi-fa-song": "Code Sent", "wu-xiang-ying-shu-ju": "No Response from Server", "huo-qu-er-wei-ma-shi-bai": "Failed to Get QR Code", "er-wei-ma-sheng-cheng-shi-bai": "Failed to Generate QR Code", "er-wei-ma-deng-lu-cheng-gong": "QR Code Login Successful", "er-wei-ma-yi-guo-qi-qing-zhong-xin-sheng-cheng": "QR Code Expired. Please Regenerate", "er-wei-ma-jian-ce-shi-bai": "QR Code Verification Failed", "deng-lu-shi-bai-0": "Login Failed: ", "yan-zheng-ma-fa-song-shi-bai-0": "Failed to Send Code: ", "yong-hu": "User", "yi-sao-ma-deng-dai-que-ren": "QR Code Scanned. Confirm in App", "yong-hu-tou-xiang": "Avatar", "de-yin-le-ku": "'s Music Library", "wo-xi-huan-ting": "Liked Tracks", "wo-chuang-jian-de-ge-dan": "My Playlists", "wo-shou-cang-de-ge-dan": "Saved Playlists", "wo-guan-zhu-de-ge-shou": "Following Artists", "deng-lu-shi-xiao-qing-zhong-xin-deng-lu": "Session Expired. Please Log In Again", "gai-nian-ban": "Concept Version:", "chang-ting-ban": "Standard Version:", "shou-ge": "tracks", "shou-ye": "Home", "yin-le-ku": "Library", "sou-suo-yin-le-ge-shou-ge-dan": "Search music, artists, playlists...", "she-zhi": "Settings", "tui-chu": "Log Out", "deng-lu": "Log In", "geng-xin": "Update", "guan-yu": "About", "mian-ze-sheng-ming": "Disclaimer", "0-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "0. This is an unofficial Kugou client. For full features, use the official app.", "1-ben-xiang-mu-jin-gong-xue-xi-shi-yong-qing-zun-zhong-ban-quan-qing-wu-li-yong-ci-xiang-mu-cong-shi-shang-ye-hang-wei-ji-fei-fa-yong-tu": "1. This project is for educational purposes only. Please respect copyright and do not use it for commercial or illegal purposes.", "2-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-chan-sheng-ban-quan-shu-ju-dui-yu-zhe-xie-ban-quan-shu-ju-ben-xiang-mu-bu-yong-you-ta-men-de-suo-you-quan-wei-le-bi-mian-qin-quan-shi-yong-zhe-wu-bi-zai-24-xiao-shi-nei-qing-chu-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-suo-chan-sheng-de-ban-quan-shu-ju": "2. Usage may generate copyrighted data. This project does not own such data. To avoid infringement, delete any copyrighted data within 24 hours.", "3-you-yu-shi-yong-ben-xiang-mu-chan-sheng-de-bao-kuo-you-yu-ben-xie-yi-huo-you-yu-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-er-yin-qi-de-ren-he-xing-zhi-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-sun-hai-bao-kuo-dan-bu-xian-yu-yin-shang-yu-sun-shi-ting-gong-ji-suan-ji-gu-zhang-huo-gu-zhang-yin-qi-de-sun-hai-pei-chang-huo-ren-he-ji-suo-you-qi-ta-shang-ye-sun-hai-huo-sun-shi-you-shi-yong-zhe-fu-ze": "3. Users are responsible for any direct or indirect damages arising from use or inability to use this project.", "4-jin-zhi-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-dui-yu-shi-yong-zhe-zai-ming-zhi-huo-bu-zhi-dang-di-fa-lv-fa-gui-bu-yun-xu-de-qing-kuang-xia-shi-yong-ben-xiang-mu-suo-zao-cheng-de-ren-he-wei-fa-wei-gui-hang-wei-you-shi-yong-zhe-cheng-dan-ben-xiang-mu-bu-cheng-dan-you-ci-zao-cheng-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-ze-ren": "4. Do not use this project in violation of local laws. Users are responsible for any legal violations.", "5-yin-le-ping-tai-bu-yi-qing-zun-zhong-ban-quan-zhi-chi-zheng-ban": "5. Please respect copyright and support legitimate music platforms.", "6-ben-xiang-mu-jin-yong-yu-dui-ji-shu-ke-hang-xing-de-tan-suo-ji-yan-jiu-bu-jie-shou-ren-he-shang-ye-bao-kuo-dan-bu-xian-yu-guang-gao-deng-he-zuo-ji-juan-zeng": "6. This project is for technical research only. No commercial cooperation or donations accepted.", "7-ru-guo-guan-fang-yin-le-ping-tai-jue-de-ben-xiang-mu-bu-tuo-ke-lian-xi-ben-xiang-mu-geng-gai-huo-yi-chu": "7. If rights holders have concerns, they may contact developers for removal.", "yong-hu-tiao-kuan": "Terms of Use", "1-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "1. This is an unofficial Kugou client. For full features, use the official app.", "2-ben-xiang-mu-jin-gong-xue-xi-jiao-liu-shi-yong-nin-zai-shi-yong-guo-cheng-zhong-ying-zun-zhong-ban-quan-bu-de-yong-yu-shang-ye-huo-fei-fa-yong-tu": "2. This project is for educational purposes. Respect copyright and do not use for commercial or illegal purposes.", "3-zai-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-sheng-cheng-ban-quan-nei-rong-ben-xiang-mu-bu-yong-you-zhe-xie-ban-quan-nei-rong-de-suo-you-quan-wei-le-bi-mian-qin-quan-hang-wei-nin-xu-zai-24-xiao-shi-nei-qing-chu-you-ben-xiang-mu-chan-sheng-de-ban-quan-nei-rong": "3. Usage may generate copyrighted content. Delete such content within 24 hours.", "4-ben-xiang-mu-de-kai-fa-zhe-bu-dui-yin-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-suo-dao-zhi-de-ren-he-sun-hai-cheng-dan-ze-ren-bao-kuo-dan-bu-xian-yu-shu-ju-diu-shi-ting-gong-ji-suan-ji-gu-zhang-huo-qi-ta-jing-ji-sun-shi": "4. Developers are not liable for any damages from use of this project.", "5-nin-bu-de-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-yin-wei-fan-fa-lv-fa-gui-suo-dao-zhi-de-ren-he-fa-lv-hou-guo-you-yong-hu-cheng-dan": "5. Do not use this project in violation of local laws. Users bear all legal consequences.", "6-ben-xiang-mu-jin-yong-yu-ji-shu-tan-suo-he-yan-jiu-bu-jie-shou-ren-he-shang-ye-he-zuo-guang-gao-huo-juan-zeng-ru-guo-guan-fang-yin-le-ping-tai-dui-ci-xiang-mu-cun-you-yi-lv-ke-sui-shi-lian-xi-kai-fa-zhe-yi-chu-xiang-guan-nei-rong": "6. This project is for technical research only. Rights holders may contact developers for removal.", "tong-yi-ji-xu-shi-yong-ben-xiang-mu-nin-ji-jie-shou-yi-shang-tiao-kuan-sheng-ming-nei-rong": "By continuing to use this project, you accept the above terms.", "tong-yi": "Accept", "bu-tong-yi": "Decline", "que-ding": "OK", "qu-xiao": "Cancel", "shao-nv-qi-dao-zhong": "Loading...", "zan-wu-ge-ci": "No Lyrics Available", "ni-huan-mei-you-tian-jia-ge-quo-kuai-qu-tian-jia-ba": "No tracks yet. Add some!", "huo-qu-ge-dan-shi-bai": "Failed to Load Playlist", "huo-qu-yin-le-shi-bai": "Failed to Load Music", "3-miao-hou-zi-dong-qie-huan-xia-yi-shou": "Next track in 3 seconds", "huo-qu-yin-le-di-zhi-shi-bai": "Failed to Get Track URL", "huo-qu-ge-ci-zhong": "Loading Lyrics...", "huo-qu-ge-ci-shi-bai": "Failed to Load Lyrics", "dan-qu-xun-huan": "Repeat Track", "lie-biao-xun-huan": "Repeat Playlist", "shun-xu-bo-fang": "Sequential", "sui-ji-bo-fang": "Shuffle", "bo-fang-lie-biao": "Play Queue", "zheng-zai-sheng-cheng-er-wei-ma": "Generating QR Code...", "zhe-li-shi-mo-du-mei-you": "Nothing Here Yet", "wu-sun-yin-zhi-1104kbps": "Lossless — 1104 Kbps", "gao-pin-zhi-yin-le-xu-yao-deng-lu-hou-cai-neng-bo-fango": "Log in for High Quality Playback", "tian-jia-ge-dan": "Add to Playlist", "qing-xian-deng-lu": "Please Log In First", "cheng-gong-tian-jia-dao-ge-dan": "Added to Playlist", "tian-jia-dao-ge-dan-shi-bai": "Failed to Add to Playlist", "cheng-gong-qu-xiao-shou-cang": "Removed from Favorites", "qu-xiao-shou-cang-shi-bai": "Failed to Remove", "ni-que-ren-yao-tui-chu-deng-lu-ma": "Log Out?", "gai-ge-qu-zan-wu-ban-quan": "Track Unavailable (No License)", "wo-shou-cang-de-zhuan-ji": "Saved Albums", "bo-fang-shi-bai": "Playback Failed", "wo-guan-zhu-de-hao-you": "Friends", "tian-jia-cheng-gong": "Added", "chuang-jian-ge-dan": "Create Playlist", "qing-shu-ru-xin-de-ge-dan-ming-cheng": "Enter Playlist Name", "chuang-jian-shi-bai": "Failed to Create", "que-ren-shan-chu-ge-dan": "Delete Playlist?", "shou-cang-cheng-gong": "Added to Favorites", "shou-cang-shi-bai": "Failed to Add to Favorites", "wo-guan-zhu-de-yi-ren": "Following Musicians", "sou-suo-ge-qu": "Search Tracks...", "dang-qian-bo-fang-ge-qu": "Now Playing", "fan-hui-ding-bu": "Back to Top", "yi-fu-zhi-fen-xiang-ma-qing-zai-moekoe-ke-hu-duan-zhong-fang-wen": "Code Copied. Open in MoeKoe", "kou-ling-yi-fu-zhi,kuai-ba-ge-qu-fen-xiang-gei-peng-you-ba": "Copied! Share with Friends", "ge-qu-shu-ju-cuo-wu": "Playlist Not Found", "xi-tong": "System", "quan-ju-kuai-jie-jian": "Global Shortcuts", "zi-ding-yi-kuai-jie-jian": "Customize Shortcuts", "kuai-jie-jian-she-zhi": "Keyboard Shortcuts", "xian-shi-yin-cang-zhu-chuang-kou": "Show/Hide Window", "tui-chu-zhu-cheng-xu": "Quit Application", "shang-yi-shou": "Previous Track", "xia-yi-shou": "Next Track", "zan-ting-bo-fang": "Play/Pause", "yin-liang-zeng-jia": "Volume Up", "yin-liang-jian-xiao": "Volume Down", "jing-yin": "Mute", "bao-cun": "Save", "fei-ke-hu-duan-huan-jing-wu-fa-qi-yong": "Not Available in Web Version", "qing-an-xia-xiu-shi-jian": "Press Modifier Key", "qing-an-xia-qi-ta-jian": "+ [Press a Key]", "kuai-jie-jian-bi-xu-bao-han-zhi-shao-yi-ge-xiu-shi-jian-ctrlaltshiftcommand": "Shortcut Must Include Ctrl/Alt/Shift/Command", "gai-kuai-jie-jian-yu": "This Shortcut Conflicts with ", "de-kuai-jie-jian-chong-tu": "", "cun-zai-wu-xiao-de-kuai-jie-jian-she-zhi-qing-que-bao-mei-ge-kuai-jie-jian-du-bao-han-xiu-shi-jian": "Invalid Shortcut. Add a Modifier Key", "bao-cun-she-zhi-shi-bai": "Failed to Save Settings", "mei-ri-tui-jian": "Daily Recommendations", "mei-you-zheng-zai-bo-fang-de-ge-qu": "Nothing Playing", "shou-cang-dao": "Save to", "mei-you-ge-dan": "No Playlists", "jin-yong-gpu-jia-su-zhong-qi-sheng-xiao": "Disable GPU Acceleration", "jie-mian": "Interface", "guan-bi-shi-minimize-to-tray": "Minimize to Tray on Close", "mi-gan-cheng": "Orange", "zhu-ce": "No Account?", "xin-zeng-zhang-hao-qing-xian-zai-guan-fang-ke-hu-duan-zhong-deng-lu-yi-ci": "New Account? Log in to Official App First", "shua-xin-hou-sheng-xiao": "(After Refresh)", "zhong-qi-hou-sheng-xiao": "(After Restart)", "shi-pei-gao-dpi": "High DPI Support", "hires-yin-zhi": "Hi-Res", "kui-she-chao-qing-yin-zhi": "Viper Ultra HD", "tian-jia-wo-xi-huan": "Add to Liked", "qie-huan-bo-fang-mo-shi": "Change Playback Mode", "ke-yong": "Available", "wo-de-yun-pan": "My Cloud", "yun-pan-ge-qu-shu": "Cloud Tracks", "yun-pan-miao-shu": "Your uploaded music files", "yun-pan-ge-qu": "Cloud Music", "cong-yun-pan-shan-chu": "Remove from Cloud", "que-ren-shan-chu-yun-pan-ge-qu": "Remove Selected Tracks from Cloud?", "shang-chuan-yin-le": "Upload", "pi-liang-cao-zuo": "Select Multiple", "pwa-app": "PWA App", "install": "Install", "yin-pin-jia-zai-shi-bai": "Failed to Load Audio", "guan-zhu": "Following", "fen-si": "Followers", "hao-you": "Friends", "fang-wen": "Views", "qian-dao": "Check In", "xiao-shi": "hrs", "fen-zhong": "min", "le-ling": "Member Since", "nian": "yrs", "ting-ge-shi-chang": "Listening Time", "zheng-zai-jia-zai-quan-bu-ge-qu": "Loading All Tracks...", "bo-fang-chu-cuo": "Playback Error", "bo-fang-shi-bai-qu-mu-wei-kong": "Playlist Is Empty", "shan-chu-cheng-gong": "Deleted", "tian-jia-dao-bo-fang-lie-biao-cheng-gong": "Added to Queue", "hot": "Hot", "new": "New", "yun-pan-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "Cloud tracks cannot be added to playlists", "xian-qu-kan-kan-ni-de-shou-cang-jia-ba": "Check your favorites first", "yi-da-dao-zui-da-chong-shi-ci-shu": "Max retries reached. Please select a track manually", "hui-fu-chu-chang-she-zhi-cheng-gong": "Settings reset. Restart to apply", "liu-lan-qi-bu-zhi-chi-file-system-api": "Browser doesn't support File System API. Use Chrome 86+ or Edge 86+", "cha-jian-an-zhuang-cheng-gong": "Plugin installed", "zhi-chi-http-https-dai-li": "Only HTTP/HTTPS proxies are supported", "ben-di-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "Local tracks cannot be added to playlists", "mei-you-xuan-ze-zheng-que-de-ge-qu": "No track selected", "zhuang-tai-lan-ge-ci-jin-zhi-chi-mac": "Menu bar lyrics only available on macOS", "qian-dao-shi-bai": "Check-in failed. Don't check in too frequently", "huo-qu-vip-shi-bai": "Failed to get VIP. Available once per day", "qing-zai-web-huan-jing-xia-an-zhuang": "Install in web version", "qing-shu-ru-you-xiao-de-url": "Enter a valid URL", "zhe-shi-yi-ge-alert": "Alert", "fei-mac-bu-zhi-chi-touchbar": "TouchBar only available on Mac", "zi-ti-she-zhi": "Font Settings", "mo-ren-zi-ti": "Default Font", "hui-fu-chu-chang-she-zhi": "Reset Settings", "zi-ti-url-di-zhi": "Font URL", "qing-shu-ru-zi-ti-url-di-zhi": "Enter font URL", "zi-ti-ming-cheng": "Font Name", "qing-shu-ru-zi-ti-ming-cheng": "Enter font name", "cha-jian": "Plugins", "shua-xin-cha-jian": "Refresh", "da-kai-cha-jian-mu-lu": "Open Folder", "an-zhuang-cha-jian": "Install", "zan-wu-cha-jian": "No Plugins", "jiang-cha-jian-wen-jian-jia-fang-ru-cha-jian-mu-lu": "Place plugin folder in the plugins directory and click Refresh", "kai-ji-zi-qi-dong": "Launch at Startup", "wang-luo-mo-shi": "Network Mode", "zhu-wang": "Main Network", "qi-dong-shi-zui-xiao-hua": "Start Minimized", "zu-zhi-xi-tong-xiu-mian": "Prevent Sleep", "api-mo-shi": "API Mode", "wang-luo-dai-li": "Network Proxy", "zhuang-tai-lan-ge-ci": "Menu Bar Lyrics", "ge-ci-fan-yi": "Lyrics Translation", "dui-qi-fang-shi": "Alignment", "ju-zhong": "Center", "ju-zuo": "Left", "ju-you": "Right", "ping-heng-yin-pin-xiang-du": "Normalize Loudness", "shu-ju-yuan": "Data Source", "suo-fang-yin-zi": "Scale Factor", "tiao-zheng-hou-xu-zhong-qi": "Restart app after adjustment", "api-di-zhi": "API Address", "websocket-di-zhi": "WebSocket Address", "mo-ren-api-ti-shi": "These are default API addresses. Custom modification not supported in this version", "dai-li-placeholder": "Enter HTTP/HTTPS proxy address, e.g.: http://127.0.0.1:7890", "zheng-zai-ce-shi": "Testing...", "ce-shi-lian-jie": "Test Connection", "bao-cun-she-zhi-an-niu": "Save", "qing-shu-ru-dai-li-di-zhi": "Enter proxy server address", "ce-wang": "Testnet", "kai-fa-wang": "Devnet", "qi-yong": "Enabled", "jin-yong": "Disabled", "dai-li-di-zhi": "Proxy Address", "gai-nian-ban-xuan-xiang": "Concept", "zheng-shi-ban": "Official", "dai-li-lian-jie-cheng-gong": "Proxy connected, IP: ", "dai-li-lian-jie-shi-bai": "Proxy connection failed: ", "lian-jie-chao-shi": "Connection timeout", "lian-jie-cuo-wu": "Connection error: ", "jin-zhi-chi-mac": " (macOS only)", "xian-shi-yin-cang-zhuo-mian-ge-ci": "Show/Hide Desktop Lyrics", "ni-que-ren-hui-fu-chu-chang": "Reset all settings? This cannot be undone!", "bang-zhu": "Help", "dian-ji-she-zhi-kuai-jie-jian": "Click to set shortcut", "wang-luo-jie-dian": "Network Node", "zi-ti-wen-jian-di-zhi": "Font File URL", "jia-zai-zhong": "Loading...", "ban-ben": "Version", "yi-qi-yong": "Enabled", "da-kai-tan-chuang": "Settings", "xie-zai": "Uninstall", "zheng-zai-jia-zai-cha-jian": "Loading plugins...", "web-cha-jian-ti-shi": "In web version, manage extensions via chrome://extensions/", "da-kai-tan-chuang-shi-bai": "Failed to open plugin window", "que-ren-xie-zai-cha-jian": "Uninstall plugin name?", "xie-zai-cha-jian-shi-bai": "Failed to uninstall plugin", "xuan-ze-wen-jian-shi-bai": "Failed to select file", "an-zhuang-cha-jian-shi-bai": "Failed to install plugin", "an-zhuang-cha-jian-chu-cuo": "Error installing plugin", "cha-jian-bao": "Plugin archive", "zhuo-mian-ge-ci": "Desktop Lyrics", "bo-fang-su-du": "Playback Speed", "wo-xi-huan": "Like", "shou-cang-zhi": "Save to", "fen-xiang-ge-qu": "Share", "qie-huan-dao-yin-yi": "Switch to Romanization", "qie-huan-dao-fan-yi": "Switch to Translation", "wei-zhi-cuo-wu": "Unknown error" } ================================================ FILE: src/language/ja.json ================================================ { "tui-jian": "推薦する", "tui-jian-ge-qu": "おすすめの曲", "tui-jian-ge-dan": "おすすめのプレイリスト", "fa-xian": "発見する", "da": "大きい", "da-kai": "開ける", "gao-yin-zhi-320kbps": "高品音質 - 320Kbps", "ge-ci": "歌詞", "ge-ci-zi-ti-da-xiao": "フルスクリーンの歌詞フォントサイズ", "guan-bi": "閉鎖", "guan-bi-an-niu": "閉じる", "kai-qi": "オンにする", "nan-nan-lan": "スカイブルー", "pu-tong-yin-zhi": "通常の音質 - 128Kbps", "qi-dong-wen-hou-yu": "挨拶を始める", "qian-se": "明るい色", "shao-nv-fen": "ガーリーピンク", "shen-se": "暗い", "sheng-yin": "音", "tou-ding-lv": "ミントグリーン", "wai-guan": "外観", "native-title-bar":"ネイティブタイトルバー", "xian-shi-ge-ci-bei-jing": "フルスクリーンの歌詞の背景カバーを表示します", "xian-shi-zhuo-mian-ge-ci": "デスクトップの歌詞を表示する", "xiao": "小さい", "xuan-ze-wai-guan": "外観の選択", "xuan-ze-yu-yan": "言語を選択してください", "xuan-ze-zhu-se-tiao": "メインカラーを選択してください", "yin-zhi-xuan-ze": "音質の選択", "yu-yan": "言語", "zhong": "真ん中", "zhu-se-tiao": "メインカラー", "zi-dong": "自動", "0-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "0. このプログラムは Kugou のサードパーティ クライアントであり、Kugou 株式会社ではありません。より完全な機能が必要な場合は、株式会社クライアントをダウンロードして体験してください。", "1-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "1. このプログラムは Kugou のサードパーティ クライアントであり、Kugou 株式会社ではありません。より完全な機能が必要な場合は、株式会社クライアントをダウンロードして体験してください。", "1-ben-xiang-mu-jin-gong-xue-xi-shi-yong-qing-zun-zhong-ban-quan-qing-wu-li-yong-ci-xiang-mu-cong-shi-shang-ye-hang-wei-ji-fei-fa-yong-tu": "1. このプロジェクトは学習のみを目的としています。著作権を尊重してください。商業活動や違法な目的には使用しないでください。", "2-ben-xiang-mu-jin-gong-xue-xi-jiao-liu-shi-yong-nin-zai-shi-yong-guo-cheng-zhong-ying-zun-zhong-ban-quan-bu-de-yong-yu-shang-ye-huo-fei-fa-yong-tu": "2. このプロジェクトは学習とコミュニケーションのみを目的としており、使用中は著作権を尊重する必要があり、商業目的または違法な目的で使用することはできません。", "2-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-chan-sheng-ban-quan-shu-ju-dui-yu-zhe-xie-ban-quan-shu-ju-ben-xiang-mu-bu-yong-you-ta-men-de-suo-you-quan-wei-le-bi-mian-qin-quan-shi-yong-zhe-wu-bi-zai-24-xiao-shi-nei-qing-chu-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-suo-chan-sheng-de-ban-quan-shu-ju": "2. 本プロジェクトの利用中に著作権データが発生する場合があります。\nこのプロジェクトは、これらの著作権で保護されたデータの所有権を所有しません。\n侵害を避けるために、ユーザーはこのプロジェクトの使用中に生成された著作権データを 24 時間以内に消去する必要があります。", "3-miao-hou-zi-dong-qie-huan-xia-yi-shou": "3秒後に自動的に次の曲に切り替わります", "3-you-yu-shi-yong-ben-xiang-mu-chan-sheng-de-bao-kuo-you-yu-ben-xie-yi-huo-you-yu-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-er-yin-qi-de-ren-he-xing-zhi-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-sun-hai-bao-kuo-dan-bu-xian-yu-yin-shang-yu-sun-shi-ting-gong-ji-suan-ji-gu-zhang-huo-gu-zhang-yin-qi-de-sun-hai-pei-chang-huo-ren-he-ji-suo-you-qi-ta-shang-ye-sun-hai-huo-sun-shi-you-shi-yong-zhe-fu-ze": "3. 本契約または本プロジェクトの使用または使用不能から生じる、あらゆる性質の直接的、間接的、特別、付随的または結果的損害(信用の喪失、業務の停止、コンピュータの故障または誤動作に起因する損害を含みますがこれらに限定されません) 、またはその他すべての商業上の損害または損失)は、ユーザーの責任となります。", "3-zai-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-sheng-cheng-ban-quan-nei-rong-ben-xiang-mu-bu-yong-you-zhe-xie-ban-quan-nei-rong-de-suo-you-quan-wei-le-bi-mian-qin-quan-hang-wei-nin-xu-zai-24-xiao-shi-nei-qing-chu-you-ben-xiang-mu-chan-sheng-de-ban-quan-nei-rong": "3. 本プロジェクトの利用過程において、著作権で保護されたコンテンツが生成される場合があります。\nこのプロジェクトは、これらの著作権で保護されたコンテンツの所有権を所有しません。\n侵害を避けるために、このプロジェクトによって生成された著作権で保護されたコンテンツを 24 時間以内に削除する必要があります。", "4-ben-xiang-mu-de-kai-fa-zhe-bu-dui-yin-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-suo-dao-zhi-de-ren-he-sun-hai-cheng-dan-ze-ren-bao-kuo-dan-bu-xian-yu-shu-ju-diu-shi-ting-gong-ji-suan-ji-gu-zhang-huo-qi-ta-jing-ji-sun-shi": "4. 本プロジェクトの開発者は、データの損失、ダウンタイム、コンピュータの故障、またはその他の経済的損失を含むがこれらに限定されない、本プロジェクトの使用または使用不能から生じるいかなる損害についても責任を負いません。", "4-jin-zhi-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-dui-yu-shi-yong-zhe-zai-ming-zhi-huo-bu-zhi-dang-di-fa-lv-fa-gui-bu-yun-xu-de-qing-kuang-xia-shi-yong-ben-xiang-mu-suo-zao-cheng-de-ren-he-wei-fa-wei-gui-hang-wei-you-shi-yong-zhe-cheng-dan-ben-xiang-mu-bu-cheng-dan-you-ci-zao-cheng-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-ze-ren": "4. 現地の法令に違反して本プロジェクトを使用することは禁止されています。\nユーザーは、現地の法律や規制がこのプロジェクトの使用を許可していないことを知っているかどうかにかかわらず、ユーザーが引き起こした違法行為の責任を負うものとし、それによって生じる直接的、間接的、特別、偶発的、結果的責任を負いません。 。", "5-nin-bu-de-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-yin-wei-fan-fa-lv-fa-gui-suo-dao-zhi-de-ren-he-fa-lv-hou-guo-you-yong-hu-cheng-dan": "5. 現地の法律や規制に違反してこのプロジェクトを使用することはできません。\n法令違反による法的責任は利用者の負担となります。", "5-yin-le-ping-tai-bu-yi-qing-zun-zhong-ban-quan-zhi-chi-zheng-ban": "5. 音楽プラットフォームは簡単ではありません。著作権を尊重し、本物のプラットフォームをサポートしてください。", "6-ben-xiang-mu-jin-yong-yu-dui-ji-shu-ke-hang-xing-de-tan-suo-ji-yan-jiu-bu-jie-shou-ren-he-shang-ye-bao-kuo-dan-bu-xian-yu-guang-gao-deng-he-zuo-ji-juan-zeng": "6. 本プロジェクトは技術的実現可能性の探究・研究のみを目的としており、営利(広告等を含みますがこれに限定されません)の協力・寄付等は一切受け付けておりません。", "6-ben-xiang-mu-jin-yong-yu-ji-shu-tan-suo-he-yan-jiu-bu-jie-shou-ren-he-shang-ye-he-zuo-guang-gao-huo-juan-zeng-ru-guo-guan-fang-yin-le-ping-tai-dui-ci-xiang-mu-cun-you-yi-lv-ke-sui-shi-lian-xi-kai-fa-zhe-yi-chu-xiang-guan-nei-rong": "6. このプロジェクトは技術の探索と研究のみに使用され、商業的な協力、広告、寄付は一切受け付けません。\n音楽会社プラットフォームがこのプロジェクトに関して懸念がある場合は、いつでも開発者に連絡して関連コンテンツを削除することができます。", "7-ru-guo-guan-fang-yin-le-ping-tai-jue-de-ben-xiang-mu-bu-tuo-ke-lian-xi-ben-xiang-mu-geng-gai-huo-yi-chu": "7. 音楽会社プラットフォームがこのプロジェクトが不適切であると判断した場合は、このプロジェクトに連絡して変更または削除することができます。", "bo-fang": "遊ぶ", "bo-fang-lie-biao": "プレイリスト", "bu-tong-yi": "同意しない", "chang-ting-ban": "無料試聴版:", "dan-qu-xun-huan": "シングルループ", "de-yin-le-ku": "の音楽ライブラリ", "deng-lu": "ログイン", "deng-lu-cheng-gong": "ログイン成功", "deng-lu-ni-de-ku-gou-zhang-hao": "Kugou アカウントにログインします", "deng-lu-shi-bai": "ログインに失敗しました", "deng-lu-shi-bai-0": "ログインに失敗しました、", "deng-lu-shi-xiao-qing-zhong-xin-deng-lu": "ログインに失敗しました。再度ログインしてください", "di": "いいえ。", "er-wei-ma": "QRコード", "er-wei-ma-deng-lu-cheng-gong": "QRコードログイン成功", "er-wei-ma-jian-ce-shi-bai": "QRコードの検出に失敗しました", "er-wei-ma-sheng-cheng-shi-bai": "QRコードの生成に失敗しました", "er-wei-ma-yi-guo-qi-qing-zhong-xin-sheng-cheng": "QRコードの有効期限が切れていますので再生成してください", "fa-song-yan-zheng-ma": "確認コードを送信する", "gai-nian-ban": "コンセプトバージョン:", "ge-qu-lie-biao": "曲リスト", "geng-xin": "更新する", "gong": "一般", "guan-yu": "について", "huo-qu-ge-ci-shi-bai": "歌詞の取得に失敗しました", "huo-qu-ge-ci-zhong": "歌詞を取得しています...", "huo-qu-ge-dan-shi-bai": "プレイリストの取得に失敗しました", "huo-qu-yin-le-di-zhi-shi-bai": "音楽アドレスの取得に失敗しました", "huo-qu-yin-le-shi-bai": "音楽の取得に失敗しました", "li-ji-deng-lu": "今すぐログインしてください", "lie-biao-xun-huan": "リストループ", "login-tips": "MoeKoe は、アカウント情報をクラウドに保存しないことを約束します。\nあなたのパスワードはローカルで暗号化され、Kugou 株式会社に送信されます。 \nMengyin は Kugou の株式会社 Web サイトではありません。アカウント情報を入力する前に慎重に検討してください。すべてのアカウントがアカウントパスワードログインをサポートしているわけではありません", "mian-ze-sheng-ming": "免責事項", "ni-huan-mei-you-tian-jia-ge-quo-kuai-qu-tian-jia-ba": "まだ曲を追加していませんので、追加してください。", "qing-shi-yong-ku-gou-sao-miao-er-wei-ma-deng-lu": "Kugou を使用して QR コードをスキャンしてログインしてください", "qing-shu-ru-deng-lu-you-xiang": "ログインアカウントを入力してください", "qing-shu-ru-mi-ma": "パスワードを入力してください", "qing-shu-ru-shou-ji-hao": "携帯電話番号を入力してください", "qing-shu-ru-shou-ji-hao-ma": "携帯電話番号を入力してください", "qing-shu-ru-yan-zheng-ma": "確認コードを入力してください", "qing-shu-ru-you-xiang": "アカウント番号を入力してください", "qu-xiao": "キャンセル", "que-ding": "もちろん", "sao-ma-deng-lu": "コードをスキャンしてログインします", "shang-yi-ye": "前のページへ", "shao-nv-qi-dao-zhong": "少女は祈っている……。", "she-zhi": "設定", "shi-yong-yan-zheng-ma-deng-lu": "確認コードを使用してログインします。", "shou-ge": "首の歌", "shou-ji-hao-deng-lu": "携帯電話番号ログイン", "shou-ji-hao-ge-shi-cuo-wu": "携帯電話番号の形式エラー", "shou-ye": "フロントページ", "shun-xu-bo-fang": "順番に再生する", "sou-suo-jie-guo": "検索結果", "sou-suo-yin-le-ge-shou-ge-dan": "音楽、歌手、プレイリスト、共有コードを検索...", "sui-ji-bo-fang": "シャッフル", "tong-yi": "同意する", "tong-yi-ji-xu-shi-yong-ben-xiang-mu-nin-ji-jie-shou-yi-shang-tiao-kuan-sheng-ming-nei-rong": "このプロジェクトの使用を継続することに同意すると、上記の規約と声明に同意したことになります。", "tui-chu": "やめる", "wo-chuang-jian-de-ge-dan": "私が作成したプレイリスト", "wo-guan-zhu-de-ge-shou": "私がフォローしている歌手", "wo-shou-cang-de-ge-dan": "私のプレイリストのコレクション", "wo-xi-huan-ting": "聞くのが好きです", "wu-xiang-ying-shu-ju": "応答データがありません", "xia-yi-ye": "次のページ", "yan-zheng-ma-fa-song-shi-bai": "確認コードの送信に失敗しました", "yan-zheng-ma-fa-song-shi-bai-0": "確認コードの送信に失敗しました。", "yan-zheng-ma-yi-fa-song": "認証コードが送信されました", "ye": "ページ", "yi-sao-ma-deng-dai-que-ren": "コードをスキャンしました。確認を待っています", "yin-le-ku": "音楽ライブラリ", "yong-hu": "ユーザー", "yong-hu-tiao-kuan": "ユーザー規約", "yong-hu-tou-xiang": "ユーザーのアバター", "you-xiang-deng-lu": "アカウントログイン", "you-xiang-ge-shi-cuo-wu": "メール形式エラー", "zan-wu-ge-ci": "まだ歌詞がありません", "huo-qu-er-wei-ma-shi-bai": "QRコードの取得に失敗しました", "zheng-zai-sheng-cheng-er-wei-ma": "QRコードを生成中...", "zhe-li-shi-mo-du-mei-you": "ここには何もない", "wu-sun-yin-zhi-1104kbps": "ロスレス音質 - 1104kbps", "gao-pin-zhi-yin-le-xu-yao-deng-lu-hou-cai-neng-bo-fango": "高品質の音楽を再生するにはログインが必要です~", "tian-jia-ge-dan": "プレイリストに追加", "qing-xian-deng-lu": "まずログインしてください", "cheng-gong-tian-jia-dao-ge-dan": "プレイリストに追加されました!", "tian-jia-dao-ge-dan-shi-bai": "プレイリストに追加できませんでした!", "cheng-gong-qu-xiao-shou-cang": "お気に入りをキャンセルする", "qu-xiao-shou-cang-shi-bai": "キャンセルに失敗しました", "ni-que-ren-yao-tui-chu-deng-lu-ma": "ログアウトしてもよろしいですか?", "gai-ge-qu-zan-wu-ban-quan": "この曲には著作権がありません", "wo-shou-cang-de-zhuan-ji": "私がコレクションしたアルバム", "bo-fang-shi-bai": "再生に失敗しました", "wo-guan-zhu-de-hao-you": "私がフォローしている友達", "tian-jia-cheng-gong": "正常に追加されました", "chuang-jian-ge-dan": "プレイリストの作成", "qing-shu-ru-xin-de-ge-dan-ming-cheng": "新しいプレイリスト名を入力してください", "chuang-jian-shi-bai": "作成に失敗しました", "que-ren-shan-chu-ge-dan": "プレイリストを削除しますか?", "shou-cang-shi-bai": "収集に失敗しました", "shou-cang-cheng-gong": "収集に成功しました", "wo-guan-zhu-de-yi-ren": "私がフォローしているミュージシャン", "sou-suo-ge-qu": "曲を検索...", "fan-hui-ding-bu": "トップに戻る", "dang-qian-bo-fang-ge-qu": "現在再生中の曲", "yi-fu-zhi-fen-xiang-ma-qing-zai-moekoe-ke-hu-duan-zhong-fang-wen": "共有コードがコピーされました。MoeKoe クライアントで表示してください。", "ge-qu-shu-ju-cuo-wu": "プレイリストが存在しません", "bao-cun": "保存", "bao-cun-she-zhi-shi-bai": "設定の保存に失敗しました", "cun-zai-wu-xiao-de-kuai-jie-jian-she-zhi-qing-que-bao-mei-ge-kuai-jie-jian-du-bao-han-xiu-shi-jian": "無効なショートカット キー設定があります。各ショートカット キーに修飾キーが含まれていることを確認してください。", "de-kuai-jie-jian-chong-tu": "ショートカットキーの競合", "fei-ke-hu-duan-huan-jing-wu-fa-qi-yong": "非クライアント環境では有効にできません", "gai-kuai-jie-jian-yu": "このショートカットキーは、", "jing-yin": "ミュート", "kuai-jie-jian-bi-xu-bao-han-zhi-shao-yi-ge-xiu-shi-jian-ctrlaltshiftcommand": "ショートカットキーには、少なくとも1つの修飾子(ctrl/alt/shift/command)を含める必要があります", "kuai-jie-jian-she-zhi": "ショートカットキーの設定", "qing-an-xia-qi-ta-jian": "[他のキーを押してください]", "qing-an-xia-xiu-shi-jian": "修飾キーを押してください", "quan-ju-kuai-jie-jian": "グローバルショートカットキー", "shang-yi-shou": "最後", "tui-chu-zhu-cheng-xu": "メインプログラムを終了する", "xi-tong": "システム", "xia-yi-shou": "次", "xian-shi-yin-cang-zhu-chuang-kou": "メインウィンドウを表示/非表示にします", "yin-liang-jian-xiao": "音量を下げる", "yin-liang-zeng-jia": "音量の増加", "zan-ting-bo-fang": "一時停止/再生", "zi-ding-yi-kuai-jie-jian": "カスタムショートカットキー", "mei-ri-tui-jian": "毎日の推奨", "mei-you-zheng-zai-bo-fang-de-ge-qu": "演奏している曲はありません", "shou-cang-dao": "集める", "mei-you-ge-dan": "曲リストはありません", "jin-yong-gpu-jia-su-zhong-qi-sheng-xiao": "GPU加速度を無効にします", "jie-mian": "インタフェース", "guan-bi-shi-minimize-to-tray": "窓を閉めるときにトレイに移動します", "mi-gan-cheng": "ミアンオレンジ", "zhu-ce": "まだアカウントを持っていませんか?", "xin-zeng-zhang-hao-qing-xian-zai-guan-fang-ke-hu-duan-zhong-deng-lu-yi-ci": "新しいアカウントについては、公式クライアントにログインしてください", "shua-xin-hou-sheng-xiao": "(リフレッシュ後に効果的)", "zhong-qi-hou-sheng-xiao": "(再起動後に有効)", "shi-pei-gao-dpi": "高DPIサポートを有効にする", "kui-she-chao-qing-yin-zhi": "Viper Ultra-definition Sound Quality", "hires-yin-zhi": "高解像度の音質", "tian-jia-wo-xi-huan": "私のお気に入りに追加してください", "qie-huan-bo-fang-mo-shi": "再生モードを切り替えます", "pwa-app": "PWA アプリケーション", "install": "インストール", "yin-pin-jia-zai-shi-bai": "音楽のロードに失敗しました", "zheng-zai-jia-zai-quan-bu-ge-qu": "すべての曲を読み込んでいます...", "bo-fang-chu-cuo": "再生エラー", "bo-fang-shi-bai-qu-mu-wei-kong": "再生失敗、曲目が空です", "shan-chu-cheng-gong": "削除成功", "tian-jia-dao-bo-fang-lie-biao-cheng-gong": "再生リストに追加しました", "hot": "人気", "new": "最新", "yun-pan-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "クラウド音楽はプレイリストに追加できません", "xian-qu-kan-kan-ni-de-shou-cang-jia-ba": "お気に入りを確認してください", "yi-da-dao-zui-da-chong-shi-ci-shu": "最大リトライ回数に達しました。曲を手動で選択してください", "hui-fu-chu-chang-she-zhi-cheng-gong": "初期設定に戻しました。アプリを再起動してください", "liu-lan-qi-bu-zhi-chi-file-system-api": "ブラウザはFile System APIをサポートしていません。Chrome 86+またはEdge 86+をご使用ください", "cha-jian-an-zhuang-cheng-gong": "プラグインをインストールしました", "zhi-chi-http-https-dai-li": "HTTP/HTTPSプロキシのみサポート", "ben-di-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "ローカル音楽はプレイリストに追加できません", "mei-you-xuan-ze-zheng-que-de-ge-qu": "曲が選択されていません", "zhuang-tai-lan-ge-ci-jin-zhi-chi-mac": "メニューバー歌詞はmacOSのみ対応", "qian-dao-shi-bai": "チェックイン失敗。頻繁にチェックインしないでください", "huo-qu-vip-shi-bai": "VIP取得失敗。1日1回のみ取得可能", "qing-zai-web-huan-jing-xia-an-zhuang": "Web版でインストールしてください", "qing-shu-ru-you-xiao-de-url": "有効なURLを入力してください", "zhe-shi-yi-ge-alert": "お知らせ", "fei-mac-bu-zhi-chi-touchbar": "TouchBarはMacのみ対応", "zi-ti-she-zhi": "フォント設定", "mo-ren-zi-ti": "デフォルトフォント", "hui-fu-chu-chang-she-zhi": "初期設定に戻す", "zi-ti-url-di-zhi": "フォントURL", "qing-shu-ru-zi-ti-url-di-zhi": "フォントURLを入力", "zi-ti-ming-cheng": "フォント名", "qing-shu-ru-zi-ti-ming-cheng": "フォント名を入力", "cha-jian": "プラグイン", "shua-xin-cha-jian": "プラグイン更新", "da-kai-cha-jian-mu-lu": "プラグインフォルダを開く", "an-zhuang-cha-jian": "プラグインをインストール", "zan-wu-cha-jian": "プラグインなし", "jiang-cha-jian-wen-jian-jia-fang-ru-cha-jian-mu-lu": "プラグインフォルダをディレクトリに入れて、更新ボタンをクリック", "kai-ji-zi-qi-dong": "自動起動", "wang-luo-mo-shi": "ネットワークモード", "zhu-wang": "メインネット", "qi-dong-shi-zui-xiao-hua": "起動時に最小化", "zu-zhi-xi-tong-xiu-mian": "スリープを防止", "api-mo-shi": "APIモード", "wang-luo-dai-li": "ネットワークプロキシ", "zhuang-tai-lan-ge-ci": "メニューバー歌詞", "ge-ci-fan-yi": "歌詞翻訳", "dui-qi-fang-shi": "配置", "ju-zhong": "中央", "ju-zuo": "左", "ju-you": "右", "ping-heng-yin-pin-xiang-du": "ラウドネス正規化", "shu-ju-yuan": "データソース", "suo-fang-yin-zi": "スケールファクター", "tiao-zheng-hou-xu-zhong-qi": "調整後は再起動が必要", "api-di-zhi": "APIアドレス", "websocket-di-zhi": "WebSocketアドレス", "mo-ren-api-ti-shi": "これはデフォルトのAPIアドレスです。現在のバージョンではカスタム変更はサポートされていません", "dai-li-placeholder": "HTTP/HTTPSプロキシアドレスを入力、例:http://127.0.0.1:7890", "zheng-zai-ce-shi": "テスト中...", "ce-shi-lian-jie": "接続テスト", "bao-cun-she-zhi-an-niu": "保存", "qing-shu-ru-dai-li-di-zhi": "プロキシサーバーアドレスを入力", "ce-wang": "テストネット", "kai-fa-wang": "開発ネット", "qi-yong": "有効", "jin-yong": "無効", "dai-li-di-zhi": "プロキシアドレス", "gai-nian-ban-xuan-xiang": "コンセプト版", "zheng-shi-ban": "正式版", "dai-li-lian-jie-cheng-gong": "プロキシ接続成功、IP:", "dai-li-lian-jie-shi-bai": "プロキシ接続失敗:", "lian-jie-chao-shi": "接続タイムアウト", "lian-jie-cuo-wu": "接続エラー:", "jin-zhi-chi-mac": "(macOSのみ)", "xian-shi-yin-cang-zhuo-mian-ge-ci": "デスクトップ歌詞を表示/非表示", "ni-que-ren-hui-fu-chu-chang": "初期設定に戻しますか?この操作は元に戻せません!", "bang-zhu": "ヘルプ", "dian-ji-she-zhi-kuai-jie-jian": "クリックしてショートカットを設定", "wang-luo-jie-dian": "ネットワークノード", "zi-ti-wen-jian-di-zhi": "フォントファイルURL", "jia-zai-zhong": "読み込み中...", "ban-ben": "バージョン", "yi-qi-yong": "有効", "da-kai-tan-chuang": "設定", "xie-zai": "アンインストール", "zheng-zai-jia-zai-cha-jian": "プラグインを読み込み中...", "web-cha-jian-ti-shi": "Web版では chrome://extensions/ で拡張機能を管理してください", "da-kai-tan-chuang-shi-bai": "ポップアップを開けませんでした", "que-ren-xie-zai-cha-jian": "プラグイン name をアンインストールしますか?", "xie-zai-cha-jian-shi-bai": "プラグインのアンインストールに失敗しました", "xuan-ze-wen-jian-shi-bai": "ファイル選択に失敗しました", "an-zhuang-cha-jian-shi-bai": "プラグインのインストールに失敗しました", "an-zhuang-cha-jian-chu-cuo": "プラグインのインストール中にエラー", "cha-jian-bao": "プラグインパッケージ", "zhuo-mian-ge-ci": "デスクトップ歌詞", "bo-fang-su-du": "再生速度", "wo-xi-huan": "お気に入り", "shou-cang-zhi": "保存先", "fen-xiang-ge-qu": "共有", "qie-huan-dao-yin-yi": "ローマ字に切替", "qie-huan-dao-fan-yi": "翻訳に切替", "wei-zhi-cuo-wu": "不明なエラー" } ================================================ FILE: src/language/ko.json ================================================ { "tui-jian": "추천하다", "tui-jian-ge-qu": "추천곡", "tui-jian-ge-dan": "추천 재생목록", "fa-xian": "발견하다", "da": "큰", "da-kai": "열려 있는", "gao-yin-zhi-320kbps": "고품질 사운드 - 320Kbps", "ge-ci": "가사", "ge-ci-zi-ti-da-xiao": "전체 화면 가사 글꼴 크기", "guan-bi": "폐쇄", "guan-bi-an-niu": "닫기", "kai-qi": "켜다", "nan-nan-lan": "하늘색", "pu-tong-yin-zhi": "일반 음질 - 128Kbps", "qi-dong-wen-hou-yu": "인사말 시작", "qian-se": "연한 색", "shao-nv-fen": "만나고 핑크", "shen-se": "어두운", "sheng-yin": "소리", "tou-ding-lv": "민트 그린", "wai-guan": "모습", "native-title-bar":"네이티브 타이틀 바", "xian-shi-ge-ci-bei-jing": "전체 스크린 가사 배경 표지 표시", "xian-shi-zhuo-mian-ge-ci": "데스크톱 가사 표시", "xiao": "작은", "xuan-ze-wai-guan": "외관 선택", "xuan-ze-yu-yan": "언어 선택", "xuan-ze-zhu-se-tiao": "메인 컬러 선택", "yin-zhi-xuan-ze": "음질 선택", "yu-yan": "언어", "zhong": "가운데", "zhu-se-tiao": "메인 컬러", "zi-dong": "오토매틱", "0-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "0. 이 프로그램은 Kugou 공식이 아닌 타사 클라이언트입니다. 더 완전한 기능이 필요한 경우 공식 클라이언트를 다운로드하여 사용해 보세요.", "1-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "1. 이 프로그램은 Kugou 공식이 아닌 타사 클라이언트입니다. 더 완전한 기능이 필요한 경우 공식 클라이언트를 다운로드하여 사용해 보세요.", "1-ben-xiang-mu-jin-gong-xue-xi-shi-yong-qing-zun-zhong-ban-quan-qing-wu-li-yong-ci-xiang-mu-cong-shi-shang-ye-hang-wei-ji-fei-fa-yong-tu": "1. 이 프로젝트는 학습 목적으로만 제작되었습니다. 저작권을 존중해 주시고, 상업적인 활동이나 불법적인 목적으로 사용하지 마세요.", "2-ben-xiang-mu-jin-gong-xue-xi-jiao-liu-shi-yong-nin-zai-shi-yong-guo-cheng-zhong-ying-zun-zhong-ban-quan-bu-de-yong-yu-shang-ye-huo-fei-fa-yong-tu": "2. 본 프로젝트는 학습과 소통을 위한 목적으로만 사용되어야 하며, 상업적 또는 불법적인 목적으로 사용할 수 없습니다.", "2-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-chan-sheng-ban-quan-shu-ju-dui-yu-zhe-xie-ban-quan-shu-ju-ben-xiang-mu-bu-yong-you-ta-men-de-suo-you-quan-wei-le-bi-mian-qin-quan-shi-yong-zhe-wu-bi-zai-24-xiao-shi-nei-qing-chu-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-suo-chan-sheng-de-ban-quan-shu-ju": "2. 본 프로젝트 이용 과정에서 저작권 데이터가 생성될 수 있습니다. \n본 프로젝트는 이러한 저작권이 있는 데이터의 소유권을 보유하지 않습니다. \n침해를 방지하기 위해 사용자는 본 프로젝트 이용 과정에서 생성된 저작권 데이터를 24시간 이내에 삭제해야 합니다.", "3-miao-hou-zi-dong-qie-huan-xia-yi-shou": "3초 후 자동으로 다음곡으로 전환", "3-you-yu-shi-yong-ben-xiang-mu-chan-sheng-de-bao-kuo-you-yu-ben-xie-yi-huo-you-yu-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-er-yin-qi-de-ren-he-xing-zhi-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-sun-hai-bao-kuo-dan-bu-xian-yu-yin-shang-yu-sun-shi-ting-gong-ji-suan-ji-gu-zhang-huo-gu-zhang-yin-qi-de-sun-hai-pei-chang-huo-ren-he-ji-suo-you-qi-ta-shang-ye-sun-hai-huo-sun-shi-you-shi-yong-zhe-fu-ze": "3. 본 계약 또는 본 프로젝트의 사용 또는 사용 불가능으로 인해 발생하는 모든 종류의 직간접적, 특별, 우발적 또는 결과적 손해(영업권 상실, 작업 중단, 컴퓨터 오작동 또는 오작동으로 인한 손해를 포함하되 이에 국한되지 않음) , 또는 기타 모든 상업적 손해나 손실)은 사용자의 책임입니다.", "3-zai-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-sheng-cheng-ban-quan-nei-rong-ben-xiang-mu-bu-yong-you-zhe-xie-ban-quan-nei-rong-de-suo-you-quan-wei-le-bi-mian-qin-quan-hang-wei-nin-xu-zai-24-xiao-shi-nei-qing-chu-you-ben-xiang-mu-chan-sheng-de-ban-quan-nei-rong": "3. 본 프로젝트 이용과정에서 저작권으로 보호되는 콘텐츠가 생성될 수 있습니다. \n본 프로젝트는 이러한 저작권이 있는 콘텐츠에 대한 소유권을 보유하지 않습니다. \n침해를 방지하려면 이 프로젝트에서 생성된 저작권이 있는 콘텐츠를 24시간 이내에 제거해야 합니다.", "4-ben-xiang-mu-de-kai-fa-zhe-bu-dui-yin-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-suo-dao-zhi-de-ren-he-sun-hai-cheng-dan-ze-ren-bao-kuo-dan-bu-xian-yu-shu-ju-diu-shi-ting-gong-ji-suan-ji-gu-zhang-huo-qi-ta-jing-ji-sun-shi": "4. 이 프로젝트의 개발자는 데이터 손실, 다운타임, 컴퓨터 오류 또는 기타 경제적 손실을 포함하되 이에 국한되지 않고 이 프로젝트를 사용하거나 사용할 수 없음으로 인해 발생하는 모든 손해에 대해 책임을 지지 않습니다.", "4-jin-zhi-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-dui-yu-shi-yong-zhe-zai-ming-zhi-huo-bu-zhi-dang-di-fa-lv-fa-gui-bu-yun-xu-de-qing-kuang-xia-shi-yong-ben-xiang-mu-suo-zao-cheng-de-ren-he-wei-fa-wei-gui-hang-wei-you-shi-yong-zhe-cheng-dan-ben-xiang-mu-bu-cheng-dan-you-ci-zao-cheng-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-ze-ren": "4. 본 프로젝트를 현지 법규에 위반하여 이용하는 것은 금지되어 있습니다. \n현지 법률 및 규정이 본 프로젝트의 사용을 허용하지 않는다는 사실을 사용자가 고의로 또는 알지 못하여 발생한 불법 행위에 대한 책임은 사용자에게 있습니다. 본 프로젝트는 이로 인해 발생하는 직접적, 간접적, 특수적, 우발적 또는 결과적 책임을 지지 않습니다. .", "5-nin-bu-de-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-yin-wei-fan-fa-lv-fa-gui-suo-dao-zhi-de-ren-he-fa-lv-hou-guo-you-yong-hu-cheng-dan": "5. 현지 법규를 위반하여 본 프로젝트를 이용할 수 없습니다. \n법령 위반으로 인해 발생하는 모든 법적 책임은 이용자에게 있습니다.", "5-yin-le-ping-tai-bu-yi-qing-zun-zhong-ban-quan-zhi-chi-zheng-ban": "5. 뮤직 플랫폼은 쉽지 않습니다. 저작권을 존중하고 정품을 지원해주세요.", "6-ben-xiang-mu-jin-yong-yu-dui-ji-shu-ke-hang-xing-de-tan-suo-ji-yan-jiu-bu-jie-shou-ren-he-shang-ye-bao-kuo-dan-bu-xian-yu-guang-gao-deng-he-zuo-ji-juan-zeng": "6. 본 프로젝트는 기술적 타당성 탐색 및 연구 목적으로만 사용되며 어떠한 상업적(광고 등을 포함하되 이에 국한되지 않음) 협력 및 기부를 받지 않습니다.", "6-ben-xiang-mu-jin-yong-yu-ji-shu-tan-suo-he-yan-jiu-bu-jie-shou-ren-he-shang-ye-he-zuo-guang-gao-huo-juan-zeng-ru-guo-guan-fang-yin-le-ping-tai-dui-ci-xiang-mu-cun-you-yi-lv-ke-sui-shi-lian-xi-kai-fa-zhe-yi-chu-xiang-guan-nei-rong": "6. 본 프로젝트는 기술 탐구 및 연구 목적으로만 사용되며 어떠한 상업적 협력, 광고 또는 기부도 받지 않습니다. \n공식 뮤직 플랫폼이 본 프로젝트에 대해 우려하는 경우 언제든지 개발자에게 연락하여 관련 콘텐츠를 삭제할 수 있습니다.", "7-ru-guo-guan-fang-yin-le-ping-tai-jue-de-ben-xiang-mu-bu-tuo-ke-lian-xi-ben-xiang-mu-geng-gai-huo-yi-chu": "7. 공식 음원 플랫폼에서 본 프로젝트가 부적절하다고 판단하는 경우 본 프로젝트에 연락하여 변경 또는 삭제를 요청할 수 있습니다.", "bo-fang": "놀다", "bo-fang-lie-biao": "재생목록", "bu-tong-yi": "맞지 않다", "chang-ting-ban": "무료 듣기 버전:", "dan-qu-xun-huan": "단일 루프", "de-yin-le-ku": "님의 음악 라이브러리", "deng-lu": "로그인", "deng-lu-cheng-gong": "로그인 성공", "deng-lu-ni-de-ku-gou-zhang-hao": "Kugou 계정에 로그인하세요", "deng-lu-shi-bai": "로그인 실패", "deng-lu-shi-xiao-qing-zhong-xin-deng-lu": "로그인에 실패했습니다. 다시 로그인해 주세요.", "di": "아니요.", "er-wei-ma": "QR 코드", "er-wei-ma-deng-lu-cheng-gong": "QR코드 로그인 성공", "er-wei-ma-jian-ce-shi-bai": "QR 코드 감지 실패", "er-wei-ma-sheng-cheng-shi-bai": "QR 코드 생성 실패", "er-wei-ma-yi-guo-qi-qing-zhong-xin-sheng-cheng": "QR코드가 만료되었습니다. 다시 생성해주세요.", "fa-song-yan-zheng-ma": "인증코드 보내기", "gai-nian-ban": "컨셉 버전:", "ge-qu-lie-biao": "노래 목록", "geng-xin": "고쳐 쓰다", "gong": "흔한", "guan-yu": "~에 대한", "huo-qu-er-wei-ma-shi-bai": "QR 코드를 가져오지 못했습니다.", "huo-qu-ge-ci-shi-bai": "가사를 가져오지 못했습니다.", "huo-qu-ge-ci-zhong": "가사 가져오는 중...", "huo-qu-ge-dan-shi-bai": "재생목록을 가져오지 못했습니다.", "huo-qu-yin-le-di-zhi-shi-bai": "음악 주소를 가져오지 못했습니다.", "huo-qu-yin-le-shi-bai": "음악을 가져오지 못했습니다.", "li-ji-deng-lu": "지금 로그인하세요", "lie-biao-xun-huan": "목록 루프", "login-tips": "MoeKoe은 귀하의 계정 정보를 클라우드에 저장하지 않을 것을 약속합니다. \n귀하의 비밀번호는 로컬로 암호화된 후 Kugou 공식에게 전송됩니다. \nMengyin은 Kugou의 공식 웹사이트가 아닙니다. 계정 정보를 입력하기 전에 신중하게 고려하시기 바랍니다. 모든 계정이 계정 비밀번호 로그인을 지원하는 것은 아닙니다.", "mian-ze-sheng-ming": "부인 성명", "ni-huan-mei-you-tian-jia-ge-quo-kuai-qu-tian-jia-ba": "아직 노래를 추가하지 않았습니다. 계속해서 추가하세요!", "qing-shi-yong-ku-gou-sao-miao-er-wei-ma-deng-lu": "로그인하려면 Kugou를 사용하여 QR 코드를 스캔하세요.", "qing-shu-ru-deng-lu-you-xiang": "로그인 계정을 입력하십시오", "qing-shu-ru-mi-ma": "비밀번호를 입력해주세요", "qing-shu-ru-shou-ji-hao": "휴대폰번호를 입력해주세요", "qing-shu-ru-shou-ji-hao-ma": "휴대폰번호를 입력해주세요", "qing-shu-ru-yan-zheng-ma": "인증번호를 입력해주세요", "qing-shu-ru-you-xiang": "계정 번호를 입력하십시오", "qu-xiao": "취소", "que-ding": "확신하는", "sao-ma-deng-lu": "로그인하려면 코드를 스캔하세요", "shang-yi-ye": "이전 페이지", "shao-nv-qi-dao-zhong": "소녀는 기도하고 있다....", "she-zhi": "설정", "shi-yong-yan-zheng-ma-deng-lu": "인증코드를 사용하여 로그인하세요.", "shou-ge": "노래", "shou-ji-hao-deng-lu": "휴대폰번호 로그인", "shou-ji-hao-ge-shi-cuo-wu": "휴대폰 번호 형식 오류", "shou-ye": "첫 페이지", "shun-xu-bo-fang": "순차적으로 재생", "sou-suo-jie-guo": "검색결과", "sou-suo-yin-le-ge-shou-ge-dan": "음악, 가수, 재생목록 검색, 코드 공유...", "sui-ji-bo-fang": "혼합", "tong-yi": "동의하다", "tong-yi-ji-xu-shi-yong-ben-xiang-mu-nin-ji-jie-shou-yi-shang-tiao-kuan-sheng-ming-nei-rong": "이 프로젝트를 계속 사용하는 데 동의하면 위의 이용 약관에 동의하는 것입니다.", "tui-chu": "그만두다", "wo-chuang-jian-de-ge-dan": "내가 만든 재생목록", "wo-guan-zhu-de-ge-shou": "내가 팔로우하는 가수", "wo-shou-cang-de-ge-dan": "내 재생목록 모음", "wo-xi-huan-ting": "나는 듣는 것을 좋아한다", "wu-xiang-ying-shu-ju": "응답 데이터 없음", "xia-yi-ye": "다음 페이지", "yan-zheng-ma-fa-song-shi-bai": "인증코드 전송 실패", "yan-zheng-ma-fa-song-shi-bai-0": "인증코드 전송에 실패했습니다.", "yan-zheng-ma-yi-fa-song": "인증 코드가 전송되었습니다", "ye": "페이지", "yi-sao-ma-deng-dai-que-ren": "스캔된 코드, 확인 대기 중", "yin-le-ku": "음악 도서관", "yong-hu": "사용자", "yong-hu-tiao-kuan": "사용자 약관", "yong-hu-tou-xiang": "사용자 아바타", "you-xiang-deng-lu": "계정 로그인", "you-xiang-ge-shi-cuo-wu": "이메일 형식 오류", "zan-wu-ge-ci": "아직 가사가 없습니다", "deng-lu-shi-bai-0": "로그인에 실패했습니다.", "zheng-zai-sheng-cheng-er-wei-ma": "QR 코드 생성 중...", "zhe-li-shi-mo-du-mei-you": "여기에는 아무것도 없습니다", "wu-sun-yin-zhi-1104kbps": "무손실 음질 - 1104kbps", "gao-pin-zhi-yin-le-xu-yao-deng-lu-hou-cai-neng-bo-fango": "고음질 음악은 로그인이 필요합니다~", "tian-jia-ge-dan": "재생목록에 추가", "qing-xian-deng-lu": "먼저 로그인해주세요", "cheng-gong-tian-jia-dao-ge-dan": "재생목록에 추가되었습니다!", "tian-jia-dao-ge-dan-shi-bai": "재생목록에 추가하지 못했습니다!", "cheng-gong-qu-xiao-shou-cang": "즐겨찾기 취소", "qu-xiao-shou-cang-shi-bai": "취소 실패", "ni-que-ren-yao-tui-chu-deng-lu-ma": "정말로 로그아웃하시겠습니까?", "gai-ge-qu-zan-wu-ban-quan": "이 곡에는 저작권이 없습니다.", "wo-shou-cang-de-zhuan-ji": "내가 컬렉션한 앨범", "bo-fang-shi-bai": "재생 실패", "wo-guan-zhu-de-hao-you": "내가 팔로우하는 친구들", "tian-jia-cheng-gong": "성공적으로 추가되었습니다", "chuang-jian-ge-dan": "재생목록 만들기", "qing-shu-ru-xin-de-ge-dan-ming-cheng": "새 재생목록 이름을 입력하세요.", "chuang-jian-shi-bai": "생성 실패", "que-ren-shan-chu-ge-dan": "재생목록을 삭제하시겠습니까?", "shou-cang-shi-bai": "수집 실패", "shou-cang-cheng-gong": "수집 성공", "wo-guan-zhu-de-yi-ren": "내가 팔로우하는 음악가", "sou-suo-ge-qu": "노래 검색...", "fan-hui-ding-bu": "맨 위로 돌아가기", "dang-qian-bo-fang-ge-qu": "현재 재생 중인 노래", "yi-fu-zhi-fen-xiang-ma-qing-zai-moekoe-ke-hu-duan-zhong-fang-wen": "공유 코드가 복사되었습니다. MoeKoe 클라이언트에서 확인하세요.", "ge-qu-shu-ju-cuo-wu": "재생목록이 존재하지 않습니다", "bao-cun": "구하다", "bao-cun-she-zhi-shi-bai": "설정이 실패했습니다", "cun-zai-wu-xiao-de-kuai-jie-jian-she-zhi-qing-que-bao-mei-ge-kuai-jie-jian-du-bao-han-xiu-shi-jian": "잘못된 단축키 설정이 있습니다. 각 단축키에 수정자 키가 포함되어 있는지 확인하세요.", "de-kuai-jie-jian-chong-tu": "바로 가기 키 충돌", "fei-ke-hu-duan-huan-jing-wu-fa-qi-yong": "비클라이언트 환경, 활성화할 수 없음", "gai-kuai-jie-jian-yu": "똥 키와", "jing-yin": "무음", "kuai-jie-jian-bi-xu-bao-han-zhi-shao-yi-ge-xiu-shi-jian-ctrlaltshiftcommand": "바로 가기 키에는 하나 이상의 수정 자 (ctrl/alt/shift/command)가 포함되어야합니다.", "kuai-jie-jian-she-zhi": "단축키 설정", "qing-an-xia-qi-ta-jian": "[다른 키를 누르십시오]", "qing-an-xia-xiu-shi-jian": "수정자 키를 눌러주세요", "quan-ju-kuai-jie-jian": "글로벌 바로 가기 키", "shang-yi-shou": "이전 노래", "tui-chu-zhu-cheng-xu": "메인 프로그램 종료", "xi-tong": "체계", "xia-yi-shou": "다음", "xian-shi-yin-cang-zhu-chuang-kou": "기본 창을 표시/숨기십시오", "yin-liang-jian-xiao": "감소하다", "yin-liang-zeng-jia": "볼륨 증가", "zan-ting-bo-fang": "일시 정지/놀이", "zi-ding-yi-kuai-jie-jian": "맞춤형 단축키 키", "mei-ri-tui-jian": "매일 추천", "mei-you-zheng-zai-bo-fang-de-ge-qu": "연주하는 노래는 없습니다", "shou-cang-dao": "모으다", "mei-you-ge-dan": "노래 목록이 없습니다", "jin-yong-gpu-jia-su-zhong-qi-sheng-xiao": "GPU 가속도를 비활성화합니다", "jie-mian": "인터페이스", "guan-bi-shi-minimize-to-tray": "창을 닫을 때 트레이로 이동하십시오", "mi-gan-cheng": "미안 오렌지", "zhu-ce": "아직 계정이 없습니까?", "xin-zeng-zhang-hao-qing-xian-zai-guan-fang-ke-hu-duan-zhong-deng-lu-yi-ci": "새 계정을 위해 공식 고객에게 로그인하십시오.", "shua-xin-hou-sheng-xiao": "(새로 고침 후 효과)", "zhong-qi-hou-sheng-xiao": "(재시작 후 효과)", "shi-pei-gao-dpi": "고DPI 지원 활성화", "kui-she-chao-qing-yin-zhi": "Viper Ultra-Definition 음질", "hires-yin-zhi": "고해상도 음질", "tian-jia-wo-xi-huan": "내가 좋아하는 것에 추가하십시오", "qie-huan-bo-fang-mo-shi": "재생 모드를 전환합니다", "pwa-app": "PWA 응용 프로그램", "install": "설치", "yin-pin-jia-zai-shi-bai": "음원 로드 실패", "zheng-zai-jia-zai-quan-bu-ge-qu": "모든 노래를 로드하는 중...", "bo-fang-chu-cuo": "재생 오류", "bo-fang-shi-bai-qu-mu-wei-kong": "재생 실패, 곡 목록이 비어 있습니다", "shan-chu-cheng-gong": "삭제 완료", "tian-jia-dao-bo-fang-lie-biao-cheng-gong": "재생 목록에 추가됨", "hot": "인기", "new": "최신", "yun-pan-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "클라우드 음악은 재생목록에 추가할 수 없습니다", "xian-qu-kan-kan-ni-de-shou-cang-jia-ba": "즐겨찾기를 확인하세요", "yi-da-dao-zui-da-chong-shi-ci-shu": "최대 재시도 횟수에 도달했습니다. 직접 곡을 선택하세요", "hui-fu-chu-chang-she-zhi-cheng-gong": "초기 설정으로 복원되었습니다. 앱을 다시 시작하세요", "liu-lan-qi-bu-zhi-chi-file-system-api": "브라우저가 File System API를 지원하지 않습니다. Chrome 86+ 또는 Edge 86+를 사용하세요", "cha-jian-an-zhuang-cheng-gong": "플러그인 설치 완료", "zhi-chi-http-https-dai-li": "HTTP/HTTPS 프록시만 지원", "ben-di-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "로컬 음악은 재생목록에 추가할 수 없습니다", "mei-you-xuan-ze-zheng-que-de-ge-qu": "곡이 선택되지 않았습니다", "zhuang-tai-lan-ge-ci-jin-zhi-chi-mac": "메뉴바 가사는 macOS만 지원", "qian-dao-shi-bai": "체크인 실패. 너무 자주 체크인하지 마세요", "huo-qu-vip-shi-bai": "VIP 획득 실패. 하루에 한 번만 가능", "qing-zai-web-huan-jing-xia-an-zhuang": "웹 버전에서 설치하세요", "qing-shu-ru-you-xiao-de-url": "유효한 URL을 입력하세요", "zhe-shi-yi-ge-alert": "알림", "fei-mac-bu-zhi-chi-touchbar": "TouchBar는 Mac에서만 지원", "zi-ti-she-zhi": "글꼴 설정", "mo-ren-zi-ti": "기본 글꼴", "hui-fu-chu-chang-she-zhi": "초기 설정으로 복원", "zi-ti-url-di-zhi": "글꼴 URL", "qing-shu-ru-zi-ti-url-di-zhi": "글꼴 URL 입력", "zi-ti-ming-cheng": "글꼴 이름", "qing-shu-ru-zi-ti-ming-cheng": "글꼴 이름 입력", "cha-jian": "플러그인", "shua-xin-cha-jian": "플러그인 새로고침", "da-kai-cha-jian-mu-lu": "플러그인 폴더 열기", "an-zhuang-cha-jian": "플러그인 설치", "zan-wu-cha-jian": "플러그인 없음", "jiang-cha-jian-wen-jian-jia-fang-ru-cha-jian-mu-lu": "플러그인 폴더를 디렉토리에 넣고 새로고침 버튼을 클릭하세요", "kai-ji-zi-qi-dong": "자동 시작", "wang-luo-mo-shi": "네트워크 모드", "zhu-wang": "메인넷", "qi-dong-shi-zui-xiao-hua": "시작 시 최소화", "zu-zhi-xi-tong-xiu-mian": "절전 모드 방지", "api-mo-shi": "API 모드", "wang-luo-dai-li": "네트워크 프록시", "zhuang-tai-lan-ge-ci": "메뉴바 가사", "ge-ci-fan-yi": "가사 번역", "dui-qi-fang-shi": "정렬", "ju-zhong": "가운데", "ju-zuo": "왼쪽", "ju-you": "오른쪽", "ping-heng-yin-pin-xiang-du": "음량 정규화", "shu-ju-yuan": "데이터 소스", "suo-fang-yin-zi": "배율", "tiao-zheng-hou-xu-zhong-qi": "조정 후 재시작 필요", "api-di-zhi": "API 주소", "websocket-di-zhi": "WebSocket 주소", "mo-ren-api-ti-shi": "기본 API 주소입니다. 현재 버전에서는 사용자 정의 변경을 지원하지 않습니다", "dai-li-placeholder": "HTTP/HTTPS 프록시 주소 입력, 예: http://127.0.0.1:7890", "zheng-zai-ce-shi": "테스트 중...", "ce-shi-lian-jie": "연결 테스트", "bao-cun-she-zhi-an-niu": "저장", "qing-shu-ru-dai-li-di-zhi": "프록시 서버 주소 입력", "ce-wang": "테스트넷", "kai-fa-wang": "개발넷", "qi-yong": "활성화", "jin-yong": "비활성화", "dai-li-di-zhi": "프록시 주소", "gai-nian-ban-xuan-xiang": "컨셉 버전", "zheng-shi-ban": "정식 버전", "dai-li-lian-jie-cheng-gong": "프록시 연결 성공, IP: ", "dai-li-lian-jie-shi-bai": "프록시 연결 실패: ", "lian-jie-chao-shi": "연결 시간 초과", "lian-jie-cuo-wu": "연결 오류: ", "jin-zhi-chi-mac": " (macOS만 지원)", "xian-shi-yin-cang-zhuo-mian-ge-ci": "데스크톱 가사 표시/숨기기", "ni-que-ren-hui-fu-chu-chang": "초기 설정으로 복원하시겠습니까? 이 작업은 되돌릴 수 없습니다!", "bang-zhu": "도움말", "dian-ji-she-zhi-kuai-jie-jian": "클릭하여 단축키 설정", "wang-luo-jie-dian": "네트워크 노드", "zi-ti-wen-jian-di-zhi": "글꼴 파일 URL", "jia-zai-zhong": "로딩 중...", "ban-ben": "버전", "yi-qi-yong": "활성화됨", "da-kai-tan-chuang": "설정", "xie-zai": "제거", "zheng-zai-jia-zai-cha-jian": "플러그인 로딩 중...", "web-cha-jian-ti-shi": "웹 버전에서는 chrome://extensions/에서 확장 프로그램을 관리하세요", "da-kai-tan-chuang-shi-bai": "팝업을 열 수 없습니다", "que-ren-xie-zai-cha-jian": "플러그인 name 을(를) 제거하시겠습니까?", "xie-zai-cha-jian-shi-bai": "플러그인 제거 실패", "xuan-ze-wen-jian-shi-bai": "파일 선택 실패", "an-zhuang-cha-jian-shi-bai": "플러그인 설치 실패", "an-zhuang-cha-jian-chu-cuo": "플러그인 설치 중 오류", "cha-jian-bao": "플러그인 패키지", "zhuo-mian-ge-ci": "데스크톱 가사", "bo-fang-su-du": "재생 속도", "wo-xi-huan": "좋아요", "shou-cang-zhi": "저장 위치", "fen-xiang-ge-qu": "공유", "qie-huan-dao-yin-yi": "로마자로 전환", "qie-huan-dao-fan-yi": "번역으로 전환", "wei-zhi-cuo-wu": "알 수 없는 오류" } ================================================ FILE: src/language/ru.json ================================================ { "tui-jian": "Рекомендации", "tui-jian-ge-qu": "Рекомендуемые треки", "tui-jian-ge-dan": "Рекомендуемые плейлисты", "fa-xian": "Обзор", "yu-yan": "Язык", "zhu-se-tiao": "Цветовая тема", "wai-guan": "Оформление", "native-title-bar": "Системный заголовок окна", "sheng-yin": "Звук", "yin-zhi-xuan-ze": "Качество звука", "qi-dong-wen-hou-yu": "Приветствие при запуске", "ge-ci": "Тексты песен", "xian-shi-ge-ci-bei-jing": "Показывать обложку на фоне текста", "xian-shi-zhuo-mian-ge-ci": "Показывать текст на рабочем столе", "ge-ci-zi-ti-da-xiao": "Размер шрифта текста", "guan-bi": "Нет", "guan-bi-an-niu": "Закрыть", "shao-nv-fen": "Розовый", "qian-se": "Светлая", "pu-tong-yin-zhi": "Обычное качество — 128 Кбит/с", "da-kai": "Да", "zhong": "Средний", "kai-qi": "Да", "xuan-ze-yu-yan": "Выбор языка", "xuan-ze-zhu-se-tiao": "Выбор цветовой темы", "nan-nan-lan": "Небесно-голубой", "tou-ding-lv": "Мятный", "xuan-ze-wai-guan": "Выбор оформления", "zi-dong": "Авто", "shen-se": "Тёмная", "gao-yin-zhi-320kbps": "Высокое качество — 320 Кбит/с", "xiao": "Маленький", "da": "Большой", "sou-suo-jie-guo": "Результаты поиска", "shang-yi-ye": "Назад", "xia-yi-ye": "Вперёд", "di": "Стр.", "ye": "", "gong": "из", "bo-fang": "Воспроизвести", "ge-qu-lie-biao": "Список треков", "deng-lu-ni-de-ku-gou-zhang-hao": "Войдите в аккаунт Kugou", "qing-shu-ru-shou-ji-hao": "Введите номер телефона", "qing-shu-ru-yan-zheng-ma": "Введите код подтверждения", "fa-song-yan-zheng-ma": "Отправить код", "li-ji-deng-lu": "Войти", "qing-shu-ru-deng-lu-you-xiang": "Введите логин", "qing-shu-ru-mi-ma": "Введите пароль", "you-xiang-deng-lu": "Вход по логину", "er-wei-ma": "QR-код", "login-tips": "MoeKoe не сохраняет данные вашего аккаунта на сервере. Пароль шифруется локально перед отправкой на серверы Kugou. MoeKoe не является официальным сайтом Kugou — вводите данные на свой страх и риск. Не все аккаунты поддерживают вход по паролю.", "shi-yong-yan-zheng-ma-deng-lu": "Войти по коду из SMS", "shou-ji-hao-deng-lu": "Вход по телефону", "sao-ma-deng-lu": "Вход по QR-коду", "qing-shu-ru-shou-ji-hao-ma": "Введите номер телефона", "shou-ji-hao-ge-shi-cuo-wu": "Неверный формат номера", "qing-shu-ru-you-xiang": "Введите логин", "you-xiang-ge-shi-cuo-wu": "Неверный формат email", "qing-shi-yong-ku-gou-sao-miao-er-wei-ma-deng-lu": "Отсканируйте QR-код в приложении Kugou", "deng-lu-cheng-gong": "Вход выполнен", "deng-lu-shi-bai": "Ошибка входа", "yan-zheng-ma-fa-song-shi-bai": "Не удалось отправить код", "yan-zheng-ma-yi-fa-song": "Код отправлен", "wu-xiang-ying-shu-ju": "Нет ответа от сервера", "huo-qu-er-wei-ma-shi-bai": "Не удалось получить QR-код", "er-wei-ma-sheng-cheng-shi-bai": "Не удалось сгенерировать QR-код", "er-wei-ma-deng-lu-cheng-gong": "Вход по QR-коду выполнен", "er-wei-ma-yi-guo-qi-qing-zhong-xin-sheng-cheng": "QR-код устарел, сгенерируйте новый", "er-wei-ma-jian-ce-shi-bai": "Ошибка проверки QR-кода", "deng-lu-shi-bai-0": "Ошибка входа: ", "yan-zheng-ma-fa-song-shi-bai-0": "Не удалось отправить код: ", "yong-hu": "Пользователь", "yi-sao-ma-deng-dai-que-ren": "QR-код отсканирован, подтвердите вход", "yong-hu-tou-xiang": "Аватар", "de-yin-le-ku": " — Музыкальная библиотека", "wo-xi-huan-ting": "Мне нравится", "wo-chuang-jian-de-ge-dan": "Мои плейлисты", "wo-shou-cang-de-ge-dan": "Сохранённые плейлисты", "wo-guan-zhu-de-ge-shou": "Мои исполнители", "deng-lu-shi-xiao-qing-zhong-xin-deng-lu": "Сессия истекла, войдите снова", "gai-nian-ban": "Концептуальная версия:", "chang-ting-ban": "Стандартная версия:", "shou-ge": "треков", "shou-ye": "Главная", "yin-le-ku": "Библиотека", "sou-suo-yin-le-ge-shou-ge-dan": "Поиск музыки, исполнителей, плейлистов...", "she-zhi": "Настройки", "tui-chu": "Выход", "deng-lu": "Вход", "geng-xin": "Обновление", "guan-yu": "О программе", "mian-ze-sheng-ming": "Отказ от ответственности", "0-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "0. Это неофициальный клиент Kugou. Для полного функционала используйте официальное приложение.", "1-ben-xiang-mu-jin-gong-xue-xi-shi-yong-qing-zun-zhong-ban-quan-qing-wu-li-yong-ci-xiang-mu-cong-shi-shang-ye-hang-wei-ji-fei-fa-yong-tu": "1. Проект предназначен только для обучения. Уважайте авторские права и не используйте его в коммерческих или незаконных целях.", "2-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-chan-sheng-ban-quan-shu-ju-dui-yu-zhe-xie-ban-quan-shu-ju-ben-xiang-mu-bu-yong-you-ta-men-de-suo-you-quan-wei-le-bi-mian-qin-quan-shi-yong-zhe-wu-bi-zai-24-xiao-shi-nei-qing-chu-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-suo-chan-sheng-de-ban-quan-shu-ju": "2. При использовании могут создаваться данные, защищённые авторским правом. Проект не владеет этими данными. Во избежание нарушений удалите их в течение 24 часов.", "3-you-yu-shi-yong-ben-xiang-mu-chan-sheng-de-bao-kuo-you-yu-ben-xie-yi-huo-you-yu-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-er-yin-qi-de-ren-he-xing-zhi-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-sun-hai-bao-kuo-dan-bu-xian-yu-yin-shang-yu-sun-shi-ting-gong-ji-suan-ji-gu-zhang-huo-gu-zhang-yin-qi-de-sun-hai-pei-chang-huo-ren-he-ji-suo-you-qi-ta-shang-ye-sun-hai-huo-sun-shi-you-shi-yong-zhe-fu-ze": "3. Пользователь несёт ответственность за любой прямой или косвенный ущерб, связанный с использованием или невозможностью использования проекта.", "4-jin-zhi-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-dui-yu-shi-yong-zhe-zai-ming-zhi-huo-bu-zhi-dang-di-fa-lv-fa-gui-bu-yun-xu-de-qing-kuang-xia-shi-yong-ben-xiang-mu-suo-zao-cheng-de-ren-he-wei-fa-wei-gui-hang-wei-you-shi-yong-zhe-cheng-dan-ben-xiang-mu-bu-cheng-dan-you-ci-zao-cheng-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-ze-ren": "4. Запрещено использовать проект в нарушение местного законодательства. Ответственность за нарушения несёт пользователь.", "5-yin-le-ping-tai-bu-yi-qing-zun-zhong-ban-quan-zhi-chi-zheng-ban": "5. Поддержите правообладателей — слушайте музыку легально.", "6-ben-xiang-mu-jin-yong-yu-dui-ji-shu-ke-hang-xing-de-tan-suo-ji-yan-jiu-bu-jie-shou-ren-he-shang-ye-bao-kuo-dan-bu-xian-yu-guang-gao-deng-he-zuo-ji-juan-zeng": "6. Проект предназначен только для технических исследований. Коммерческое сотрудничество и пожертвования не принимаются.", "7-ru-guo-guan-fang-yin-le-ping-tai-jue-de-ben-xiang-mu-bu-tuo-ke-lian-xi-ben-xiang-mu-geng-gai-huo-yi-chu": "7. Если правообладатели имеют претензии, они могут связаться с разработчиками для изменения или удаления проекта.", "yong-hu-tiao-kuan": "Пользовательское соглашение", "1-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "1. Это неофициальный клиент Kugou. Для полного функционала используйте официальное приложение.", "2-ben-xiang-mu-jin-gong-xue-xi-jiao-liu-shi-yong-nin-zai-shi-yong-guo-cheng-zhong-ying-zun-zhong-ban-quan-bu-de-yong-yu-shang-ye-huo-fei-fa-yong-tu": "2. Проект предназначен для обучения. Уважайте авторские права и не используйте его в коммерческих или незаконных целях.", "3-zai-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-sheng-cheng-ban-quan-nei-rong-ben-xiang-mu-bu-yong-you-zhe-xie-ban-quan-nei-rong-de-suo-you-quan-wei-le-bi-mian-qin-quan-hang-wei-nin-xu-zai-24-xiao-shi-nei-qing-chu-you-ben-xiang-mu-chan-sheng-de-ban-quan-nei-rong": "3. При использовании могут создаваться данные, защищённые авторским правом. Удалите их в течение 24 часов.", "4-ben-xiang-mu-de-kai-fa-zhe-bu-dui-yin-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-suo-dao-zhi-de-ren-he-sun-hai-cheng-dan-ze-ren-bao-kuo-dan-bu-xian-yu-shu-ju-diu-shi-ting-gong-ji-suan-ji-gu-zhang-huo-qi-ta-jing-ji-sun-shi": "4. Разработчики не несут ответственности за любой ущерб от использования проекта.", "5-nin-bu-de-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-yin-wei-fan-fa-lv-fa-gui-suo-dao-zhi-de-ren-he-fa-lv-hou-guo-you-yong-hu-cheng-dan": "5. Запрещено использовать проект в нарушение законодательства. Ответственность несёт пользователь.", "6-ben-xiang-mu-jin-yong-yu-ji-shu-tan-suo-he-yan-jiu-bu-jie-shou-ren-he-shang-ye-he-zuo-guang-gao-huo-juan-zeng-ru-guo-guan-fang-yin-le-ping-tai-dui-ci-xiang-mu-cun-you-yi-lv-ke-sui-shi-lian-xi-kai-fa-zhe-yi-chu-xiang-guan-nei-rong": "6. Проект предназначен для технических исследований. Коммерческое сотрудничество не принимается. Правообладатели могут связаться с разработчиками.", "tong-yi-ji-xu-shi-yong-ben-xiang-mu-nin-ji-jie-shou-yi-shang-tiao-kuan-sheng-ming-nei-rong": "Продолжая использовать проект, вы принимаете условия соглашения.", "tong-yi": "Принять", "bu-tong-yi": "Отклонить", "que-ding": "ОК", "qu-xiao": "Отмена", "shao-nv-qi-dao-zhong": "Загрузка...", "zan-wu-ge-ci": "Текст недоступен", "ni-huan-mei-you-tian-jia-ge-quo-kuai-qu-tian-jia-ba": "Здесь пока пусто. Добавьте треки!", "huo-qu-ge-dan-shi-bai": "Не удалось загрузить плейлист", "huo-qu-yin-le-shi-bai": "Не удалось загрузить музыку", "3-miao-hou-zi-dong-qie-huan-xia-yi-shou": "Следующий трек через 3 сек.", "huo-qu-yin-le-di-zhi-shi-bai": "Не удалось получить ссылку на трек", "huo-qu-ge-ci-zhong": "Загрузка текста...", "huo-qu-ge-ci-shi-bai": "Не удалось загрузить текст", "dan-qu-xun-huan": "Повтор трека", "lie-biao-xun-huan": "Повтор плейлиста", "shun-xu-bo-fang": "По порядку", "sui-ji-bo-fang": "Случайный порядок", "bo-fang-lie-biao": "Очередь воспроизведения", "zheng-zai-sheng-cheng-er-wei-ma": "Генерация QR-кода...", "zhe-li-shi-mo-du-mei-you": "Здесь пока ничего нет", "wu-sun-yin-zhi-1104kbps": "Lossless — 1104 Кбит/с", "gao-pin-zhi-yin-le-xu-yao-deng-lu-hou-cai-neng-bo-fango": "Для высокого качества войдите в аккаунт", "tian-jia-ge-dan": "Добавить в плейлист", "qing-xian-deng-lu": "Сначала войдите в аккаунт", "cheng-gong-tian-jia-dao-ge-dan": "Добавлено в плейлист", "tian-jia-dao-ge-dan-shi-bai": "Не удалось добавить в плейлист", "cheng-gong-qu-xiao-shou-cang": "Удалено из избранного", "qu-xiao-shou-cang-shi-bai": "Не удалось удалить", "ni-que-ren-yao-tui-chu-deng-lu-ma": "Выйти из аккаунта?", "gai-ge-qu-zan-wu-ban-quan": "Трек недоступен (нет лицензии)", "wo-shou-cang-de-zhuan-ji": "Сохранённые альбомы", "bo-fang-shi-bai": "Ошибка воспроизведения", "wo-guan-zhu-de-hao-you": "Мои друзья", "tian-jia-cheng-gong": "Добавлено", "chuang-jian-ge-dan": "Создать плейлист", "qing-shu-ru-xin-de-ge-dan-ming-cheng": "Введите название плейлиста", "chuang-jian-shi-bai": "Не удалось создать", "que-ren-shan-chu-ge-dan": "Удалить плейлист?", "shou-cang-cheng-gong": "Добавлено в избранное", "shou-cang-shi-bai": "Не удалось добавить в избранное", "wo-guan-zhu-de-yi-ren": "Мои музыканты", "sou-suo-ge-qu": "Поиск треков...", "dang-qian-bo-fang-ge-qu": "Сейчас играет", "fan-hui-ding-bu": "Наверх", "yi-fu-zhi-fen-xiang-ma-qing-zai-moekoe-ke-hu-duan-zhong-fang-wen": "Код скопирован. Откройте его в MoeKoe", "kou-ling-yi-fu-zhi,kuai-ba-ge-qu-fen-xiang-gei-peng-you-ba": "Скопировано! Поделитесь с друзьями", "ge-qu-shu-ju-cuo-wu": "Плейлист не найден", "xi-tong": "Система", "quan-ju-kuai-jie-jian": "Глобальные горячие клавиши", "zi-ding-yi-kuai-jie-jian": "Настроить горячие клавиши", "kuai-jie-jian-she-zhi": "Горячие клавиши", "xian-shi-yin-cang-zhu-chuang-kou": "Показать/скрыть окно", "tui-chu-zhu-cheng-xu": "Выход из программы", "shang-yi-shou": "Предыдущий трек", "xia-yi-shou": "Следующий трек", "zan-ting-bo-fang": "Пауза/воспроизведение", "yin-liang-zeng-jia": "Громче", "yin-liang-jian-xiao": "Тише", "jing-yin": "Без звука", "bao-cun": "Сохранить", "fei-ke-hu-duan-huan-jing-wu-fa-qi-yong": "Недоступно в веб-версии", "qing-an-xia-xiu-shi-jian": "Нажмите модификатор", "qing-an-xia-qi-ta-jian": "+ [нажмите клавишу]", "kuai-jie-jian-bi-xu-bao-han-zhi-shao-yi-ge-xiu-shi-jian-ctrlaltshiftcommand": "Комбинация должна содержать Ctrl/Alt/Shift/Command", "gai-kuai-jie-jian-yu": "Эта комбинация конфликтует с ", "de-kuai-jie-jian-chong-tu": "", "cun-zai-wu-xiao-de-kuai-jie-jian-she-zhi-qing-que-bao-mei-ge-kuai-jie-jian-du-bao-han-xiu-shi-jian": "Некорректная комбинация. Добавьте модификатор", "bao-cun-she-zhi-shi-bai": "Не удалось сохранить настройки", "mei-ri-tui-jian": "Рекомендации дня", "mei-you-zheng-zai-bo-fang-de-ge-qu": "Ничего не воспроизводится", "shou-cang-dao": "Сохранить в", "mei-you-ge-dan": "Нет плейлистов", "jin-yong-gpu-jia-su-zhong-qi-sheng-xiao": "Отключить GPU-ускорение", "jie-mian": "Интерфейс", "guan-bi-shi-minimize-to-tray": "Сворачивать в трей при закрытии", "mi-gan-cheng": "Оранжевый", "zhu-ce": "Нет аккаунта?", "xin-zeng-zhang-hao-qing-xian-zai-guan-fang-ke-hu-duan-zhong-deng-lu-yi-ci": "Новый аккаунт? Сначала войдите в официальном приложении", "shua-xin-hou-sheng-xiao": "(после перезагрузки страницы)", "zhong-qi-hou-sheng-xiao": "(после перезапуска)", "shi-pei-gao-dpi": "Поддержка высокого DPI", "hires-yin-zhi": "Hi-Res", "kui-she-chao-qing-yin-zhi": "Viper Ultra HD", "tian-jia-wo-xi-huan": "Добавить в «Мне нравится»", "qie-huan-bo-fang-mo-shi": "Сменить режим воспроизведения", "ke-yong": "Доступно", "wo-de-yun-pan": "Моё облако", "yun-pan-ge-qu-shu": "Треков в облаке", "yun-pan-miao-shu": "Здесь хранится загруженная вами музыка", "yun-pan-ge-qu": "Облачные треки", "cong-yun-pan-shan-chu": "Удалить из облака", "que-ren-shan-chu-yun-pan-ge-qu": "Удалить выбранные треки из облака?", "shang-chuan-yin-le": "Загрузить", "pi-liang-cao-zuo": "Выбрать несколько", "pwa-app": "PWA-приложение", "install": "Установить", "yin-pin-jia-zai-shi-bai": "Не удалось загрузить аудио", "guan-zhu": "Подписки", "fen-si": "Подписчики", "hao-you": "Друзья", "fang-wen": "Просмотры", "qian-dao": "Отметиться", "xiao-shi": "ч.", "fen-zhong": "мин.", "le-ling": "Стаж", "nian": "г.", "ting-ge-shi-chang": "Время прослушивания", "zheng-zai-jia-zai-quan-bu-ge-qu": "Загрузка всех треков...", "bo-fang-chu-cuo": "Ошибка воспроизведения", "bo-fang-shi-bai-qu-mu-wei-kong": "Список воспроизведения пуст", "shan-chu-cheng-gong": "Удалено", "tian-jia-dao-bo-fang-lie-biao-cheng-gong": "Добавлено в очередь", "hot": "Популярное", "new": "Новинки", "yun-pan-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "Облачные треки нельзя добавить в плейлист", "xian-qu-kan-kan-ni-de-shou-cang-jia-ba": "Сначала загляните в избранное", "yi-da-dao-zui-da-chong-shi-ci-shu": "Превышено число попыток, выберите трек вручную", "hui-fu-chu-chang-she-zhi-cheng-gong": "Настройки сброшены. Перезапустите приложение", "liu-lan-qi-bu-zhi-chi-file-system-api": "Браузер не поддерживает File System API. Используйте Chrome 86+ или Edge 86+", "cha-jian-an-zhuang-cheng-gong": "Плагин установлен", "zhi-chi-http-https-dai-li": "Поддерживаются только HTTP/HTTPS прокси", "ben-di-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "Локальные треки нельзя добавить в плейлист", "mei-you-xuan-ze-zheng-que-de-ge-qu": "Трек не выбран", "zhuang-tai-lan-ge-ci-jin-zhi-chi-mac": "Текст в строке меню только для macOS", "qian-dao-shi-bai": "Ошибка отметки. Не отмечайтесь слишком часто", "huo-qu-vip-shi-bai": "Не удалось получить VIP. Доступно раз в день", "qing-zai-web-huan-jing-xia-an-zhuang": "Установите в веб-версии", "qing-shu-ru-you-xiao-de-url": "Введите корректный URL", "zhe-shi-yi-ge-alert": "Уведомление", "fei-mac-bu-zhi-chi-touchbar": "TouchBar доступен только на Mac", "zi-ti-she-zhi": "Настройки шрифта", "mo-ren-zi-ti": "Шрифт по умолчанию", "hui-fu-chu-chang-she-zhi": "Сбросить настройки", "zi-ti-url-di-zhi": "URL шрифта", "qing-shu-ru-zi-ti-url-di-zhi": "Введите URL шрифта", "zi-ti-ming-cheng": "Название шрифта", "qing-shu-ru-zi-ti-ming-cheng": "Введите название шрифта", "cha-jian": "Плагины", "shua-xin-cha-jian": "Обновить", "da-kai-cha-jian-mu-lu": "Открыть папку", "an-zhuang-cha-jian": "Установить", "zan-wu-cha-jian": "Нет плагинов", "jiang-cha-jian-wen-jian-jia-fang-ru-cha-jian-mu-lu": "Поместите папку плагина в каталог и нажмите «Обновить»", "kai-ji-zi-qi-dong": "Автозапуск", "wang-luo-mo-shi": "Сетевой режим", "zhu-wang": "Основная сеть", "qi-dong-shi-zui-xiao-hua": "Сворачивать при запуске", "zu-zhi-xi-tong-xiu-mian": "Предотвращать спящий режим", "api-mo-shi": "Режим API", "wang-luo-dai-li": "Сетевой прокси", "zhuang-tai-lan-ge-ci": "Текст в строке меню", "ge-ci-fan-yi": "Перевод текста", "dui-qi-fang-shi": "Выравнивание", "ju-zhong": "По центру", "ju-zuo": "По левому краю", "ju-you": "По правому краю", "ping-heng-yin-pin-xiang-du": "Нормализация громкости", "shu-ju-yuan": "Источник данных", "suo-fang-yin-zi": "Масштаб", "tiao-zheng-hou-xu-zhong-qi": "Изменение вступит в силу после перезапуска", "api-di-zhi": "Адрес API", "websocket-di-zhi": "Адрес WebSocket", "mo-ren-api-ti-shi": "Это адреса API по умолчанию. Изменение не поддерживается в текущей версии", "dai-li-placeholder": "Введите адрес HTTP/HTTPS прокси, например: http://127.0.0.1:7890", "zheng-zai-ce-shi": "Проверка...", "ce-shi-lian-jie": "Проверить", "bao-cun-she-zhi-an-niu": "Сохранить", "qing-shu-ru-dai-li-di-zhi": "Введите адрес прокси-сервера", "ce-wang": "Тестовая сеть", "kai-fa-wang": "Сеть разработки", "qi-yong": "Включено", "jin-yong": "Выключено", "dai-li-di-zhi": "Адрес прокси", "gai-nian-ban-xuan-xiang": "Концептуальная", "zheng-shi-ban": "Официальная", "dai-li-lian-jie-cheng-gong": "Прокси подключён, IP: ", "dai-li-lian-jie-shi-bai": "Ошибка подключения к прокси: ", "lian-jie-chao-shi": "Таймаут подключения", "lian-jie-cuo-wu": "Ошибка подключения: ", "jin-zhi-chi-mac": " (только macOS)", "xian-shi-yin-cang-zhuo-mian-ge-ci": "Показать/скрыть текст на рабочем столе", "ni-que-ren-hui-fu-chu-chang": "Сбросить все настройки? Это действие необратимо!", "bang-zhu": "Справка", "dian-ji-she-zhi-kuai-jie-jian": "Нажмите для настройки", "wang-luo-jie-dian": "Сетевой узел", "zi-ti-wen-jian-di-zhi": "URL файла шрифта", "jia-zai-zhong": "Загрузка...", "ban-ben": "Версия", "yi-qi-yong": "Включён", "da-kai-tan-chuang": "Настройки", "xie-zai": "Удалить", "zheng-zai-jia-zai-cha-jian": "Загрузка плагинов...", "web-cha-jian-ti-shi": "В веб-версии управляйте расширениями через chrome://extensions/", "da-kai-tan-chuang-shi-bai": "Не удалось открыть окно плагина", "que-ren-xie-zai-cha-jian": "Удалить плагин name?", "xie-zai-cha-jian-shi-bai": "Не удалось удалить плагин", "xuan-ze-wen-jian-shi-bai": "Не удалось выбрать файл", "an-zhuang-cha-jian-shi-bai": "Не удалось установить плагин", "an-zhuang-cha-jian-chu-cuo": "Ошибка при установке плагина", "cha-jian-bao": "Архив плагина", "zhuo-mian-ge-ci": "Текст на рабочем столе", "bo-fang-su-du": "Скорость воспроизведения", "wo-xi-huan": "Мне нравится", "shou-cang-zhi": "Сохранить в", "fen-xiang-ge-qu": "Поделиться", "qie-huan-dao-yin-yi": "Показать транслитерацию", "qie-huan-dao-fan-yi": "Показать перевод", "wei-zhi-cuo-wu": "Неизвестная ошибка" } ================================================ FILE: src/language/zh-CN.json ================================================ { "tui-jian": "推荐", "tui-jian-ge-qu": "推荐歌曲", "tui-jian-ge-dan": "推荐歌单", "fa-xian": "发现", "yu-yan": "语言", "zhu-se-tiao": "主色调", "wai-guan": "外观", "native-title-bar":"原生窗口装饰器", "sheng-yin": "声音", "yin-zhi-xuan-ze": "音质选择", "qi-dong-wen-hou-yu": "启动问候语", "ge-ci": "歌词", "xian-shi-ge-ci-bei-jing": "显示全屏歌词背景封面", "xian-shi-zhuo-mian-ge-ci": "显示桌面歌词", "ge-ci-zi-ti-da-xiao": "全屏歌词字体大小", "guan-bi": "关闭", "guan-bi-an-niu": "关闭", "shao-nv-fen": "少女粉", "qian-se": "浅色", "pu-tong-yin-zhi": "普通音质 - 128Kbps", "da-kai": "打开", "zhong": "中", "kai-qi": "开启", "xuan-ze-yu-yan": "选择语言", "xuan-ze-zhu-se-tiao": "选择主色调", "nan-nan-lan": "天空蓝", "tou-ding-lv": "薄荷绿", "xuan-ze-wai-guan": "选择外观", "zi-dong": "自动", "shen-se": "深色", "gao-yin-zhi-320kbps": "高品音质 - 320Kbps", "xiao": "小", "da": "大", "sou-suo-jie-guo": "搜索结果", "shang-yi-ye": "上一页", "xia-yi-ye": "下一页", "di": "第", "ye": "页", "gong": "共", "bo-fang": "播放", "ge-qu-lie-biao": "歌曲列表", "deng-lu-ni-de-ku-gou-zhang-hao": "登录你的酷狗账号", "qing-shu-ru-shou-ji-hao": "请输入手机号", "qing-shu-ru-yan-zheng-ma": "请输入验证码", "fa-song-yan-zheng-ma": "发送验证码", "li-ji-deng-lu": "立即登录", "qing-shu-ru-deng-lu-you-xiang": "请输入登录账号", "qing-shu-ru-mi-ma": "请输入密码", "you-xiang-deng-lu": "账号登录", "er-wei-ma": "二维码", "login-tips": "萌音 承诺不会保存你的任何账号信息到云端。你的密码会在本地进行加密后再传输到酷狗官方。萌音并非酷狗官方网站,输入账号信息前请慎重考虑,非所有账号都支持账号密码登录.", "shi-yong-yan-zheng-ma-deng-lu": "使用验证码登录.", "shou-ji-hao-deng-lu": "手机号登录", "sao-ma-deng-lu": "扫码登录", "qing-shu-ru-shou-ji-hao-ma": "请输入手机号码", "shou-ji-hao-ge-shi-cuo-wu": "手机号格式错误", "qing-shu-ru-you-xiang": "请输入账号", "you-xiang-ge-shi-cuo-wu": "邮箱格式错误", "qing-shi-yong-ku-gou-sao-miao-er-wei-ma-deng-lu": "请使用酷狗扫描二维码登录", "deng-lu-cheng-gong": "登录成功", "deng-lu-shi-bai": "登录失败", "yan-zheng-ma-fa-song-shi-bai": "验证码发送失败", "yan-zheng-ma-yi-fa-song": "验证码已发送", "wu-xiang-ying-shu-ju": "无响应数据", "huo-qu-er-wei-ma-shi-bai": "获取二维码失败", "er-wei-ma-sheng-cheng-shi-bai": "二维码生成失败", "er-wei-ma-deng-lu-cheng-gong": "二维码登录成功", "er-wei-ma-yi-guo-qi-qing-zhong-xin-sheng-cheng": "二维码已过期,请重新生成", "er-wei-ma-jian-ce-shi-bai": "二维码检测失败", "deng-lu-shi-bai-0": "登录失败,", "yan-zheng-ma-fa-song-shi-bai-0": "验证码发送失败,", "yong-hu": "用户", "yi-sao-ma-deng-dai-que-ren": "已扫码,等待确认", "yong-hu-tou-xiang": "用户头像", "de-yin-le-ku": "的音乐库", "wo-xi-huan-ting": "我喜欢听", "wo-chuang-jian-de-ge-dan": "我创建的歌单", "wo-shou-cang-de-ge-dan": "我收藏的歌单", "wo-guan-zhu-de-ge-shou": "我关注的歌手", "deng-lu-shi-xiao-qing-zhong-xin-deng-lu": "登录失效,请重新登录", "gai-nian-ban": "概念版:", "chang-ting-ban": "畅听版:", "shou-ge": "首歌", "shou-ye": "首页", "yin-le-ku": "音乐库", "sou-suo-yin-le-ge-shou-ge-dan": "搜索音乐、歌手、歌单、分享码...", "she-zhi": "设置", "tui-chu": "退出", "deng-lu": "登录", "geng-xin": "更新", "guan-yu": "关于", "mian-ze-sheng-ming": "免责声明", "0-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "0. 本程序是酷狗第三方客户端,并非酷狗官方,需要更完善的功能请下载官方客户端体验.", "1-ben-xiang-mu-jin-gong-xue-xi-shi-yong-qing-zun-zhong-ban-quan-qing-wu-li-yong-ci-xiang-mu-cong-shi-shang-ye-hang-wei-ji-fei-fa-yong-tu": "1. 本项目仅供学习使用,请尊重版权,请勿利用此项目从事商业行为及非法用途!", "2-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-chan-sheng-ban-quan-shu-ju-dui-yu-zhe-xie-ban-quan-shu-ju-ben-xiang-mu-bu-yong-you-ta-men-de-suo-you-quan-wei-le-bi-mian-qin-quan-shi-yong-zhe-wu-bi-zai-24-xiao-shi-nei-qing-chu-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-suo-chan-sheng-de-ban-quan-shu-ju": "2. 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在 24 小时内清除使用本项目的过程中所产生的版权数据。", "3-you-yu-shi-yong-ben-xiang-mu-chan-sheng-de-bao-kuo-you-yu-ben-xie-yi-huo-you-yu-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-er-yin-qi-de-ren-he-xing-zhi-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-sun-hai-bao-kuo-dan-bu-xian-yu-yin-shang-yu-sun-shi-ting-gong-ji-suan-ji-gu-zhang-huo-gu-zhang-yin-qi-de-sun-hai-pei-chang-huo-ren-he-ji-suo-you-qi-ta-shang-ye-sun-hai-huo-sun-shi-you-shi-yong-zhe-fu-ze": "3.由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。", "4-jin-zhi-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-dui-yu-shi-yong-zhe-zai-ming-zhi-huo-bu-zhi-dang-di-fa-lv-fa-gui-bu-yun-xu-de-qing-kuang-xia-shi-yong-ben-xiang-mu-suo-zao-cheng-de-ren-he-wei-fa-wei-gui-hang-wei-you-shi-yong-zhe-cheng-dan-ben-xiang-mu-bu-cheng-dan-you-ci-zao-cheng-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-ze-ren": "4. 禁止在违反当地法律法规的情况下使用本项目。对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。", "5-yin-le-ping-tai-bu-yi-qing-zun-zhong-ban-quan-zhi-chi-zheng-ban": "5. 音乐平台不易,请尊重版权,支持正版。", "6-ben-xiang-mu-jin-yong-yu-dui-ji-shu-ke-hang-xing-de-tan-suo-ji-yan-jiu-bu-jie-shou-ren-he-shang-ye-bao-kuo-dan-bu-xian-yu-guang-gao-deng-he-zuo-ji-juan-zeng": "6. 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。", "7-ru-guo-guan-fang-yin-le-ping-tai-jue-de-ben-xiang-mu-bu-tuo-ke-lian-xi-ben-xiang-mu-geng-gai-huo-yi-chu": "7. 如果官方音乐平台觉得本项目不妥,可联系本项目更改或移除。", "yong-hu-tiao-kuan": "用户条款", "1-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "1. 本程序是酷狗第三方客户端,并非酷狗官方,需要更完善的功能请下载官方客户端体验.", "2-ben-xiang-mu-jin-gong-xue-xi-jiao-liu-shi-yong-nin-zai-shi-yong-guo-cheng-zhong-ying-zun-zhong-ban-quan-bu-de-yong-yu-shang-ye-huo-fei-fa-yong-tu": "2. 本项目仅供学习交流使用,您在使用过程中应尊重版权,不得用于商业或非法用途。", "3-zai-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-sheng-cheng-ban-quan-nei-rong-ben-xiang-mu-bu-yong-you-zhe-xie-ban-quan-nei-rong-de-suo-you-quan-wei-le-bi-mian-qin-quan-hang-wei-nin-xu-zai-24-xiao-shi-nei-qing-chu-you-ben-xiang-mu-chan-sheng-de-ban-quan-nei-rong": "3. 在使用本项目的过程中,可能会生成版权内容。本项目不拥有这些版权内容的所有权。为了避免侵权行为,您需在 24 小时内清除由本项目产生的版权内容。", "4-ben-xiang-mu-de-kai-fa-zhe-bu-dui-yin-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-suo-dao-zhi-de-ren-he-sun-hai-cheng-dan-ze-ren-bao-kuo-dan-bu-xian-yu-shu-ju-diu-shi-ting-gong-ji-suan-ji-gu-zhang-huo-qi-ta-jing-ji-sun-shi": "4. 本项目的开发者不对因使用或无法使用本项目所导致的任何损害承担责任,包括但不限于数据丢失、停工、计算机故障或其他经济损失。", "5-nin-bu-de-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-yin-wei-fan-fa-lv-fa-gui-suo-dao-zhi-de-ren-he-fa-lv-hou-guo-you-yong-hu-cheng-dan": "5. 您不得在违反当地法律法规的情况下使用本项目。因违反法律法规所导致的任何法律后果由用户承担。", "6-ben-xiang-mu-jin-yong-yu-ji-shu-tan-suo-he-yan-jiu-bu-jie-shou-ren-he-shang-ye-he-zuo-guang-gao-huo-juan-zeng-ru-guo-guan-fang-yin-le-ping-tai-dui-ci-xiang-mu-cun-you-yi-lv-ke-sui-shi-lian-xi-kai-fa-zhe-yi-chu-xiang-guan-nei-rong": "6. 本项目仅用于技术探索和研究,不接受任何商业合作、广告或捐赠。如果官方音乐平台对此项目存有疑虑,可随时联系开发者移除相关内容。", "tong-yi-ji-xu-shi-yong-ben-xiang-mu-nin-ji-jie-shou-yi-shang-tiao-kuan-sheng-ming-nei-rong": "同意继续使用本项目,您即接受以上条款声明内容。", "tong-yi": "同意", "bu-tong-yi": "不同意", "que-ding": "确定", "qu-xiao": "取消", "shao-nv-qi-dao-zhong": "少女祈祷中....", "zan-wu-ge-ci": "暂无歌词", "ni-huan-mei-you-tian-jia-ge-quo-kuai-qu-tian-jia-ba": "你还没有添加歌曲哦,快去添加吧!", "huo-qu-ge-dan-shi-bai": "获取歌单失败", "huo-qu-yin-le-shi-bai": "获取音乐失败", "3-miao-hou-zi-dong-qie-huan-xia-yi-shou": "3秒后自动切换下一首", "huo-qu-yin-le-di-zhi-shi-bai": "获取音乐地址失败", "huo-qu-ge-ci-zhong": "获取歌词中...", "huo-qu-ge-ci-shi-bai": "获取歌词失败", "dan-qu-xun-huan": "单曲循环", "lie-biao-xun-huan": "列表循环", "shun-xu-bo-fang": "顺序播放", "sui-ji-bo-fang": "随机播放", "bo-fang-lie-biao": "播放列表", "zheng-zai-sheng-cheng-er-wei-ma": "正在生成二维码...", "zhe-li-shi-mo-du-mei-you": "这里什么都没有", "wu-sun-yin-zhi-1104kbps": "无损音质 - 1104kbps", "gao-pin-zhi-yin-le-xu-yao-deng-lu-hou-cai-neng-bo-fango": "高品质音乐需要登录后才能播放哦~", "tian-jia-ge-dan": "添加到歌单", "qing-xian-deng-lu": "请先登录", "cheng-gong-tian-jia-dao-ge-dan": "成功添加到歌单!", "tian-jia-dao-ge-dan-shi-bai": "添加到歌单失败!", "cheng-gong-qu-xiao-shou-cang": "取消收藏", "qu-xiao-shou-cang-shi-bai": "取消失败", "ni-que-ren-yao-tui-chu-deng-lu-ma": "你确认要退出登录吗?", "gai-ge-qu-zan-wu-ban-quan": "该歌曲暂无版权", "wo-shou-cang-de-zhuan-ji": "我收藏的专辑", "bo-fang-shi-bai": "播放失败", "wo-guan-zhu-de-hao-you": "我关注的好友", "tian-jia-cheng-gong": "添加成功", "chuang-jian-ge-dan": "创建歌单", "qing-shu-ru-xin-de-ge-dan-ming-cheng": "请输入新的歌单名称", "chuang-jian-shi-bai": "创建失败", "que-ren-shan-chu-ge-dan": "确认删除歌单?", "shou-cang-cheng-gong": "收藏成功", "shou-cang-shi-bai": "收藏失败", "wo-guan-zhu-de-yi-ren": "我关注的音乐人", "sou-suo-ge-qu": "搜索歌曲...", "dang-qian-bo-fang-ge-qu": "当前播放歌曲", "fan-hui-ding-bu": "返回顶部", "yi-fu-zhi-fen-xiang-ma-qing-zai-moekoe-ke-hu-duan-zhong-fang-wen": "已复制分享码,请在MoeKoe客户端中查看", "kou-ling-yi-fu-zhi,kuai-ba-ge-qu-fen-xiang-gei-peng-you-ba": "已复制,快把歌曲分享给朋友吧~", "ge-qu-shu-ju-cuo-wu": "歌单不存在", "xi-tong": "系统", "quan-ju-kuai-jie-jian": "全局快捷键", "zi-ding-yi-kuai-jie-jian": "自定义快捷键", "kuai-jie-jian-she-zhi": "快捷键设置", "xian-shi-yin-cang-zhu-chuang-kou": "显示/隐藏主窗口", "tui-chu-zhu-cheng-xu": "退出主程序", "shang-yi-shou": "上一首", "xia-yi-shou": "下一首", "zan-ting-bo-fang": "暂停/播放", "yin-liang-zeng-jia": "音量增加", "yin-liang-jian-xiao": "音量减小", "jing-yin": "静音", "bao-cun": "保存", "fei-ke-hu-duan-huan-jing-wu-fa-qi-yong": "非客户端环境,无法启用", "qing-an-xia-xiu-shi-jian": "请按下修饰键", "qing-an-xia-qi-ta-jian": "+ [请按下其他键]", "kuai-jie-jian-bi-xu-bao-han-zhi-shao-yi-ge-xiu-shi-jian-ctrlaltshiftcommand": "快捷键必须包含至少一个修饰键(Ctrl/Alt/Shift/Command)", "gai-kuai-jie-jian-yu": "该快捷键与", "de-kuai-jie-jian-chong-tu": "的快捷键冲突", "cun-zai-wu-xiao-de-kuai-jie-jian-she-zhi-qing-que-bao-mei-ge-kuai-jie-jian-du-bao-han-xiu-shi-jian": "存在无效的快捷键设置,请确保每个快捷键都包含修饰键", "bao-cun-she-zhi-shi-bai": "保存设置失败", "mei-ri-tui-jian": "每日推荐", "mei-you-zheng-zai-bo-fang-de-ge-qu": "没有在播放的歌曲", "shou-cang-dao": "收藏到", "mei-you-ge-dan": "还没有歌单", "jin-yong-gpu-jia-su-zhong-qi-sheng-xiao": "禁用GPU加速", "jie-mian": "界面", "guan-bi-shi-minimize-to-tray": "关闭窗口时到托盘", "mi-gan-cheng": "蜜柑橙", "zhu-ce": "还没有账号?", "xin-zeng-zhang-hao-qing-xian-zai-guan-fang-ke-hu-duan-zhong-deng-lu-yi-ci": "新注册账号请先在官方客户端中登录一次", "shua-xin-hou-sheng-xiao": "(刷新后生效)", "zhong-qi-hou-sheng-xiao": "(重启后生效)", "shi-pei-gao-dpi": "启用高DPI支持", "hires-yin-zhi": "Hi-Res音质", "kui-she-chao-qing-yin-zhi": "蝰蛇超清音质", "tian-jia-wo-xi-huan": "添加至我喜欢", "qie-huan-bo-fang-mo-shi": "切换播放模式", "ke-yong": "可用", "wo-de-yun-pan": "我的云盘", "yun-pan-ge-qu-shu": "云盘歌曲数", "yun-pan-miao-shu": "这里存放您上传到云盘的音乐文件,支持在线播放和管理。", "yun-pan-ge-qu": "云盘歌曲", "cong-yun-pan-shan-chu": "从云盘删除", "que-ren-shan-chu-yun-pan-ge-qu": "确认从云盘删除选中歌曲?", "shang-chuan-yin-le": "上传", "pi-liang-cao-zuo": "批量操作", "pwa-app": "PWA应用", "install": "安装", "yin-pin-jia-zai-shi-bai": "音频加载失败", "guan-zhu": "关注", "fen-si": "粉丝", "hao-you": "好友", "fang-wen": "访问", "qian-dao": "签到", "xiao-shi": "小时", "fen-zhong": "分钟", "le-ling": "乐龄", "nian": "年", "ting-ge-shi-chang": "听歌时长", "zheng-zai-jia-zai-quan-bu-ge-qu": "正在加载全部歌曲...", "bo-fang-chu-cuo": "播放出错", "bo-fang-shi-bai-qu-mu-wei-kong": "播放失败,曲目为空", "shan-chu-cheng-gong": "删除成功", "tian-jia-dao-bo-fang-lie-biao-cheng-gong": "添加到播放列表成功", "hot": "热门", "new": "最新", "yun-pan-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "云盘音乐不支持添加到歌单", "xian-qu-kan-kan-ni-de-shou-cang-jia-ba": "先去看看你的收藏夹吧", "yi-da-dao-zui-da-chong-shi-ci-shu": "已达到最大重试次数,请手动选择歌曲", "hui-fu-chu-chang-she-zhi-cheng-gong": "恢复出厂设置成功,请重启应用", "liu-lan-qi-bu-zhi-chi-file-system-api": "浏览器不支持 File System API,请使用 Chrome 86+ 或 Edge 86+", "cha-jian-an-zhuang-cheng-gong": "插件安装成功", "zhi-chi-http-https-dai-li": "仅支持 HTTP/HTTPS 代理", "ben-di-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "本地音乐不支持添加到歌单", "mei-you-xuan-ze-zheng-que-de-ge-qu": "没有选择正确的歌曲", "zhuang-tai-lan-ge-ci-jin-zhi-chi-mac": "状态栏歌词仅支持 macOS", "qian-dao-shi-bai": "签到失败,请勿频繁签到", "huo-qu-vip-shi-bai": "获取VIP失败,每天仅可获取一次", "qing-zai-web-huan-jing-xia-an-zhuang": "请在 Web 环境下安装", "qing-shu-ru-you-xiao-de-url": "请输入有效的 URL", "zhe-shi-yi-ge-alert": "提示", "fei-mac-bu-zhi-chi-touchbar": "非 Mac 不支持 TouchBar", "zi-ti-she-zhi": "字体设置", "mo-ren-zi-ti": "默认字体", "hui-fu-chu-chang-she-zhi": "重置应用", "zi-ti-url-di-zhi": "字体URL地址", "qing-shu-ru-zi-ti-url-di-zhi": "请输入字体URL地址", "zi-ti-ming-cheng": "字体名称", "qing-shu-ru-zi-ti-ming-cheng": "请输入字体名称", "cha-jian": "插件", "shua-xin-cha-jian": "刷新插件", "da-kai-cha-jian-mu-lu": "打开插件目录", "an-zhuang-cha-jian": "安装插件", "zan-wu-cha-jian": "暂无插件", "jiang-cha-jian-wen-jian-jia-fang-ru-cha-jian-mu-lu": "将插件文件夹放入插件目录中,然后点击刷新按钮", "kai-ji-zi-qi-dong": "开机自启动", "wang-luo-mo-shi": "网络模式", "zhu-wang": "主网", "qi-dong-shi-zui-xiao-hua": "启动时最小化", "zu-zhi-xi-tong-xiu-mian": "阻止系统休眠", "api-mo-shi": "API模式", "wang-luo-dai-li": "网络代理", "zhuang-tai-lan-ge-ci": "状态栏歌词", "ge-ci-fan-yi": "歌词翻译", "dui-qi-fang-shi": "对齐方式", "ju-zhong": "居中", "ju-zuo": "居左", "ju-you": "居右", "ping-heng-yin-pin-xiang-du": "平衡音频响度", "shu-ju-yuan": "数据源", "suo-fang-yin-zi": "缩放因子", "tiao-zheng-hou-xu-zhong-qi": "调整后需重启", "api-di-zhi": "API地址", "websocket-di-zhi": "WebSocket地址", "mo-ren-api-ti-shi": "这是默认API地址,当前版本不支持自定义修改", "dai-li-placeholder": "输入HTTP/HTTPS代理地址,例如:http://127.0.0.1:7890", "zheng-zai-ce-shi": "正在测试...", "ce-shi-lian-jie": "测试连接", "bao-cun-she-zhi-an-niu": "保存", "qing-shu-ru-dai-li-di-zhi": "请输入代理服务器地址", "ce-wang": "测试网", "kai-fa-wang": "开发网", "qi-yong": "启用", "jin-yong": "禁用", "dai-li-di-zhi": "代理地址", "gai-nian-ban-xuan-xiang": "概念版", "zheng-shi-ban": "正式版", "dai-li-lian-jie-cheng-gong": "代理连接成功,IP:", "dai-li-lian-jie-shi-bai": "代理连接失败:", "lian-jie-chao-shi": "连接超时", "lian-jie-cuo-wu": "连接错误:", "jin-zhi-chi-mac": "(仅支持macOS)", "xian-shi-yin-cang-zhuo-mian-ge-ci": "显示/隐藏桌面歌词", "ni-que-ren-hui-fu-chu-chang": "确定恢复出厂设置?此操作不可逆!", "bang-zhu": "帮助", "dian-ji-she-zhi-kuai-jie-jian": "点击设置快捷键", "wang-luo-jie-dian": "网络节点", "zi-ti-wen-jian-di-zhi": "字体文件地址", "jia-zai-zhong": "加载中...", "ban-ben": "版本", "yi-qi-yong": "已启用", "da-kai-tan-chuang": "设置", "xie-zai": "卸载", "zheng-zai-jia-zai-cha-jian": "正在加载插件...", "web-cha-jian-ti-shi": "Web端请直接在浏览器插件中心 chrome://extensions/ 进行管理", "da-kai-tan-chuang-shi-bai": "打开插件弹窗失败", "que-ren-xie-zai-cha-jian": "确定要卸载插件name吗?", "xie-zai-cha-jian-shi-bai": "卸载插件失败", "xuan-ze-wen-jian-shi-bai": "选择文件失败", "an-zhuang-cha-jian-shi-bai": "安装插件失败", "an-zhuang-cha-jian-chu-cuo": "安装插件时出错", "cha-jian-bao": "插件包", "zhuo-mian-ge-ci": "桌面歌词", "bo-fang-su-du": "播放速度", "wo-xi-huan": "我喜欢", "shou-cang-zhi": "收藏至", "fen-xiang-ge-qu": "分享歌曲", "qie-huan-dao-yin-yi": "切换到音译", "qie-huan-dao-fan-yi": "切换到翻译", "wei-zhi-cuo-wu": "未知错误" } ================================================ FILE: src/language/zh-TW.json ================================================ { "tui-jian": "推薦", "tui-jian-ge-qu": "推薦歌曲", "tui-jian-ge-dan": "推薦歌單", "fa-xian": "發現", "da": "大", "da-kai": "打開", "gao-yin-zhi-320kbps": "高品音質 - 320Kbps", "ge-ci": "歌詞", "ge-ci-zi-ti-da-xiao": "全屏歌詞字體大小", "guan-bi": "關閉", "guan-bi-an-niu": "關閉", "kai-qi": "開啟", "nan-nan-lan": "天空藍", "pu-tong-yin-zhi": "普通音質 - 128Kbps", "qi-dong-wen-hou-yu": "啟動問候語", "qian-se": "淺色", "shao-nv-fen": "少女粉", "shen-se": "深色", "sheng-yin": "聲音", "tou-ding-lv": "薄荷綠", "wai-guan": "外觀", "native-title-bar":"原生窗口装饰器", "xian-shi-ge-ci-bei-jing": "顯示全屏歌詞背景封面", "xian-shi-zhuo-mian-ge-ci": "顯示桌面歌詞", "xiao": "小", "xuan-ze-wai-guan": "選擇外觀", "xuan-ze-yu-yan": "選擇語言", "xuan-ze-zhu-se-tiao": "選擇主色調", "yin-zhi-xuan-ze": "音質選擇", "yu-yan": "語言", "zhong": "中", "zhu-se-tiao": "主色調", "zi-dong": "自動", "0-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "0. 本程式是酷狗第三方用戶端,並非酷狗官方,需要更完善的功能請下載官方客戶端體驗.", "1-ben-cheng-xu-shi-ku-gou-di-san-fang-ke-hu-duan-bing-fei-ku-gou-guan-fang-xu-yao-geng-wan-shan-de-gong-neng-qing-xia-zai-guan-fang-ke-hu-duan-ti-yan": "1. 本程式是酷狗第三方客戶端,並非酷狗官方,需要更完善的功能請下載官方客戶端體驗.", "1-ben-xiang-mu-jin-gong-xue-xi-shi-yong-qing-zun-zhong-ban-quan-qing-wu-li-yong-ci-xiang-mu-cong-shi-shang-ye-hang-wei-ji-fei-fa-yong-tu": "1. 本項目僅供學習使用,請尊重版權,請勿利用此項目從事商業行為及非法用途!", "2-ben-xiang-mu-jin-gong-xue-xi-jiao-liu-shi-yong-nin-zai-shi-yong-guo-cheng-zhong-ying-zun-zhong-ban-quan-bu-de-yong-yu-shang-ye-huo-fei-fa-yong-tu": "2. 本項目僅供學習交流使用,您在使用過程中應尊重版權,不得用於商業或非法用途。", "2-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-chan-sheng-ban-quan-shu-ju-dui-yu-zhe-xie-ban-quan-shu-ju-ben-xiang-mu-bu-yong-you-ta-men-de-suo-you-quan-wei-le-bi-mian-qin-quan-shi-yong-zhe-wu-bi-zai-24-xiao-shi-nei-qing-chu-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-suo-chan-sheng-de-ban-quan-shu-ju": "2. 使用本項目的過程中可能會產生版權資料。\n對於這些版權數據,本項目不擁有它們的所有權。\n為了避免侵權,使用者請務必在 24 小時內清除使用本項目的過程中所產生的版權資料。", "3-miao-hou-zi-dong-qie-huan-xia-yi-shou": "3秒後自動切換下一首", "3-you-yu-shi-yong-ben-xiang-mu-chan-sheng-de-bao-kuo-you-yu-ben-xie-yi-huo-you-yu-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-er-yin-qi-de-ren-he-xing-zhi-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-sun-hai-bao-kuo-dan-bu-xian-yu-yin-shang-yu-sun-shi-ting-gong-ji-suan-ji-gu-zhang-huo-gu-zhang-yin-qi-de-sun-hai-pei-chang-huo-ren-he-ji-suo-you-qi-ta-shang-ye-sun-hai-huo-sun-shi-you-shi-yong-zhe-fu-ze": "3.因使用本項目產生的包括因本協議或因使用或無法使用本項目而引起的任何性質的任何直接、間接、特殊、偶然或結果性損害(包括但不限於因商譽損失、停工、\n電腦故障或故障所引起的損害賠償,或任何及所有其他商業損害或損失)由使用者負責。", "3-zai-shi-yong-ben-xiang-mu-de-guo-cheng-zhong-ke-neng-hui-sheng-cheng-ban-quan-nei-rong-ben-xiang-mu-bu-yong-you-zhe-xie-ban-quan-nei-rong-de-suo-you-quan-wei-le-bi-mian-qin-quan-hang-wei-nin-xu-zai-24-xiao-shi-nei-qing-chu-you-ben-xiang-mu-chan-sheng-de-ban-quan-nei-rong": "3. 在使用本項目的過程中,可能會產生版權內容。\n本項目不擁有這些版權內容的所有權。\n為了避免侵權行為,您需在 24 小時內清除本項目產生的版權內容。", "4-ben-xiang-mu-de-kai-fa-zhe-bu-dui-yin-shi-yong-huo-wu-fa-shi-yong-ben-xiang-mu-suo-dao-zhi-de-ren-he-sun-hai-cheng-dan-ze-ren-bao-kuo-dan-bu-xian-yu-shu-ju-diu-shi-ting-gong-ji-suan-ji-gu-zhang-huo-qi-ta-jing-ji-sun-shi": "4. 本專案的開發者不對因使用或無法使用本項目所導致的任何損害承擔責任,包括但不限於資料遺失、停工、電腦故障或其他經濟損失。", "4-jin-zhi-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-dui-yu-shi-yong-zhe-zai-ming-zhi-huo-bu-zhi-dang-di-fa-lv-fa-gui-bu-yun-xu-de-qing-kuang-xia-shi-yong-ben-xiang-mu-suo-zao-cheng-de-ren-he-wei-fa-wei-gui-hang-wei-you-shi-yong-zhe-cheng-dan-ben-xiang-mu-bu-cheng-dan-you-ci-zao-cheng-de-ren-he-zhi-jie-jian-jie-te-shu-ou-ran-huo-jie-guo-xing-ze-ren": "4. 禁止在違反當地法律法規的情況下使用本項目。\n對於使用者在明知或不知當地法律法規不允許的情況下使用本項目所造成的任何違法違規行為由使用者承擔,本項目不承擔由此造成的任何直接、間接、特殊、偶然或結果性責任\n。", "5-nin-bu-de-zai-wei-fan-dang-di-fa-lv-fa-gui-de-qing-kuang-xia-shi-yong-ben-xiang-mu-yin-wei-fan-fa-lv-fa-gui-suo-dao-zhi-de-ren-he-fa-lv-hou-guo-you-yong-hu-cheng-dan": "5. 您不得在違反當地法律法規的情況下使用本項目。\n因違反法律法規所導致的任何法律後果由使用者承擔。", "5-yin-le-ping-tai-bu-yi-qing-zun-zhong-ban-quan-zhi-chi-zheng-ban": "5. 音樂平台不易,請尊重版權,支持正版。", "6-ben-xiang-mu-jin-yong-yu-ji-shu-tan-suo-he-yan-jiu-bu-jie-shou-ren-he-shang-ye-he-zuo-guang-gao-huo-juan-zeng-ru-guo-guan-fang-yin-le-ping-tai-dui-ci-xiang-mu-cun-you-yi-lv-ke-sui-shi-lian-xi-kai-fa-zhe-yi-chu-xiang-guan-nei-rong": "6. 本計畫僅用於技術探索和研究,不接受任何商業合作、廣告或捐贈。\n若官方音樂平台對此專案存有疑慮,可隨時聯絡開發者移除相關內容。", "7-ru-guo-guan-fang-yin-le-ping-tai-jue-de-ben-xiang-mu-bu-tuo-ke-lian-xi-ben-xiang-mu-geng-gai-huo-yi-chu": "7. 若官方音樂平台覺得本項目不妥,可聯絡本項目更改或移除。", "bo-fang": "播放", "bo-fang-lie-biao": "播放清單", "bu-tong-yi": "不同意", "chang-ting-ban": "暢聽版:", "dan-qu-xun-huan": "單曲循環", "de-yin-le-ku": "的音樂庫", "deng-lu": "登入", "deng-lu-cheng-gong": "登入成功", "deng-lu-ni-de-ku-gou-zhang-hao": "登入你的酷狗帳號", "deng-lu-shi-bai": "登入失敗", "deng-lu-shi-bai-0": "登入失敗,", "deng-lu-shi-xiao-qing-zhong-xin-deng-lu": "登入失效,請重新登入", "di": "第", "er-wei-ma": "QR 圖碼", "er-wei-ma-deng-lu-cheng-gong": "QR 圖碼登入成功", "er-wei-ma-jian-ce-shi-bai": "二維碼偵測失敗", "er-wei-ma-sheng-cheng-shi-bai": "二維碼產生失敗", "er-wei-ma-yi-guo-qi-qing-zhong-xin-sheng-cheng": "二維碼已過期,請重新生成", "fa-song-yan-zheng-ma": "發送驗證碼", "gai-nian-ban": "概念版:", "geng-xin": "更新", "gong": "共", "guan-yu": "關於", "huo-qu-er-wei-ma-shi-bai": "取得二維碼失敗", "huo-qu-ge-ci-shi-bai": "取得歌詞失敗", "huo-qu-ge-ci-zhong": "取得歌詞中...", "huo-qu-ge-dan-shi-bai": "取得歌單失敗", "huo-qu-yin-le-di-zhi-shi-bai": "取得音樂地址失敗", "huo-qu-yin-le-shi-bai": "獲取音樂失敗", "li-ji-deng-lu": "立即登入", "lie-biao-xun-huan": "清單循環", "login-tips": "萌音 承諾不會將你的任何帳號資訊保存到雲端。\n你的密碼會在本地加密後再傳送到酷狗官方。\n萌音並非酷狗官方網站,輸入帳號資訊前請慎重考慮,非所有帳號都支持帳號密碼登錄.", "mian-ze-sheng-ming": "免責聲明", "ni-huan-mei-you-tian-jia-ge-quo-kuai-qu-tian-jia-ba": "你還沒加歌曲哦,快去添加吧!", "qing-shi-yong-ku-gou-sao-miao-er-wei-ma-deng-lu": "請使用酷狗掃描二維碼登錄", "qing-shu-ru-deng-lu-you-xiang": "請輸入登錄賬號", "qing-shu-ru-mi-ma": "請輸入密碼", "qing-shu-ru-shou-ji-hao": "請輸入手機號", "qing-shu-ru-shou-ji-hao-ma": "請輸入手機號碼", "qing-shu-ru-yan-zheng-ma": "請輸入驗證碼", "qing-shu-ru-you-xiang": "請輸入賬號", "qu-xiao": "取消", "que-ding": "確定", "sao-ma-deng-lu": "掃碼登入", "shang-yi-ye": "上一頁", "shao-nv-qi-dao-zhong": "少女祈禱中....", "she-zhi": "設定", "shi-yong-yan-zheng-ma-deng-lu": "使用驗證碼登入.", "shou-ge": "首歌", "shou-ji-hao-deng-lu": "手機號登入", "shou-ji-hao-ge-shi-cuo-wu": "手機號碼格式錯誤", "shou-ye": "首頁", "shun-xu-bo-fang": "順序播放", "sou-suo-jie-guo": "搜尋結果", "sou-suo-yin-le-ge-shou-ge-dan": "搜尋音樂、歌手、歌單、分享碼...", "sui-ji-bo-fang": "隨機播放", "tong-yi": "同意", "tong-yi-ji-xu-shi-yong-ben-xiang-mu-nin-ji-jie-shou-yi-shang-tiao-kuan-sheng-ming-nei-rong": "同意繼續使用本項目,您即接受以上條款聲明內容。", "tui-chu": "退出", "wo-chuang-jian-de-ge-dan": "我創建的歌單", "wo-guan-zhu-de-ge-shou": "我關注的歌手", "wo-shou-cang-de-ge-dan": "我收藏的歌單", "wo-xi-huan-ting": "我喜歡聽", "wu-xiang-ying-shu-ju": "無回應數據", "xia-yi-ye": "下一頁", "yan-zheng-ma-fa-song-shi-bai": "驗證碼發送失敗", "yan-zheng-ma-fa-song-shi-bai-0": "驗證碼發送失敗,", "yan-zheng-ma-yi-fa-song": "驗證碼已發送", "ye": "頁", "yi-sao-ma-deng-dai-que-ren": "已掃碼,等待確認", "yin-le-ku": "音樂庫", "yong-hu": "使用者", "yong-hu-tiao-kuan": "使用者條款", "yong-hu-tou-xiang": "使用者頭像", "you-xiang-deng-lu": "賬號登錄", "you-xiang-ge-shi-cuo-wu": "郵箱格式錯誤", "zan-wu-ge-ci": "暫無歌詞", "6-ben-xiang-mu-jin-yong-yu-dui-ji-shu-ke-hang-xing-de-tan-suo-ji-yan-jiu-bu-jie-shou-ren-he-shang-ye-bao-kuo-dan-bu-xian-yu-guang-gao-deng-he-zuo-ji-juan-zeng": "6. 本計畫僅用於技術可行性的探索及研究,不接受任何商業(包括但不限於廣告等)合作及捐贈。", "ge-qu-lie-biao": "歌曲清單", "zheng-zai-sheng-cheng-er-wei-ma": "正在產生二維碼...", "zhe-li-shi-mo-du-mei-you": "這裡什麼都沒有", "wu-sun-yin-zhi-1104kbps": "無損音質 - 1104kbps", "gao-pin-zhi-yin-le-xu-yao-deng-lu-hou-cai-neng-bo-fango": "高品質音樂需要登入後才能播放哦~", "tian-jia-ge-dan": "加入歌單", "qing-xian-deng-lu": "請先登入", "cheng-gong-tian-jia-dao-ge-dan": "成功加入歌單!", "tian-jia-dao-ge-dan-shi-bai": "加入歌單失敗!", "cheng-gong-qu-xiao-shou-cang": "取消收藏", "qu-xiao-shou-cang-shi-bai": "取消失敗", "ni-que-ren-yao-tui-chu-deng-lu-ma": "你確認要登出登入嗎?", "gai-ge-qu-zan-wu-ban-quan": "該歌曲暫無版權", "wo-shou-cang-de-zhuan-ji": "我收藏的專輯", "bo-fang-shi-bai": "播放失敗", "wo-guan-zhu-de-hao-you": "我關注的好友", "tian-jia-cheng-gong": "添加成功", "chuang-jian-ge-dan": "建立歌單", "qing-shu-ru-xin-de-ge-dan-ming-cheng": "請輸入新的歌單名稱", "chuang-jian-shi-bai": "創建失敗", "que-ren-shan-chu-ge-dan": "確認刪除歌單?", "shou-cang-shi-bai": "收藏失敗", "shou-cang-cheng-gong": "收藏成功", "wo-guan-zhu-de-yi-ren": "我關注的音樂人", "sou-suo-ge-qu": "搜尋歌曲...", "fan-hui-ding-bu": "回到頂部", "dang-qian-bo-fang-ge-qu": "目前播放歌曲", "yi-fu-zhi-fen-xiang-ma-qing-zai-moekoe-ke-hu-duan-zhong-fang-wen": "已複製分享碼,請在MoeKoe客戶端中查看", "ge-qu-shu-ju-cuo-wu": "歌單不存在", "bao-cun": "儲存", "bao-cun-she-zhi-shi-bai": "保存設置失敗", "cun-zai-wu-xiao-de-kuai-jie-jian-she-zhi-qing-que-bao-mei-ge-kuai-jie-jian-du-bao-han-xiu-shi-jian": "存在無效的快捷鍵設置,請確保每個快捷鍵都包含修飾鍵", "de-kuai-jie-jian-chong-tu": "的快捷鍵衝突", "fei-ke-hu-duan-huan-jing-wu-fa-qi-yong": "非客戶端環境,無法啟用", "gai-kuai-jie-jian-yu": "該快捷鍵與", "jing-yin": "靜音", "kuai-jie-jian-bi-xu-bao-han-zhi-shao-yi-ge-xiu-shi-jian-ctrlaltshiftcommand": "快速鍵必須包含至少一個修飾鍵(Ctrl/Alt/Shift/Command)", "kuai-jie-jian-she-zhi": "快捷鍵設置", "qing-an-xia-qi-ta-jian": "[請按下其他按鍵]", "qing-an-xia-xiu-shi-jian": "請按下修飾鍵", "quan-ju-kuai-jie-jian": "全域快速鍵", "shang-yi-shou": "上一首", "tui-chu-zhu-cheng-xu": "退出主程序", "xi-tong": "系統", "xia-yi-shou": "下一首", "xian-shi-yin-cang-zhu-chuang-kou": "顯示/隱藏主視窗", "yin-liang-jian-xiao": "音量減小", "yin-liang-zeng-jia": "音量增加", "zan-ting-bo-fang": "暫停/播放", "zi-ding-yi-kuai-jie-jian": "自定義快捷鍵", "mei-ri-tui-jian": "每日推薦", "mei-you-zheng-zai-bo-fang-de-ge-qu": "沒有在播放的歌曲", "shou-cang-dao": "收藏到", "mei-you-ge-dan": "還沒有歌單", "jin-yong-gpu-jia-su-zhong-qi-sheng-xiao": "禁用GPU加速", "jie-mian": "介面", "guan-bi-shi-minimize-to-tray": "關閉窗口時到托盤", "mi-gan-cheng": "蜜柑橙", "zhu-ce": "還沒有賬號?", "xin-zeng-zhang-hao-qing-xian-zai-guan-fang-ke-hu-duan-zhong-deng-lu-yi-ci": "新註冊賬號請先在官方客戶端中登錄一次", "shua-xin-hou-sheng-xiao": "(刷新後生效)", "zhong-qi-hou-sheng-xiao": "(重啟後生效)", "shi-pei-gao-dpi": "啟用高DPI支持", "kui-she-chao-qing-yin-zhi": "蝰蛇超清音質", "hires-yin-zhi": "Hi-Res音質", "tian-jia-wo-xi-huan": "添加至我喜歡", "qie-huan-bo-fang-mo-shi": "切換播放模式", "pwa-app": "PWA應用程序", "install": "安裝", "yin-pin-jia-zai-shi-bai": "音頻加載失敗", "zheng-zai-jia-zai-quan-bu-ge-qu": "正在載入全部歌曲...", "bo-fang-chu-cuo": "播放出錯", "bo-fang-shi-bai-qu-mu-wei-kong": "播放失敗,曲目為空", "shan-chu-cheng-gong": "刪除成功", "tian-jia-dao-bo-fang-lie-biao-cheng-gong": "已添加到播放清單", "hot": "熱門", "new": "最新", "yun-pan-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "雲盤音樂不支持添加到歌單", "xian-qu-kan-kan-ni-de-shou-cang-jia-ba": "先去看看你的收藏夾吧", "yi-da-dao-zui-da-chong-shi-ci-shu": "已達到最大重試次數,請手動選擇歌曲", "hui-fu-chu-chang-she-zhi-cheng-gong": "恢復出廠設置成功,請重啟應用", "liu-lan-qi-bu-zhi-chi-file-system-api": "瀏覽器不支持 File System API,請使用 Chrome 86+ 或 Edge 86+", "cha-jian-an-zhuang-cheng-gong": "插件安裝成功", "zhi-chi-http-https-dai-li": "僅支持 HTTP/HTTPS 代理", "ben-di-yin-le-bu-zhi-chi-tian-jia-dao-ge-dan": "本地音樂不支持添加到歌單", "mei-you-xuan-ze-zheng-que-de-ge-qu": "沒有選擇正確的歌曲", "zhuang-tai-lan-ge-ci-jin-zhi-chi-mac": "狀態欄歌詞僅支持 macOS", "qian-dao-shi-bai": "簽到失敗,請勿頻繁簽到", "huo-qu-vip-shi-bai": "獲取VIP失敗,每天僅可獲取一次", "qing-zai-web-huan-jing-xia-an-zhuang": "請在 Web 環境下安裝", "qing-shu-ru-you-xiao-de-url": "請輸入有效的 URL", "zhe-shi-yi-ge-alert": "提示", "fei-mac-bu-zhi-chi-touchbar": "非 Mac 不支持 TouchBar", "zi-ti-she-zhi": "字體設置", "mo-ren-zi-ti": "默認字體", "hui-fu-chu-chang-she-zhi": "重置應用", "zi-ti-url-di-zhi": "字體URL地址", "qing-shu-ru-zi-ti-url-di-zhi": "請輸入字體URL地址", "zi-ti-ming-cheng": "字體名稱", "qing-shu-ru-zi-ti-ming-cheng": "請輸入字體名稱", "cha-jian": "插件", "shua-xin-cha-jian": "刷新插件", "da-kai-cha-jian-mu-lu": "打開插件目錄", "an-zhuang-cha-jian": "安裝插件", "zan-wu-cha-jian": "暫無插件", "jiang-cha-jian-wen-jian-jia-fang-ru-cha-jian-mu-lu": "將插件文件夾放入插件目錄中,然後點擊刷新按鈕", "kai-ji-zi-qi-dong": "開機自啟動", "wang-luo-mo-shi": "網絡模式", "zhu-wang": "主網", "qi-dong-shi-zui-xiao-hua": "啟動時最小化", "zu-zhi-xi-tong-xiu-mian": "阻止系統休眠", "api-mo-shi": "API模式", "wang-luo-dai-li": "網絡代理", "zhuang-tai-lan-ge-ci": "狀態欄歌詞", "ge-ci-fan-yi": "歌詞翻譯", "dui-qi-fang-shi": "對齊方式", "ju-zhong": "居中", "ju-zuo": "居左", "ju-you": "居右", "ping-heng-yin-pin-xiang-du": "平衡音頻響度", "shu-ju-yuan": "數據源", "suo-fang-yin-zi": "縮放因子", "tiao-zheng-hou-xu-zhong-qi": "調整後需重啟", "api-di-zhi": "API地址", "websocket-di-zhi": "WebSocket地址", "mo-ren-api-ti-shi": "這是默認API地址,當前版本不支持自定義修改", "dai-li-placeholder": "輸入HTTP/HTTPS代理地址,例如:http://127.0.0.1:7890", "zheng-zai-ce-shi": "正在測試...", "ce-shi-lian-jie": "測試連接", "bao-cun-she-zhi-an-niu": "保存", "qing-shu-ru-dai-li-di-zhi": "請輸入代理服務器地址", "ce-wang": "測試網", "kai-fa-wang": "開發網", "qi-yong": "啟用", "jin-yong": "禁用", "dai-li-di-zhi": "代理地址", "gai-nian-ban-xuan-xiang": "概念版", "zheng-shi-ban": "正式版", "dai-li-lian-jie-cheng-gong": "代理連接成功,IP:", "dai-li-lian-jie-shi-bai": "代理連接失敗:", "lian-jie-chao-shi": "連接超時", "lian-jie-cuo-wu": "連接錯誤:", "jin-zhi-chi-mac": "(僅支持macOS)", "xian-shi-yin-cang-zhuo-mian-ge-ci": "顯示/隱藏桌面歌詞", "ni-que-ren-hui-fu-chu-chang": "確定恢復出廠設置?此操作不可逆!", "bang-zhu": "幫助", "dian-ji-she-zhi-kuai-jie-jian": "點擊設置快捷鍵", "wang-luo-jie-dian": "網絡節點", "zi-ti-wen-jian-di-zhi": "字體文件地址", "jia-zai-zhong": "載入中...", "ban-ben": "版本", "yi-qi-yong": "已啟用", "da-kai-tan-chuang": "設定", "xie-zai": "卸載", "zheng-zai-jia-zai-cha-jian": "正在載入插件...", "web-cha-jian-ti-shi": "Web端請直接在瀏覽器插件中心 chrome://extensions/ 進行管理", "da-kai-tan-chuang-shi-bai": "打開插件彈窗失敗", "que-ren-xie-zai-cha-jian": "確定要卸載插件name嗎?", "xie-zai-cha-jian-shi-bai": "卸載插件失敗", "xuan-ze-wen-jian-shi-bai": "選擇文件失敗", "an-zhuang-cha-jian-shi-bai": "安裝插件失敗", "an-zhuang-cha-jian-chu-cuo": "安裝插件時出錯", "cha-jian-bao": "插件包", "zhuo-mian-ge-ci": "桌面歌詞", "bo-fang-su-du": "播放速度", "wo-xi-huan": "我喜歡", "shou-cang-zhi": "收藏至", "fen-xiang-ge-qu": "分享歌曲", "qie-huan-dao-yin-yi": "切換到音譯", "qie-huan-dao-fan-yi": "切換到翻譯", "wei-zhi-cuo-wu": "未知錯誤" } ================================================ FILE: src/layouts/HomeLayout.vue ================================================ ================================================ FILE: src/main.js ================================================ import { createApp } from 'vue'; import { createPinia } from 'pinia'; import piniaPersistedstate from 'pinia-plugin-persistedstate'; import App from './App.vue'; import router from './router/router'; import { formatMilliseconds, getCover, applyColorTheme, setTheme } from '../src/utils/utils'; import ModalPlugin from './plugins/ModalPlugin'; import MessagePlugin from './plugins/MessagePlugin'; import i18n from './utils/i18n'; import '@/assets/themes/dark.css'; import { registerSW } from 'virtual:pwa-register' const app = createApp(App); const pinia = createPinia(); pinia.use(piniaPersistedstate); app.config.globalProperties.$getCover = getCover; app.config.globalProperties.$formatMilliseconds = formatMilliseconds; app.config.globalProperties.$applyColorTheme = applyColorTheme; app.config.globalProperties.$setTheme = setTheme; app.config.errorHandler = (err, vm, info) => { console.error(`全局捕获异常: ${info}`, err); }; app.config.warnHandler = (msg, vm, trace) => { console.warn(`全局捕获警告: ${msg}`, trace); }; window.addEventListener('unhandledrejection', event => { console.error('未处理的 Promise 拒绝:', event.reason); // window.$modal.alert('系统错误'); }); if (!window.electron) { registerSW({ onNeedRefresh() { console.log('有新内容可用,请刷新页面') }, onOfflineReady() { console.log('应用已准备好离线工作') } }) } app.use(pinia); app.use(router); app.use(i18n); app.use(ModalPlugin); app.use(MessagePlugin); app.mount('#app'); ================================================ FILE: src/plugins/MessagePlugin.js ================================================ import { createApp, ref } from 'vue'; import MessageNotification from '@/components/MessageNotification.vue'; export default { install() { const messageInstance = ref(null); const mountMessage = () => { if (!messageInstance.value) { const MessageComponent = createApp(MessageNotification); const div = document.createElement('div'); document.body.appendChild(div); messageInstance.value = MessageComponent.mount(div); } }; // 创建消息方法 const message = (content, type = 'default', duration = 3000) => { mountMessage(); return messageInstance.value.addMessage(content, type, duration); }; // 创建不同类型的消息方法 const success = (content, duration = 3000) => { mountMessage(); return messageInstance.value.success(content, duration); }; const error = (content, duration = 3000) => { mountMessage(); return messageInstance.value.error(content, duration); }; const warning = (content, duration = 3000) => { mountMessage(); return messageInstance.value.warning(content, duration); }; const info = (content, duration = 3000) => { mountMessage(); return messageInstance.value.info(content, duration); }; // 将方法挂载到全局 window.$message = { message, success, error, warning, info }; }, }; ================================================ FILE: src/plugins/ModalPlugin.js ================================================ import { createApp, ref } from 'vue'; import CustomModal from '@/components/CustomModal.vue'; export default { install() { const modal = ref(null); const mountModal = () => { if (!modal.value) { const ModalComponent = createApp(CustomModal); const div = document.createElement('div'); document.body.appendChild(div); modal.value = ModalComponent.mount(div); } }; const customAlert = (message) => { mountModal(); return modal.value.customAlert(message); }; const customConfirm = (message) => { mountModal(); return modal.value.customConfirm(message); }; const customPrompt = (message, defaultValue = '') => { mountModal(); return modal.value.customPrompt(message, defaultValue); }; const showLoading = () => { mountModal(); modal.value.showCustomLoading(); }; const hideLoading = () => { mountModal(); modal.value.hideCustomLoading(); }; window.$modal = { alert: customAlert, confirm: customConfirm, prompt: customPrompt, showLoading, hideLoading, }; }, }; ================================================ FILE: src/router/router.js ================================================ import { createRouter, createWebHashHistory } from 'vue-router'; import HomeLayout from '@/layouts/HomeLayout.vue'; import Home from '@/views/Home.vue'; import Discover from '@/views/Discover.vue'; import Library from '@/views/Library.vue'; import Login from '@/views/Login.vue'; import Settings from '@/views/Settings.vue'; import PlaylistDetail from '@/views/PlaylistDetail.vue'; import Search from '@/views/Search.vue'; import Lyrics from '@/views/Lyrics.vue'; import Ranking from '@/views/Ranking.vue'; import CloudDrive from '@/views/CloudDrive.vue'; import LocalMusic from '@/views/LocalMusic.vue'; import VideoPlayer from '@/views/VideoPlayer.vue'; import { MoeAuthStore } from '@/stores/store'; const routes = [ { path: '/', component: HomeLayout, children: [ { path: '', name: 'Index', component: Home }, { path: '/share', name: 'Share', component: Home }, { path: '/discover', name: 'Discover', component: Discover }, { path: '/library', name: 'Library', component: Library, meta: { requiresAuth: true } }, { path: '/login', name: 'Login', component: Login }, { path: '/settings', name: 'Settings', component: Settings }, { path: '/playlistDetail', name: 'PlaylistDetail', component: PlaylistDetail }, { path: '/search', name: 'Search', component: Search }, { path: '/ranking', name: 'Ranking', component: Ranking }, { path: '/CloudDrive', name: 'CloudDrive', component: CloudDrive }, { path: '/LocalMusic', name: 'LocalMusic', component: LocalMusic }, ], }, { path: '/lyrics', name: 'Lyrics', component: Lyrics }, { path: '/video', name: 'VideoPlayer', component: VideoPlayer }, ]; const router = createRouter({ history: createWebHashHistory(), routes, scrollBehavior(to, from, savedPosition) { if (savedPosition) { return new Promise((resolve) => { setTimeout(() => { resolve({ ...savedPosition, behavior: 'smooth' }); }, 100); }); } if (to.hash) { return { el: to.hash, behavior: 'smooth', top: 80, }; } if (to.path === from.path && JSON.stringify(to.params) === JSON.stringify(from.params)) { return false; } return new Promise((resolve) => { setTimeout(() => { resolve({ top: 0, behavior: 'smooth' }); }, 50); }); } }); // 全局导航守卫 router.beforeEach((to, from, next) => { console.log('完整的路由地址:', to.fullPath); const MoeAuth = MoeAuthStore() // 检查是否需要登录 if (to.matched.some(record => record.meta.requiresAuth)) { if (!MoeAuth.isAuthenticated) { next({ path: '/login', query: { redirect: to.fullPath } }); } } next(); }); export default router; ================================================ FILE: src/stores/musicQueue.js ================================================ import { defineStore } from 'pinia'; export const useMusicQueueStore = defineStore('MusicQueue', { state: () => ({ queue: [], // 播放列表 }), actions: { // 添加歌曲到播放队列 addSong(song) { this.queue.push(song); }, // 设置整个队列 setQueue(newQueue) { this.queue = newQueue; }, // 获取播放队列 getQueue() { return this.queue; }, // 清空指定歌曲 removeSong(index) { this.queue.splice(index, 1); }, // 清空播放队列 clearQueue() { this.queue = []; }, }, persist: { enabled: true, strategies: [ { key: 'MusicQueue', storage: localStorage, paths: ['queue'], }, ], }, }); ================================================ FILE: src/stores/store.js ================================================ import { defineStore } from 'pinia'; import axios from 'axios'; import { getApiBaseUrl } from '../utils/apiBaseUrl'; // 用于设备注册的独立 axios 实例(不带拦截器,避免循环依赖) const registerDeviceApi = axios.create({ baseURL: getApiBaseUrl(), timeout: 10000, }); export const MoeAuthStore = defineStore('MoeData', { state: () => ({ UserInfo: null, // 用户信息 Config: null, // 配置信息 Device: null, // 设备信息 }), actions: { fetchConfig(key) { if (!this.Config) return null; const configItem = this.Config.find(item => item.key === key); return configItem ? configItem.value : null; }, async setData(data) { if (data.UserInfo) this.UserInfo = data.UserInfo; if (data.Config) this.Config = data.Config; }, clearData() { this.UserInfo = null; // 清除用户信息 }, async initDevice() { if (this.Device) return this.Device; try { const response = await registerDeviceApi.get('/register/dev'); const device = response?.data?.data; if (device) { this.Device = device; return device; } } catch (error) { console.error('Failed to register device:', error); } return null; } }, getters: { isAuthenticated: (state) => !!state.UserInfo && !!state.UserInfo, // 是否已登录 }, persist: { enabled: true, strategies: [ { key: 'MoeData', storage: localStorage, paths: ['UserInfo', 'Config', 'Device'], }, ], }, }); ================================================ FILE: src/utils/apiBaseUrl.js ================================================ export const DEFAULT_API_BASE_URL = import.meta.env.VITE_APP_API_URL || 'http://127.0.0.1:6521'; export function normalizeApiBaseUrl(input) { const result = validateApiBaseUrl(input); return result.ok ? result.value : ''; } export function validateApiBaseUrl(input) { const raw = (input ?? '').toString().trim(); if (!raw) return { ok: true, value: '' }; let url; try { url = new URL(raw); } catch { return { ok: false, value: '', error: '请输入完整的 http(s):// 地址' }; } if (!['http:', 'https:'].includes(url.protocol)) { return { ok: false, value: '', error: '仅支持 http:// 或 https://' }; } return { ok: true, value: raw.replace(/\/+$/, '') }; } export function getApiBaseUrl() { try { const settingsRaw = localStorage.getItem('settings'); const settings = settingsRaw ? JSON.parse(settingsRaw) : {}; const custom = normalizeApiBaseUrl(settings?.apiBaseUrl); return custom || DEFAULT_API_BASE_URL; } catch { return DEFAULT_API_BASE_URL; } } export function joinApiUrl(baseUrl, path = '/') { const base = (baseUrl || '').replace(/\/+$/, ''); const rel = (path || '').replace(/^\/+/, ''); return rel ? `${base}/${rel}` : `${base}/`; } export async function testApiBaseUrl(baseUrl, options = {}) { const { path = '/register/dev', timeoutMs = 8000 } = options; const target = joinApiUrl(baseUrl, path); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(target, { method: 'GET', headers: { 'Accept': 'application/json' }, signal: controller.signal, }); if (!response.ok) { return { ok: false, status: response.status, statusText: response.statusText }; } const data = await response.json().catch(() => null); const dfid = data?.data?.dfid; if (typeof dfid !== 'string' || !dfid) { return { ok: false, error: 'no_dfid', data }; } return { ok: true, data, dfid }; } catch (error) { if (error?.name === 'AbortError') return { ok: false, error: 'timeout' }; return { ok: false, error: error?.message || String(error) }; } finally { clearTimeout(timeoutId); } } ================================================ FILE: src/utils/i18n.js ================================================ import { createI18n } from 'vue-i18n'; import en from '../language/en.json'; import ja from '../language/ja.json'; import ko from '../language/ko.json'; import ru from '../language/ru.json'; import zh_CN from '../language/zh-CN.json'; import zh_TW from '../language/zh-TW.json'; const messages = { en, ja, ko, ru, 'zh-CN': zh_CN, 'zh-TW': zh_TW, }; const getBrowserLocale = () => { const browserLang = navigator.language; if (browserLang.startsWith('zh')) { if (browserLang === 'zh-TW' || browserLang === 'zh-HK') { return 'zh-TW'; } return 'zh-CN'; } const lang = browserLang.split('-')[0]; return Object.keys(messages).includes(lang) ? lang : 'ja'; }; const defaultLocale = JSON.parse(localStorage.getItem('settings'))?.['language'] || getBrowserLocale(); const i18n = createI18n({ locale: defaultLocale, fallbackLocale: 'zh-CN', messages, }); export default i18n; ================================================ FILE: src/utils/request.js ================================================ // src/services/request.js import axios from 'axios'; import { MoeAuthStore } from '../stores/store'; import { getApiBaseUrl } from './apiBaseUrl'; // 创建一个 axios 实例 const httpClient = axios.create({ baseURL: getApiBaseUrl(), timeout: 10000, headers: { 'Content-Type': 'application/json', }, withCredentials: true, }); // 请求拦截器 httpClient.interceptors.request.use( config => { const MoeAuth = MoeAuthStore(); const token = MoeAuth.UserInfo?.token; const userid = MoeAuth.UserInfo?.userid; const t1 = MoeAuth.UserInfo?.t1; const dfid = MoeAuth.Device?.dfid; const mid = MoeAuth.Device?.mid; const guid = MoeAuth.Device?.guid; const serverDev = MoeAuth.Device?.serverDev; const mac = MoeAuth.Device?.mac; const authParts = []; if (token) authParts.push(`token=${(token)}`); if (userid) authParts.push(`userid=${(userid)}`); if (dfid) authParts.push(`dfid=${(dfid)}`); if (t1) authParts.push(`t1=${(t1)}`); if (mid) authParts.push(`KUGOU_API_MID=${(mid)}`); if (guid) authParts.push(`KUGOU_API_GUID=${(guid)}`); if (serverDev) authParts.push(`KUGOU_API_DEV=${(serverDev)}`); if (mac) authParts.push(`KUGOU_API_MAC=${(mac)}`); if (authParts.length > 0) { config.headers = { ...config.headers, Authorization: authParts.join(';') }; } return config; }, error => Promise.reject(error) ); // 响应拦截器 httpClient.interceptors.response.use( response => { return response.data; }, error => { if (error.response) { console.error(`http error status:${error.response.status}`,error.response.data); if (error.response?.data?.data) { console.error(error.response.data.data); // } else { // $message.error('服务器错误,请稍后再试!'); } } else if (error.request) { console.error('No response received:', error.request); $message.error('服务器未响应,请稍后再试!'); } else { console.error('Error:', error.message); $message.error('请求错误,请稍后再试!'); } return Promise.reject(error); } ); // 封装 GET 请求 export const get = async (url, params = {}, config = {}, onSuccess = null, onError = null) => { try { const response = await httpClient.get(url, { params, ...config }); if (onSuccess) onSuccess(response); return response; } catch (error) { if (onError) onError(error); throw error; } }; // 封装 POST 请求 export const post = async (url, data = {}, config = {}, onSuccess = null, onError = null) => { try { const response = await httpClient.post(url, data, config); if (onSuccess) onSuccess(response); return response; } catch (error) { if (onError) onError(error); throw error; } }; // 封装 PUT 请求 export const put = async (url, data = {}, config = {}, onSuccess = null, onError = null) => { try { const response = await httpClient.put(url, data, config); if (onSuccess) onSuccess(response); return response; } catch (error) { if (onError) onError(error); throw error; } }; // 封装 DELETE 请求 export const del = async (url, config = {}, onSuccess = null, onError = null) => { try { const response = await httpClient.delete(url, config); if (onSuccess) onSuccess(response); return response; } catch (error) { if (onError) onError(error); throw error; } }; // 封装 PATCH 请求 export const patch = async (url, data = {}, config = {}, onSuccess = null, onError = null) => { try { const response = await httpClient.patch(url, data, config); if (onSuccess) onSuccess(response); return response; } catch (error) { if (onError) onError(error); throw error; } }; // 封装上传图片请求 export const uploadImage = async (url, file, additionalData = {}, config = {}, onSuccess = null, onError = null) => { try { const formData = new FormData(); formData.append('file', file); // 如果有其他数据(如关联的商品信息等),也可以添加到 formData for (const key in additionalData) { if (Object.prototype.hasOwnProperty.call(additionalData, key)) { formData.append(key, additionalData[key]); } } // 需要确保 Content-Type 被设置为 multipart/form-data const response = await httpClient.post(url, formData, { ...config, headers: { ...config.headers, 'Content-Type': 'multipart/form-data' } }); if (onSuccess) onSuccess(response); return response; } catch (error) { if (onError) onError(error); throw error; } }; // 导出 httpClient 以便在需要的时候直接使用 axios 实例 export default httpClient; ================================================ FILE: src/utils/utils.js ================================================ import i18n from '@/utils/i18n'; export const applyColorTheme = (theme) => { let colors; if (theme === 'blue') { colors = { '--primary-color': '#4A90E2', '--secondary-color': '#AEDFF7', '--background-color': '#E8F4FA', '--background-color-secondary': '#D9EEFA', '--color-primary': '#2A6DAF', '--color-primary-light': 'rgba(74, 144, 226, 0.1)', '--border-color': '#C5E0F5', '--hover-color': '#D1E9F9', '--color-secondary-bg-for-transparent': 'rgba(174, 223, 247, 0.28)', '--color-box-shadow': 'rgba(74, 144, 226, 0.2)', }; } else if (theme === 'green') { colors = { '--primary-color': '#34C759', '--secondary-color': '#A7F3D0', '--background-color': '#E5F9F0', '--background-color-secondary': '#D0F5E6', '--color-primary': '#28A745', '--color-primary-light': 'rgba(52, 199, 89, 0.1)', '--border-color': '#B8ECD7', '--hover-color': '#C9F2E2', '--color-secondary-bg-for-transparent': 'rgba(167, 243, 208, 0.28)', '--color-box-shadow': 'rgba(52, 199, 89, 0.2)', }; } else if (theme === 'orange') { colors = { '--primary-color': '#ff6b6b', '--secondary-color': '#FFB6C1', '--background-color': '#FFF0F5', '--background-color-secondary': '#FFE6EC', '--color-primary': '#ea33e4', '--color-primary-light': 'rgba(255, 107, 107, 0.1)', '--border-color': '#FFDCE3', '--hover-color': '#FFE9EF', '--color-secondary-bg-for-transparent': 'rgba(209, 209, 214, 0.28)', '--color-box-shadow': 'rgba(255, 105, 180, 0.2)', }; } else { colors = { '--primary-color': '#FF69B4', '--secondary-color': '#FFB6C1', '--background-color': '#FFF0F5', '--background-color-secondary': '#FFE6F0', '--color-primary': '#ea33e4', '--color-primary-light': 'rgba(255, 105, 180, 0.1)', '--border-color': '#FFD9E6', '--hover-color': '#FFE9F2', '--color-secondary-bg-for-transparent': 'rgba(209, 209, 214, 0.28)', '--color-box-shadow': 'rgba(255, 105, 180, 0.2)', }; } Object.keys(colors).forEach(key => { document.documentElement.style.setProperty(key, colors[key]); }); }; export const getCover = (coverUrl, size) => { if (!coverUrl) return './assets/images/ico.png'; return coverUrl.replace("{size}", size); }; export const formatMilliseconds = (time) => { const milliseconds = time > 3600 ? time : time * 1000; const totalSeconds = Math.floor(milliseconds / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return `${minutes}分${seconds}秒`; }; export const requestMicrophonePermission = async () => { if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) return false; try { if (navigator.permissions?.query) { const status = await navigator.permissions.query({ name: 'microphone' }); if (status.state === 'granted') { // 不会弹窗 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); stream.getTracks().forEach(track => track.stop()); return true; } if (status.state === 'denied') return false; } } catch { // permissions API 在部分环境不可用/会抛错(例如 Safari),直接走 getUserMedia } try { // 可能弹窗申请权限 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); stream.getTracks().forEach(track => track.stop()); return true; } catch { return false; } }; export const getAudioOutputDeviceSignature = async () => { if (typeof navigator === 'undefined' || !navigator.mediaDevices?.enumerateDevices) return null; const devices = await navigator.mediaDevices.enumerateDevices(); const signatures = devices .filter(device => device.kind === 'audiooutput') .map(device => `${device.deviceId || ''}:${device.groupId || ''}`) .sort(); return signatures.join('|'); }; let themeMediaQueryListener = null; export const setTheme = (theme) => { const html = document.documentElement; const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); if (themeMediaQueryListener) { prefersDarkScheme.removeEventListener('change', themeMediaQueryListener); themeMediaQueryListener = null; } const applyTheme = (isDark) => { if (isDark) { html.classList.add('dark'); } else { html.classList.remove('dark'); } }; switch (theme) { case 'dark': applyTheme(true); localStorage.setItem('theme', 'dark'); break; case 'light': applyTheme(false); localStorage.setItem('theme', 'light'); break; case 'auto': localStorage.setItem('theme', 'auto'); applyTheme(prefersDarkScheme.matches); themeMediaQueryListener = (e) => { applyTheme(e.matches); }; prefersDarkScheme.addEventListener('change', themeMediaQueryListener); break; } }; export const openRegisterUrl = (registerUrl) => { if (window.electron) { window.electron.ipcRenderer.send('open-url', registerUrl); } else { window.open(registerUrl, '_blank'); } }; // 分享 import { MoeAuthStore } from '../stores/store'; export const share = (songName, id, type = 0, songDesc = '') => { let text = ''; const MoeAuth = MoeAuthStore(); let userName = '萌音'; if(MoeAuth.isAuthenticated) { userName = MoeAuth.UserInfo?.nickname || '萌音'; }; // 客户端分享 let shareUrl = ''; if (window.electron) { if(type == 0){ // 歌曲 shareUrl = `https://music.moekoe.cn/share/?hash=${id}`; }else{ // 歌单 shareUrl = `moekoe://share?listid=${id}`; } } else { // Web / H5 逻辑 shareUrl = (window.location.host + '/#/') + (type == 0 ? `share/?hash=${id}` : `share?listid=${id}`); } text = `你的好友@${userName}分享了${songDesc}《${songName}》给你,快去听听吧! ${shareUrl}`; navigator.clipboard.writeText(text); $message.success( i18n.global.t('kou-ling-yi-fu-zhi,kuai-ba-ge-qu-fen-xiang-gei-peng-you-ba') ); }; ================================================ FILE: src/views/CloudDrive.vue ================================================ ================================================ FILE: src/views/Discover.vue ================================================ ================================================ FILE: src/views/Home.vue ================================================ ================================================ FILE: src/views/Library.vue ================================================ ================================================ FILE: src/views/LocalMusic.vue ================================================ ================================================ FILE: src/views/Login.vue ================================================ ================================================ FILE: src/views/Lyrics.vue ================================================ ================================================ FILE: src/views/PlaylistDetail.vue ================================================ ================================================ FILE: src/views/Ranking.vue ================================================ ================================================ FILE: src/views/Search.vue ================================================ ================================================ FILE: src/views/Settings.vue ================================================ ================================================ FILE: src/views/VideoPlayer.vue ================================================